diff --git a/.devin/wiki.json b/.devin/wiki.json new file mode 100644 index 0000000000..3e491c4855 --- /dev/null +++ b/.devin/wiki.json @@ -0,0 +1,181 @@ +{ + "repo_notes": [ + { + "content": "请将整个 WxJava Wiki 以简体中文生成。所有页面标题、正文、图表标题、表格标题和说明文字均使用中文。代码、类名、方法名、包名、配置键和 Maven artifactId 保持原样;首次出现的专业术语可写为中文(English)。内容应面向 Java 开发者,准确说明公众号、小程序、企业微信、微信支付等模块。" + } + ], + "pages": [ + { + "title": "WxJava 概览", + "purpose": "介绍 WxJava 仓库,说明其核心用途、模块结构和一般使用方式,并链接到模块架构与快速入门等子页面。", + "page_notes": [{ "content": "" }] + }, + { + "title": "模块架构", + "purpose": "说明 WxJava 的整体模块架构及组件依赖关系,包括通用核心库、服务模块、Spring Boot Starter、Solon 插件和 GraalVM 支持。", + "parent": "WxJava 概览", + "page_notes": [{ "content": "" }] + }, + { + "title": "快速入门", + "purpose": "提供在不同场景中使用 WxJava 的快速入门说明,包括 Maven/Gradle 依赖配置、基础服务初始化和 HTTP 客户端选择。", + "parent": "WxJava 概览", + "page_notes": [{ "content": "" }] + }, + { + "title": "通用组件", + "purpose": "记录所有 WxJava 模块共用的工具类、常量和通用模式,并链接到消息处理与 HTTP 基础设施等子页面。", + "page_notes": [{ "content": "" }] + }, + { + "title": "消息处理框架", + "purpose": "说明不同模块使用的核心消息处理与路由架构,包括 WxConsts 消息类型、XML 消息解析、XStream 序列化,以及路由器、处理器和拦截器模式。", + "parent": "通用组件", + "page_notes": [{ "content": "" }] + }, + { + "title": "HTTP 客户端基础设施", + "purpose": "记录 HTTP 客户端抽象层,包括 RequestExecutor 策略模式、Apache HttpClient 4/5、OkHttp 和 Jodd-Http 支持、代理配置及 HttpClient 升级指南。", + "parent": "通用组件", + "page_notes": [{ "content": "" }] + }, + { + "title": "分布式状态与锁", + "purpose": "说明 WxJava 如何借助 Redis(Jedis、Redisson、Spring Data Redis)管理 access token 等凭据的分布式状态,包括 WxRedisOps 抽象和分布式锁实现。", + "parent": "通用组件", + "page_notes": [{ "content": "" }] + }, + { + "title": "微信公众号(MP)模块", + "purpose": "记录用于接入微信公众号的 MP 模块,概述 WxMpService、配置方式,并链接到服务实现、消息路由和子服务等页面。", + "page_notes": [{ "content": "" }] + }, + { + "title": "MP 服务实现", + "purpose": "详述 WxMpService 及其 Apache、OkHttp、Jodd HTTP 客户端实现、access token 生命周期、多账号切换,以及 BaseWxMpServiceImpl 请求处理流程。", + "parent": "微信公众号(MP)模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "MP 消息路由", + "purpose": "说明 MP 模块的消息路由、处理器和拦截器,包括 WxMpMessageRouter 规则配置、WxMpXmlMessage 解析和 XML 被动回复消息构建器。", + "parent": "微信公众号(MP)模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "MP 子服务", + "purpose": "记录 MP 模块的专用子服务,包括用户管理、客服(Kefu)、模板消息、群发消息、素材管理、卡券、菜单、二维码、数据立方体分析和订阅通知。", + "parent": "微信公众号(MP)模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "企业微信(CP)模块", + "purpose": "记录接入企业微信的 CP 模块,概述 WxCpService、配置存储,并链接到客户联系、用户管理、OA 功能和第三方平台等子页面。", + "page_notes": [{ "content": "" }] + }, + { + "title": "CP 客户联系管理", + "purpose": "说明如何管理企业微信外部联系人(客户),包括 ContactWay 配置、客户详情获取、ID 迁移、群聊管理、客户转接、拦截规则和商品图册。", + "parent": "企业微信(CP)模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "CP 用户管理", + "purpose": "详述企业微信的用户管理操作,包括 WxCpUserService、WxCpUser 对象、OAuth2 认证、部门与标签管理、应用管理和人事员工字段管理。", + "parent": "企业微信(CP)模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "CP OA 与消息能力", + "purpose": "记录办公自动化(OA)能力,包括打卡、审批、WeDrive 文件管理,以及 CP 消息能力,包括群机器人、客服(KF)、智能机器人和消息审计(会话存档)。", + "parent": "企业微信(CP)模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "CP 第三方平台(TP)", + "purpose": "记录面向企业微信 ISV 的 CP 第三方平台服务(WxCpTpService),包括永久授权码管理、消息路由、定制应用、版本与订单管理和许可服务。", + "parent": "企业微信(CP)模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "微信支付模块", + "purpose": "记录接入微信支付 API 的支付模块,概述 WxPayService、V2/V3 API 差异,并链接到配置、核心操作和专用支付服务等子页面。", + "page_notes": [{ "content": "" }] + }, + { + "title": "微信支付配置", + "purpose": "说明微信支付的配置选项和安全设置,包括 WxPayConfig、多商户/多 appId 支持、V3 证书管理、AutoUpdateCertificatesVerifier、SignatureExec 和可信主机配置。", + "parent": "微信支付模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "电商服务", + "purpose": "记录微信支付中的专用电商操作,包括服务商模式的 EcommerceService、二级商户进件(Applyment4Sub)、分账(V2/V3)、转账、投诉处理和微信支付分。", + "parent": "微信支付模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "支付核心操作与数据对象", + "purpose": "记录核心支付操作(统一下单、退款、查询、回调通知)以及 V2/V3 请求和结果数据对象的层次结构,包括 WxPayUnifiedOrderRequest、退款对象、订单通知结果和企业付款(EntPay)。", + "parent": "微信支付模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "微信小程序模块", + "purpose": "记录面向微信小程序的 MiniApp 模块,概述 WxMaService、配置方式,并链接到服务实现、消息处理和专用子服务等页面。", + "page_notes": [{ "content": "" }] + }, + { + "title": "小程序服务与配置", + "purpose": "详述 WxMaService 接口、BaseWxMaServiceImpl 请求处理流程、多租户配置、API 签名机制、WxMaConfig 实现(内存、Redis、Redisson)和 HTTP 客户端变体。", + "parent": "微信小程序模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "小程序消息与用户服务", + "purpose": "记录小程序消息路由(WxMaMessageRouter)、WxMaMessage 解析、客服(Kefu)消息、模板/订阅消息、用户服务(jsCode2Session、手机号)、二维码生成和直播服务。", + "parent": "微信小程序模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "小程序代码管理与开放平台集成", + "purpose": "记录小程序代码生命周期管理(提交、审核、发布)、代码模板、类目管理,以及通过开放平台进行第三方小程序管理的集成方式。", + "parent": "微信小程序模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "微信开放平台模块", + "purpose": "记录面向第三方应用开发者的开放平台模块,概述 WxOpenComponentService、授权流程,并链接到组件服务和小程序/公众号管理等子页面。", + "page_notes": [{ "content": "" }] + }, + { + "title": "开放平台组件服务", + "purpose": "详述 WxOpenComponentService 的实现、组件 access token 管理、verify ticket 处理、预授权码生成、授权码交换、服务工厂方法和平台事件消息路由。", + "parent": "微信开放平台模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "开放平台小程序与公众号管理", + "purpose": "记录通过开放平台对小程序(WxOpenMaService)和公众号(WxOpenMpService)的代管理能力,包括代码管理、域名配置、ICP备案、快速注册和隐私设置。", + "parent": "微信开放平台模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "微信视频号模块", + "purpose": "记录面向视频号和微信小店的 weixin-java-channel 模块,涵盖 WxChannelService 接口、商品/订单/售后管理、直播、优选联盟功能,以及 Spring Boot/Solon 集成。", + "page_notes": [{ "content": "" }] + }, + { + "title": "视频号服务 API", + "purpose": "详述 WxChannelService 接口及其子服务,包括商品管理、订单处理、售后处理、优惠券管理、资金操作、VIP 会员和罗盘分析服务。", + "parent": "微信视频号模块", + "page_notes": [{ "content": "" }] + }, + { + "title": "视频号直播与优选联盟能力", + "purpose": "记录视频号直播服务(WxFinderLiveService、WxChannelLiveDashboardService)、优选联盟的供货商、推广员、商品和橱窗服务,以及视频号助手能力。", + "parent": "微信视频号模块", + "page_notes": [{ "content": "" }] + } + ] +} 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/agents/my-agent.agent.md b/.github/agents/my-agent.agent.md new file mode 100644 index 0000000000..bd1b4572eb --- /dev/null +++ b/.github/agents/my-agent.agent.md @@ -0,0 +1,16 @@ +--- +# 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、生成代码时需要提供必要的单元测试代码; +- 3、实现接口时请严格按照官方文档编写代码,严禁瞎编乱造、臆想并实现不存在的接口; +- 4、新增加的代码如果标记作者信息,请注意不要把作者名设为binarywang或者其他无关人员,要改为 GitHub Copilot。 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..cad29d96d9 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,202 @@ +# Copilot Instruction +请始终使用中文生成 Pull Request 的标题、描述和提交信息 + + +# WxJava - 微信 Java SDK 开发说明 + +WxJava 是一个支持多种微信平台的完整 Java SDK,包含公众号、小程序、微信支付、企业微信、开放平台、视频号、企点等多种功能模块。 + +**请始终优先参考本说明,只有在遇到与此内容不一致的意外信息时,才退而使用搜索或 bash 命令。** + +## 高效开发指南 + +### 前置条件与环境准备 +- **Java 要求**:JDK 8+(项目最低目标为 Java 8) +- **Maven**:推荐 Maven 3.6+(已验证 Maven 3.9.11) +- **IDE**:推荐使用 IntelliJ IDEA(项目针对 IDEA 优化) + +### 引导、构建与校验 +克隆仓库后按顺序执行以下命令: + +```bash +# 1. 基础编译(请勿中断 - 约需 4-5 分钟) +mvn clean compile -DskipTests=true --no-transfer-progress +# 超时时间:建议设置 8 分钟以上。实际时间:约 4 分钟 + +# 2. 完整打包(请勿中断 - 约需 2-3 分钟) +mvn clean package -DskipTests=true --no-transfer-progress +# 超时时间:建议设置 5 分钟以上。实际时间:约 2 分钟 + +# 3. 代码质量校验(请勿中断 - 约需 45-60 秒) +mvn checkstyle:check --no-transfer-progress +# 超时时间:建议设置 3 分钟以上。实际时间:约 50 秒 +``` + +重要时间说明: +- 绝对不要中断任意 Maven 构建命令 +- 编译阶段耗时最长(约 4 分钟),原因是项目包含 34 个模块 +- 后续构建会更快,因为存在增量编译 +- 始终使用 `--no-transfer-progress` 以减少日志噪音 + +### 测试结构 +- **测试框架**:TestNG(非 JUnit) +- **测试文件**:共有 298 个测试文件 +- **默认行为**:pom.xml 中默认禁用测试(`true`) +- **测试配置**:测试需要通过 test-config.xml 提供真实的微信 API 凭据 +- **注意**:没有真实微信 API 凭据请不要尝试运行测试,测试将会失败 + +## 项目结构与导航 + +### 核心 SDK 模块(主要开发区) +- `weixin-java-common/` - 通用工具与基础类(最重要) +- `weixin-java-mp/` - 公众号 SDK +- `weixin-java-pay/` - 微信支付 SDK +- `weixin-java-miniapp/` - 小程序 SDK +- `weixin-java-cp/` - 企业微信 SDK +- `weixin-java-open/` - 开放平台 SDK +- `weixin-java-channel/` - 视频号 / Channel SDK +- `weixin-java-qidian/` - 企点 SDK + +### 框架集成模块 +- `spring-boot-starters/` - Spring Boot 自动配置 starter +- `solon-plugins/` - Solon 框架插件 +- `weixin-graal/` - GraalVM 本地镜像支持 + +### 配置与质量控制 +- `quality-checks/google_checks.xml` - Checkstyle 配置 +- `.editorconfig` - 代码格式规则(2 个空格等于 1 个制表) +- `pom.xml` - 根级 Maven 配置 + +## 开发工作流 + +### 修改代码的流程 +1. 修改前务必先构建以建立干净基线: + ```bash + mvn clean compile --no-transfer-progress + ``` + +2. 遵循代码风格(由 checkstyle 强制): + - 缩进使用 2 个空格(不要用制表符) + - 遵循 Google Java 风格指南 + - 在 IDE 中安装 EditorConfig 插件 + +3. 增量验证修改: + ```bash + # 每次修改后运行: + mvn compile --no-transfer-progress + mvn checkstyle:check --no-transfer-progress + ``` + +### 提交修改前的必须校验 +请务必按顺序完成以下校验步骤: + +1. 代码风格校验: + ```bash + mvn checkstyle:check --no-transfer-progress + # 必须通过 - 约需 50 秒 + ``` + +2. 完整清理构建: + ```bash + mvn clean package -DskipTests=true --no-transfer-progress + # 必须成功 - 约需 2 分钟 + ``` + +3. 文档:为公共方法和类补充或更新 javadoc +4. 贡献规范:遵循 `CONTRIBUTING.md`,Pull Request 必须以 `develop` 分支为目标 + +## 模块依赖与构建顺序 + +### 核心模块依赖(构建顺序) +1. `weixin-graal`(GraalVM 支持) +2. `weixin-java-common`(所有模块的基础) +3. 核心 SDK 模块(mp、pay、miniapp、cp、open、channel、qidian) +4. 框架集成(spring-boot-starters、solon-plugins) + +### 主要关系模式 +- 所有 SDK 模块都依赖于 `weixin-java-common` +- Spring Boot starters 依赖对应的 SDK 模块 +- Solon 插件遵循与 Spring Boot starters 相同的依赖模式 +- 每个模块都有单账号与多账号配置支持 + +## 常见任务与命令 + +### 验证指定模块 +```bash +# 构建单个模块(将 'weixin-java-mp' 替换为目标模块): +cd weixin-java-mp +mvn clean compile --no-transfer-progress +``` + +### 检查依赖 +```bash +# 分析依赖树: +mvn dependency:tree --no-transfer-progress + +# 检查依赖更新: +./others/check-dependency-updates.sh +``` + +### 发布与发布准备 +```bash +# 版本检查: +mvn versions:display-property-updates --no-transfer-progress + +# 部署(需要凭据): +mvn clean deploy -P release --no-transfer-progress +``` + +## 重要文件与位置 + +### 配置文件 +- `pom.xml` - 根级 Maven 配置与依赖管理 +- `quality-checks/google_checks.xml` - Checkstyle 规则 +- `.editorconfig` - IDE 格式化配置 +- `.github/workflows/maven-publish.yml` - CI/CD 工作流 + +### 文档 +- `README.md` - 项目概览与使用说明(中文) +- `CONTRIBUTING.md` - 贡献指南 +- `demo.md` - 示例项目与演示链接 +- 每个模块均有单独的文档与示例 + +### 测试资源 +- `*/src/test/resources/test-config.sample.xml` - 测试配置模板 +- 测试运行需要真实的微信 API 凭据 + +## SDK 使用模式 + +### Maven 依赖示例 +```xml + + com.github.binarywang + weixin-java-mp + 4.7.0 + +``` + +### 常见开发区域 +- **API 客户端实现**:位于 `*/service/impl/` 目录 +- **模型类**:位于 `*/bean/` 目录 +- **配置**:位于 `*/config/` 目录 +- **工具类**:位于 `weixin-java-common` 的 `*/util/` 目录 + +## 故障排查 + +### 构建问题 +- **OutOfMemoryError**:增加 Maven 内存:`export MAVEN_OPTS="-Xmx2g"` +- **编译失败**:通常为依赖问题 - 先执行 `mvn clean` +- **Checkstyle 失败**:检查 IDE 的 `.editorconfig` 设置 + +### 常见陷阱 +- **测试默认跳过**:这是正常现象 — 测试需要微信 API 凭据 +- **多模块变更**:总是在仓库根目录构建,而不是单独模块 +- **分支目标**:Pull Request 必须以 `develop` 分支为目标,而不是 `master` 或 `release` + +## 性能说明 +- **首次构建**:由于依赖下载,耗时 4-5 分钟 +- **增量构建**:通常更快(约 30-60 秒) +- **Checkstyle**:运行迅速(约 50 秒),应当经常运行 +- **IDE 性能**:项目使用 Lombok,请确保启用注解处理 + +注意:本项目为 SDK 库项目,而非可运行应用。修改应以 API 功能为主,不要改动应用级行为。 diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml new file mode 100644 index 0000000000..3dcd4db0eb --- /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@v5 + 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@v5 + 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 6a5b5f7519..eda4f369c8 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,13 @@ sonar-project.properties !/.mvn/wrapper/maven-wrapper.jar *.versionsBackup +# Local Superpowers planning artifacts +docs/superpowers/ + # STS .factorypath *.zip +.worktrees +# Local Superpowers working documents; do not commit. +/docs/superpowers/ +/.firecrawl/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..aaec80bb5c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,130 @@ +# WxJava Agent 指南 + +## 适用范围与指令优先级 + +- 本文件适用于整个仓库,供本地编码 Agent、自动化 Agent 和 Pull Request Review Agent 使用。 +- 开始工作前先阅读与任务直接相关的 `README.md`、`CONTRIBUTING.md`、模块 `pom.xml`、现有实现和测试;不要仅凭通用经验推断项目约定。 +- 若子目录存在更具体的 `AGENTS.md`,处理该目录文件时优先遵循距离目标文件最近的说明。 +- 用户的明确要求优先于本文件;若要求与兼容性、安全性或仓库约定冲突,应先说明风险,不要静默偏离。 +- 只修改完成任务所必需的文件,不处理无关格式、重构或历史遗留问题。 + +## 项目概览 + +- WxJava 是面向微信生态的 Java SDK,采用 Maven 多模块结构。 +- 当前根项目要求 Java 8(`maven.compiler.source` 和 `maven.compiler.target` 均为 `1.8`)。除非任务明确要求升级,否则新增代码、依赖和 API 必须保持 Java 8 兼容。 +- 主要 SDK 模块包括: + - `weixin-java-common`:各模块共用的基础类型、工具、异常、HTTP 与配置能力。 + - `weixin-java-mp`:微信公众号。 + - `weixin-java-miniapp`:微信小程序。 + - `weixin-java-pay`:微信支付。 + - `weixin-java-cp`:企业微信。 + - `weixin-java-open`:微信开放平台。 + - `weixin-java-channel`:微信视频号、微信小店。 + - `weixin-java-qidian`:微信客服相关能力。 + - `weixin-java-aispeech`:微信智能语音。 + - `weixin-graal`:GraalVM 相关支持。 +- 集成模块主要位于: + - `spring-boot-starters`:Spring Boot Starter 及多账号 Starter。 + - `solon-plugins`:Solon 插件及多账号插件。 + - `wx-java-bom`:统一管理对外模块版本的 BOM。 +- `README.md` 说明整体能力和使用方式,`CONTRIBUTING.md` 规定代码贡献要求,`docs` 保存补充文档。 + +## 开发流程 + +1. 确认任务涉及的微信产品和 Maven 模块,先查找同模块中的相似接口、Bean、实现类和测试。 +2. 阅读目标模块及其父级 `pom.xml`,确认依赖、测试框架和已有实现方式。 +3. 优先沿用现有的包结构、命名、序列化、HTTP 执行器、异常处理及配置模式。 +4. 以最小变更完成任务;不要在功能修改中夹带依赖升级、全局格式化或无关重构。 +5. 为修复或新增行为添加有针对性的测试,并先运行受影响模块的验证。 +6. 交付前检查 diff、测试结果、兼容性和文档影响,明确报告未执行或无法执行的验证。 + +## 代码风格与兼容性 + +- 遵循 `.editorconfig`:使用空格缩进、缩进宽度为 2、UTF-8、LF、文件末尾保留换行,并清除非 Markdown 文件的行尾空格。 +- 保持目标文件现有代码风格;避免仅为个人偏好调整 import、换行、注释或成员顺序。 +- 不使用 Java 9 及以上语言特性或仅在新版本 JDK 中存在的 API。 +- 项目使用 Lombok;新增或修改 Lombok 用法时遵循相邻代码模式,不要在同一变更中无理由改写为手工样板代码。 +- 公共 API、序列化字段、枚举值、常量、默认实现和依赖范围都可能影响下游用户,修改时优先保持源码、二进制和行为兼容。 +- 不要随意改变已有异常类型、空值语义、默认 HTTP 客户端、JSON/XML 映射或配置加载行为。 +- 新增依赖前先确认现有依赖能否满足需求;依赖版本应在合适的父 POM 或 BOM 中统一管理,避免模块间版本漂移。 + +## API 与实现约定 + +- 对接微信接口时,以对应产品的官方接口定义和仓库内现有同类实现为依据,核对请求路径、HTTP 方法、字段名、必填项和返回结构。 +- Java 字段名可以符合项目命名习惯,但传输层字段名必须与微信接口保持一致;需要时使用项目现有的 Gson、Jackson 或 XStream 映射方式。 +- 新增 Service API 时同步检查接口、实现类、请求/响应 Bean、URL 常量、序列化适配器和测试是否都需要更新。 +- 涉及多个 HTTP 客户端实现时,保持 Apache HttpClient、HttpComponents、OkHttp 和 Jodd 等现有实现的能力一致;不要只修复其中一种而遗漏其他可选实现。 +- 涉及 Starter 或插件时,检查单账号与多账号版本以及自动配置、配置属性和示例测试是否需要同步。 +- 公共方法需要清晰的 Javadoc,至少准确描述参数、返回值、异常和必要约束;不要编写与实现不一致的模板化注释。 +- 不吞掉异常,不使用空 `catch`,不在日志中泄露 token、secret、密钥、签名原文或用户敏感数据。 + +## 测试与验证 + +- 项目测试主要使用 TestNG;沿用目标模块已有测试基类、数据提供器、Mock 方式和资源布局。 +- 修复 bug 时应添加能够复现旧行为并验证修复结果的回归测试;新增公共方法必须配套单元测试。 +- 优先执行受影响模块及其依赖的测试: + +```shell +mvn -pl -am test +``` + +- 运行单个测试类时,可在对应模块范围内执行: + +```shell +mvn -pl -am -Dtest= -Dsurefire.failIfNoSpecifiedTests=false test +``` + +- 跨公共模块、父 POM、BOM 或多个产品模块的变更应扩大验证范围;条件允许时执行: + +```shell +mvn test +``` + +- 对仅需确认编译且测试依赖外部凭据的场景,可执行模块级编译或打包,但不得把跳过测试的构建描述为“测试通过”。 +- 部分测试可能依赖微信凭据、网络或本地配置。不得提交真实凭据;无法运行时应说明原因以及已完成的替代验证。 +- 完成前至少运行 `git diff --check`,并人工检查 `git diff`,确认没有意外文件、调试代码、生成物或敏感信息。 + +## 文档与依赖变更 + +- 用户可见的 API、配置项、模块使用方式或兼容性发生变化时,同步更新相关 Javadoc、README 或 `docs` 文档。 +- 示例中的版本号、模块名和配置键应与当前项目保持一致;不要复制未经验证的外部示例。 +- 修改根 POM、父 POM 或 `wx-java-bom` 时,检查所有子模块和对外依赖管理的影响。 +- 不提交构建输出、IDE 临时文件、测试报告、真实配置文件或本地备份文件。 + +## Git 与 Pull Request 规范 + +- 贡献目标分支为 `develop`;`release` 用于正式版本发布,不应作为常规 Pull Request 的目标分支。 +- 提交前保持工作区变更聚焦,不覆盖或回退用户已有的无关修改。 +- Commit 和 Pull Request 应说明变更动机、影响模块、兼容性风险和验证方式;关联已有 Issue 时写明编号。 +- 不得在未经用户明确授权的情况下执行 `git push`、创建或合并 Pull Request、改写历史或执行破坏性 Git 操作。 +- 不要使用 `git reset --hard`、强制 checkout 等方式清理不属于当前任务的改动。 + +## 安全与敏感数据 + +- 禁止提交 AppID 对应的 secret、access token、API v3 密钥、商户私钥、证书私钥、用户数据或其他真实凭据。 +- 测试和文档使用明显的占位值或脱敏数据;日志应避免输出查询串、请求体或响应体中的敏感字段。 +- 涉及签名、验签、加解密、证书、回调通知和支付金额时,重点检查字符编码、字段排序、精度、时区、重放风险和资源关闭。 +- 涉及网络请求、文件或流时,检查超时、异常路径、资源释放、响应关闭和大数据量下的内存行为。 + +## Review 指南 + +- 重点检查空指针、并发、资源释放、兼容性问题。 +- 不要只做代码风格建议,优先指出真实 bug 和回归风险。 +- 所有 Pull Request Review 的总结、结论和行内评论必须使用简体中文。 +- 技术标识符、类名、方法名、变量名、日志、错误信息及代码片段保持原文,不要翻译。 +- 严重程度标识可以保留 `P0`、`P1`、`P2` 等英文缩写。 +- 如果没有发现需要阻止合并的问题,也必须使用简体中文给出结论。 +- Review 结论必须基于当前 diff 和仓库中可验证的行为;不确定时明确说明假设,不要把推测写成确定缺陷。 +- 仅报告由本次变更引入或暴露、且作者可以采取行动的问题,并指出具体文件、位置、触发条件和影响。 +- 重点关注微信接口契约、Java 8 兼容性、公共 API 兼容性、序列化字段、HTTP 客户端实现一致性、Starter 多账号场景及敏感信息泄露。 +- 不要仅因缺少全仓库测试、个人风格偏好或与本次变更无关的历史代码而阻止合并。 + +## Agent 完成检查清单 + +- 已确认并遵循相关模块、相邻代码和更具体的 `AGENTS.md`。 +- 变更范围与任务直接相关,没有覆盖用户的无关修改。 +- 新增代码保持 Java 8、公共 API 和序列化兼容性。 +- 必要的测试与文档已经更新。 +- 已执行与风险匹配的构建或测试,并如实记录结果。 +- 已检查完整 diff、格式、生成物和敏感信息。 +- 最终回复使用简体中文,简要列出修改内容、验证结果和任何剩余风险。 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c703964824..0b16b4779e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ $ git push * 定期使用项目仓库内容更新自己仓库内容。 ```bash -$ git remote add upstream https://github.com/Wechat-Group/WxJava +$ git remote add upstream https://github.com/binarywang/WxJava $ git fetch upstream $ git checkout develop $ git rebase upstream/develop diff --git a/README.md b/README.md index 0144be7dcb..849abb6b18 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,250 @@ ## WxJava - 微信开发 Java SDK +[![Github](https://img.shields.io/github/stars/binarywang/WxJava?logo=github&style=flat&label=Stars)](https://github.com/binarywang/WxJava) +[![Gitee](https://gitee.com/binary/weixin-java-tools/badge/star.svg?theme=blue)](https://gitee.com/binary/weixin-java-tools) +[![GitCode](https://gitcode.com/binary/WxJava/star/badge.svg)](https://gitcode.com/binary/WxJava) -[![码云Gitee](https://gitee.com/binary/weixin-java-tools/badge/star.svg?theme=blue)](https://gitee.com/binary/weixin-java-tools) -[![Github](https://img.shields.io/github/stars/Wechat-Group/WxJava?logo=github&style=flat)](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://img.shields.io/circleci/project/github/Wechat-Group/WxJava/develop.svg?sanitize=true)](https://circleci.com/gh/Wechat-Group/WxJava/tree/develop) +[![GitHub release](https://img.shields.io/github/release/binarywang/WxJava?label=Release)](https://github.com/binarywang/WxJava/releases) +[![Maven Central](https://img.shields.io/maven-central/v/com.github.binarywang/wx-java?label=Maven)](https://central.sonatype.com/artifact/com.github.binarywang/wx-java/versions) +[![Build Status](https://img.shields.io/circleci/project/github/binarywang/WxJava/develop.svg?sanitize=true&label=Build)](https://circleci.com/gh/binarywang/WxJava/tree/develop) [![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-支持-blue.svg)](https://www.jetbrains.com/?from=WxJava-weixin-java-tools) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/binarywang/WxJava) -#### 微信`Java`开发工具包,支持包括微信支付、开放平台、公众号、企业微信/企业号、小程序等微信功能模块的后端开发。 - -
- 特别赞助 -
- - - - - - - - - - - - -
- - ccflow - -
- - 计全支付Jeepay,开源支付系统 - -
- - mp qrcode - - - - diboot低代码开发平台 - - - - aliyun ad - -
+[Featured|HelloGitHub](https://hellogithub.com/repository/6de6147050c94db4aedfd7098d19f8d8) +[binarywang/WxJava | 趋势转变](https://trendshift.io/repositories/12152) +[Star History](https://www.star-history.com/binarywang/wxjava) + +### 微信 `Java` 开发工具包,支持包括微信支付、开放平台、公众号、企业微信、视频号、小程序等微信功能模块的后端开发。 + +### 特别赞助 +- 计全支付Jeepay,开源支付系统 +- CRMEB + +> 赞助商招募中,欢迎联系合作。 + +### 关注公众号 +想获得技术交流群(QQ群/微信群/钉钉群)等信息,请扫码关注 `WxJava` 公众号: + +[![WxJava 公众号二维码](https://gitee.com/binary/weixin-java-tools/raw/develop/images/qrcodes/mp.png)](https://mp.weixin.qq.com/mp/homepage?__biz=MzI3MzAwMzk4OA==&hid=1&sn=f31af3bf562b116b061c9ab4edf70b61&scene=18#wechat_redirect) + +### 目录索引 +- [快速开始(3分钟)](#快速开始3分钟) +- [AI 编程智能体 SKILL 安装](#ai-编程智能体-skill-安装) +- [我该选哪个模块?](#我该选哪个模块) +- [Maven 引用方式](#maven-引用方式) +- [最小示例](#最小示例) +- [重要信息](#重要信息) +- [其他说明](#其他说明) +- [版本说明](#版本说明) +- [应用案例](#应用案例) +- [特别赞助](#特别赞助) +- [关注公众号](#关注公众号) +- [贡献者列表](#贡献者列表) + +### 快速开始(3分钟) +1. 根据业务场景选择模块(见下方“我该选哪个模块?”) +2. 引入 Maven 依赖并选择对应模块 +3. 参考最小示例完成初始化并调用 API + +### AI 编程智能体 SKILL 安装 + +仓库的 [`skills`](skills) 目录提供面向 WxJava 用户和贡献者的通用 SKILL,包括模块选择、接入、排障、接口贡献和升级迁移。每个 SKILL 都以 `SKILL.md` 为入口,可用于支持该约定的 AI 编程智能体。 + +支持远程安装 SKILL 的智能体,可以直接使用自然语言指令安装所需目录。例如: + +> 安装 https://github.com/binarywang/WxJava/tree/develop/skills/wxjava-integration-guide 中的技能。 + +可安装的 SKILL 包括: + +- [模块选择](https://github.com/binarywang/WxJava/tree/develop/skills/wxjava-module-selector) +- [接入指南](https://github.com/binarywang/WxJava/tree/develop/skills/wxjava-integration-guide) +- [故障排查](https://github.com/binarywang/WxJava/tree/develop/skills/wxjava-troubleshooter) +- [接口贡献](https://github.com/binarywang/WxJava/tree/develop/skills/wxjava-api-contributor) +- [升级迁移](https://github.com/binarywang/WxJava/tree/develop/skills/wxjava-upgrade-guide) + +不支持远程安装时,可将所需的 `skills/wxjava-*` 目录复制到智能体的 SKILL 目录或工作区配置目录;不同智能体的目录和启用方式请以其官方文档为准。 + +以 Codex 为例,可复制到个人 SKILL 目录: + +```shell +git clone https://github.com/binarywang/WxJava.git +mkdir -p "${CODEX_HOME:-$HOME/.codex}/skills" +cp -R WxJava/skills/wxjava-* "${CODEX_HOME:-$HOME/.codex}/skills/" +``` + +如果已在本仓库根目录,可直接执行: + +```shell +mkdir -p "${CODEX_HOME:-$HOME/.codex}/skills" +cp -R skills/wxjava-* "${CODEX_HOME:-$HOME/.codex}/skills/" +``` + +重启或新建智能体会话后,即可按需使用。例如:`使用 wxjava-integration-guide 为我的 Spring Boot 项目接入微信支付。` + +### 我该选哪个模块? + +| 业务场景 | 模块 | artifactId | +|---|---|---| +| 微信公众号开发 | MP | `weixin-java-mp` | +| 微信小程序开发 | MiniApp | `weixin-java-miniapp` | +| 微信支付 | Pay | `weixin-java-pay` | +| 企业微信 | CP | `weixin-java-cp` | +| 微信开放平台(第三方平台) | Open | `weixin-java-open` | +| 视频号 / 微信小店 | Channel | `weixin-java-channel` | + +> 移动端(iOS/Android)微信登录、分享等能力仍需集成微信官方客户端 SDK;本项目为服务端 SDK。 ### 重要信息 -1. 项目合作洽谈请联系微信`binary0000`(在微信里自行搜索并添加好友,请注明来意,如有关于SDK问题需讨论请参考下文入群讨论,不要加此微信)。 -2. **2023-4-23 发布 [【4.5.0正式版】](https://mp.weixin.qq.com/s/4ZYKJnIwP9YNDvbyOhW_3A)**! -3. 贡献源码可以参考视频:[【贡献源码全过程(上集)】](https://mp.weixin.qq.com/s/3xUZSATWwHR_gZZm207h7Q)、[【贡献源码全过程(下集)】](https://mp.weixin.qq.com/s/nyzJwVVoYSJ4hSbwyvTx9A) ,友情提供:[程序员小山与Bug](https://space.bilibili.com/473631007) -4. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用 `maven` 或 `gradle` 引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码; -5. 微信开发新手请务必阅读【开发文档】([Gitee Wiki](https://gitee.com/binary/weixin-java-tools/wikis/Home) 或者 [Github Wiki](https://github.com/Wechat-Group/WxJava/wiki))的常见问题部分,可以少走很多弯路,节省不少时间。 -6. 技术交流群:想获得QQ群/微信群/钉钉企业群等信息的同学,请使用微信扫描上面的微信公众号二维码关注 `WxJava` 后点击相关菜单即可获取加入方式,同时也可以在微信中搜索 `weixin-java-tools` 或 `WxJava` 后选择正确的公众号进行关注,该公众号会及时通知SDK相关更新信息,并不定期分享微信Java开发相关技术知识; -7. 钉钉技术交流群:`32206329`(技术交流2群), `30294972`(技术交流1群,目前已满),`35724728`(通知群,实时通知Github项目变更记录)。 -8. 微信开发新手或者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) ,避免浪费大家的宝贵时间; -9. 寻求帮助时需贴代码或大长串异常信息的,请利用 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);** -2. 如有新功能需求,发现BUG,或者由于微信官方接口调整导致的代码问题,可以直接在[【Issues】](https://github.com/Wechat-Group/WxJava/issues)页提出issue,便于讨论追踪问题; +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/wechat-group/WxJava/wiki),部分文档可能未能及时更新,如有发现,可以及时上报或者自行修改。 +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),以下为最新正式版。 + +#### 方式一:使用 BOM 统一管理版本(推荐) + +如果同时使用多个 WxJava 模块,推荐通过 BOM 统一管理版本,无需为每个模块单独指定版本号。 +`wx-java-bom` 从 **4.8.3.B** 版本开始提供,请使用该版本或更高版本: + +```xml + + 4.8.3.B + + + + + + com.github.binarywang + wx-java-bom + ${wx-java.version} + pom + import + + + +``` + +之后直接引入所需模块,无需指定版本: + +```xml + + com.github.binarywang + weixin-java-mp + + + com.github.binarywang + weixin-java-pay + +``` + +#### 方式二:直接引用单个模块 ```xml com.github.binarywang (不同模块参考下文) - 4.5.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`)主要用于第三方平台,代公众号或小程序进行开发和管理 + +--------------------------------- +### 最小示例 + +
+公众号(MP)示例:获取 AccessToken + +```java +WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); +config.setAppId("your-app-id"); +config.setSecret("your-secret"); + +WxMpService wxMpService = new WxMpServiceImpl(); +wxMpService.setWxMpConfigStorage(config); + +String accessToken = wxMpService.getAccessToken(); +System.out.println(accessToken); +``` + +
+ +
+小程序(MiniApp)示例:code2Session + +```java +WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); +config.setAppid("your-app-id"); +config.setSecret("your-secret"); + +WxMaService wxMaService = new WxMaServiceImpl(); +wxMaService.setWxMaConfig(config); + +WxMaJscode2SessionResult result = wxMaService.getUserService().getSessionInfo("js-code"); +System.out.println(result.getOpenid()); +``` + +
+ --------------------------------- ### 版本说明
点此展开查看 -1. 本项目定为大约每两个月发布一次正式版(同时 `develop` 分支代码合并进入 `master` 分支),版本号格式为 `X.X.0`(如`2.1.0`,`2.2.0`等),遇到重大问题需修复会及时提交新版本,欢迎大家随时提交Pull Request; -2. BUG修复和新特性一般会先发布成小版本作为临时测试版本(如`3.6.8.B`,即尾号不为0,并添加B,以区别于正式版),代码仅存在于 `develop` 分支中; -3. 目前最新版本号为 [![Maven Central](https://img.shields.io/maven-central/v/com.github.binarywang/wx-java.svg)](http://mvnrepository.com/artifact/com.github.binarywang/wx-java) ,也可以通过访问链接 [【微信支付】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-pay%22) 、[【微信小程序】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-miniapp%22) 、[【公众号】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-mp%22) 、[【企业微信】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-cp%22)、[【开放平台】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-open%22) -分别查看所有最新的版本。 +1. 本项目定为大约每半年左右发布一次正式版,遇到重大问题需修复会及时提交新版本,欢迎大家随时提交 `Pull Request`; +2. 每次代码更新都会自动构建出新版本方便及时尝鲜,版本号格式为 `x.x.x-时间戳`; +3. 发布正式版时,`develop` 分支代码合并进入 `release` 分支),版本号格式为 `X.X.0`(如`2.1.0`,`2.2.0`等); +4. 每隔一段时间后,会发布测试版本(如`3.6.8.B`,即尾号不为0,并添加B,以区别于正式版),代码仅存在于 `develop` 分支中; +5. 目前最新版本号为 [![Maven Central](https://img.shields.io/maven-central/v/com.github.binarywang/wx-java.svg)](http://mvnrepository.com/artifact/com.github.binarywang/wx-java) ,也可以通过访问以下链接分别查看各个模块最新的版本: +[【微信支付】](https://central.sonatype.com/artifact/com.github.binarywang/weixin-java-pay/versions) 、[【小程序】](https://central.sonatype.com/artifact/com.github.binarywang/weixin-java-miniapp/versions) 、[【公众号】](https://central.sonatype.com/artifact/com.github.binarywang/weixin-java-mp/versions) 、[【企业微信】](https://central.sonatype.com/artifact/com.github.binarywang/weixin-java-cp/versions)、[【开放平台】](https://central.sonatype.com/artifact/com.github.binarywang/weixin-java-open/versions)、[【视频号】](https://central.sonatype.com/artifact/com.github.binarywang/weixin-java-channel/versions) +
---------------------------------- ### 应用案例 -完整案例登记列表,请[【访问这里】](https://github.com/Wechat-Group/weixin-java-tools/issues/729)查看,欢迎登记更多的案例。 +完整案例登记列表,请[【访问这里】](https://github.com/binarywang/WxJava/issues/729)查看,欢迎登记更多的案例。 -以下为节选的部分案例: +
+以下为节选的部分案例, 点此展开查看 #### 开源项目: - 基于微信公众号的签到、抽奖、发送弹幕程序:https://github.com/workcheng/weiya @@ -171,26 +307,22 @@ - 微信公众号管理系统:http://demo.joolun.com - 锐捷网络:Saleslink +
+ ---------------------------------- ### 贡献者列表 -特别感谢参与贡献的所有同学,所有贡献者列表请在[此处](https://github.com/Wechat-Group/WxJava/graphs/contributors)查看,欢迎大家继续踊跃贡献代码! -
-点击此处展开查看贡献次数最多的几位小伙伴 - -1. [chanjarster (Daniel Qian)](https://github.com/chanjarster) -1. [binarywang (Binary Wang)](https://github.com/binarywang) -1. [007gzs](https://github.com/007gzs) -1. [Silloy](https://github.com/silloy) -1. [mgcnrx11](https://github.com/mgcnrx11) -1. [0katekate0 (Wang_Wong)](https://github.com/0katekate0) -1. [yuanqixun](https://github.com/yuanqixun) -1. [kakotor](https://github.com/kakotor) -1. [aimilin6688 (Jonk)](https://github.com/aimilin6688) -1. [lkqm (Mario Luo)](https://github.com/lkqm) -1. [kareanyi (MillerLin)](https://github.com/kareanyi) +特别感谢参与贡献的所有同学,所有贡献者列表请在[此处](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 + + diff --git a/demo.md b/demo.md index d6b55b89e2..d305fc2121 100644 --- a/demo.md +++ b/demo.md @@ -14,12 +14,11 @@ - [使用该 `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://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:[GitHub](http://github.com/binarywang/weixin-java-pay-demo)、[码云](http://gitee.com/binary/weixin-java-pay-demo) +1. 企业号/企业微信 Demo:[GitHub](http://github.com/binarywang/weixin-java-cp-demo)、[码云](http://gitee.com/binary/weixin-java-cp-demo) +1. 微信小程序 Demo:[GitHub](http://github.com/binarywang/weixin-java-miniapp-demo)、[码云](http://gitee.com/binary/weixin-java-miniapp-demo) +1. 开放平台 Demo:[GitHub](http://github.com/Wechat-Group/weixin-java-open-demo)、[码云](http://gitee.com/binary/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) - + - 使用 `Spring MVC` 实现的公众号 Demo:[GitHub](http://github.com/binarywang/weixin-java-mp-demo-springmvc)、[码云](https://gitee.com/binary/weixin-java-mp-demo) + - 使用 `Spring Boot` 实现的公众号 Demo(支持多公众号):[GitHub](http://github.com/binarywang/weixin-java-mp-demo)、[码云](http://gitee.com/binary/weixin-java-mp-demo-springboot) + - 含公众号和部分微信支付代码的 Demo:[GitHub](http://github.com/Wechat-Group/weixin-java-demo-springmvc)、[码云](http://gitee.com/binary/weixin-java-tools-springmvc) diff --git a/docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md b/docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md new file mode 100644 index 0000000000..83d9c08158 --- /dev/null +++ b/docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md @@ -0,0 +1,300 @@ +# 企业微信会话存档SDK安全使用指南 + +## 说明 +该方案已废弃,请使用新版本:[CP_MSG_AUDIT_THREADLOCAL_LIFECYCLE_REFACTOR.md](CP_MSG_AUDIT_THREADLOCAL_LIFECYCLE_REFACTOR.md) +## 问题背景 + +在使用企业微信会话存档功能时,部分开发者遇到了JVM崩溃的问题。典型错误信息如下: + +``` +SIGSEGV (0xb) at pc=0x00007fcd50460d93 +Problematic frame: +C [libWeWorkFinanceSdk_Java.so+0x260d93] WeWorkFinanceSdk::TryRefresh(std::string const&, std::string const&, int)+0x23 +``` + +## 问题原因 + +旧版API设计存在以下问题: + +1. **SDK生命周期管理混乱** + - `getChatDatas()` 方法会返回SDK实例给调用方 + - 开发者需要手动调用 `Finance.DestroySdk()` 来销毁SDK + - 但SDK在框架内部有7200秒的缓存机制 + +2. **手动销毁导致缓存失效** + - 当开发者手动销毁SDK后,框架缓存中的SDK引用变为无效 + - 后续调用(如 `getMediaFile()`)仍然使用已销毁的SDK + - 底层C++库访问无效指针,导致SIGSEGV错误 + +3. **多线程并发问题** + - 在多线程环境下,一个线程销毁SDK后 + - 其他线程仍在使用该SDK,导致崩溃 + +## 解决方案 + +从 **4.8.0** 版本开始,WxJava提供了新的安全API,完全由框架管理SDK生命周期。 + +### 新API列表 + +| 旧API(已废弃) | 新API(推荐使用) | 说明 | +|----------------|------------------|------| +| `getChatDatas()` | `getChatRecords()` | 拉取聊天记录,不暴露SDK | +| `getDecryptData(sdk, ...)` | `getDecryptChatData(...)` | 解密聊天数据,无需传入SDK | +| `getChatPlainText(sdk, ...)` | `getChatRecordPlainText(...)` | 获取明文数据,无需传入SDK | +| `getMediaFile(sdk, ...)` | `downloadMediaFile(...)` | 下载媒体文件,无需传入SDK | + +### 使用示例 + +#### 错误用法(旧API,已废弃) + +```java +// ❌ 不推荐:容易导致JVM崩溃 +WxCpMsgAuditService msgAuditService = wxCpService.getMsgAuditService(); + +// 拉取聊天记录 +WxCpChatDatas chatDatas = msgAuditService.getChatDatas(seq, 1000L, null, null, 1000L); + +for (WxCpChatDatas.WxCpChatData chatData : chatDatas.getChatData()) { + // 解密数据 + WxCpChatModel model = msgAuditService.getDecryptData(chatDatas.getSdk(), chatData, 2); + + // 下载媒体文件 + if ("image".equals(model.getMsgType())) { + String sdkFileId = model.getImage().getSdkFileId(); + msgAuditService.getMediaFile(chatDatas.getSdk(), sdkFileId, null, null, 1000L, targetPath); + } +} + +// ❌ 危险操作:手动销毁SDK可能导致后续调用崩溃 +Finance.DestroySdk(chatDatas.getSdk()); +``` + +#### 正确用法(新API,推荐) + +```java +// ✅ 推荐:SDK生命周期由框架自动管理,安全可靠 +WxCpMsgAuditService msgAuditService = wxCpService.getMsgAuditService(); + +// 拉取聊天记录(不返回SDK) +List chatRecords = + msgAuditService.getChatRecords(seq, 1000L, null, null, 1000L); + +for (WxCpChatDatas.WxCpChatData chatData : chatRecords) { + // 解密数据(无需传入SDK) + WxCpChatModel model = msgAuditService.getDecryptChatData(chatData, 2); + + // 下载媒体文件(无需传入SDK) + if ("image".equals(model.getMsgType())) { + String sdkFileId = model.getImage().getSdkFileId(); + msgAuditService.downloadMediaFile(sdkFileId, null, null, 1000L, targetPath); + } +} + +// ✅ 无需手动销毁SDK,框架会自动管理 +``` + +### 完整示例:拉取并处理会话存档 + +```java +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.api.WxCpMsgAuditService; +import me.chanjar.weixin.cp.bean.msgaudit.WxCpChatDatas; +import me.chanjar.weixin.cp.bean.msgaudit.WxCpChatModel; +import me.chanjar.weixin.cp.constant.WxCpConsts; + +import java.util.List; + +public class MsgAuditExample { + + private final WxCpService wxCpService; + + public void processMessages(long seq) throws Exception { + WxCpMsgAuditService msgAuditService = wxCpService.getMsgAuditService(); + + // 拉取聊天记录 + List chatRecords = + msgAuditService.getChatRecords(seq, 1000L, null, null, 1000L); + + for (WxCpChatDatas.WxCpChatData chatData : chatRecords) { + seq = chatData.getSeq(); + + // 获取明文数据 + String plainText = msgAuditService.getChatRecordPlainText(chatData, 2); + WxCpChatModel model = WxCpChatModel.fromJson(plainText); + + // 处理不同类型的消息 + switch (model.getMsgType()) { + case WxCpConsts.MsgAuditMediaType.TEXT: + processTextMessage(model); + break; + + case WxCpConsts.MsgAuditMediaType.IMAGE: + processImageMessage(model, msgAuditService); + break; + + case WxCpConsts.MsgAuditMediaType.FILE: + processFileMessage(model, msgAuditService); + break; + + default: + // 处理其他类型消息 + break; + } + } + } + + private void processTextMessage(WxCpChatModel model) { + String content = model.getText().getContent(); + System.out.println("文本消息:" + content); + } + + private void processImageMessage(WxCpChatModel model, WxCpMsgAuditService msgAuditService) + throws Exception { + String sdkFileId = model.getImage().getSdkFileId(); + String md5Sum = model.getImage().getMd5Sum(); + String targetPath = "/path/to/save/" + md5Sum + ".jpg"; + + // 下载图片(无需传入SDK) + msgAuditService.downloadMediaFile(sdkFileId, null, null, 1000L, targetPath); + System.out.println("图片已保存:" + targetPath); + } + + private void processFileMessage(WxCpChatModel model, WxCpMsgAuditService msgAuditService) + throws Exception { + String sdkFileId = model.getFile().getSdkFileId(); + String fileName = model.getFile().getFileName(); + String targetPath = "/path/to/save/" + fileName; + + // 下载文件(无需传入SDK) + msgAuditService.downloadMediaFile(sdkFileId, null, null, 1000L, targetPath); + System.out.println("文件已保存:" + targetPath); + } +} +``` + +### 使用Lambda处理媒体文件流 + +新API同样支持使用Lambda表达式处理媒体文件的数据流: + +```java +msgAuditService.downloadMediaFile(sdkFileId, null, null, 1000L, data -> { + try { + // 处理每个数据分片(大文件会分片传输) + // 例如:上传到云存储、写入数据库等 + uploadToCloud(data); + } catch (Exception e) { + e.printStackTrace(); + } +}); +``` + +## 技术实现原理 + +### 引用计数机制 + +新API在内部实现了SDK引用计数机制: + +1. **获取SDK时**:引用计数 +1 +2. **使用完成后**:引用计数 -1 +3. **计数归零且SDK已过期时**:SDK被销毁并清理缓存 +4. **计数归零但SDK未过期时**:保留缓存,供后续调用直接复用 + +> **注意**:引用计数归零并不等同于立即销毁SDK。只有在 SDK 已超过有效期的情况下,框架才会调用 `Finance.DestroySdk()` 释放资源。这一机制避免了每次 API 调用后的频繁初始化/销毁循环。 + +```java +// 框架内部实现(简化版) +public void downloadMediaFile(String sdkFileId, ...) { + long sdk = initSdk(); // 获取或初始化SDK(有效期内直接复用缓存) + configStorage.incrementMsgAuditSdkRefCount(sdk); // 引用计数 +1 + + try { + // 执行实际操作 + getMediaFile(sdk, sdkFileId, ...); + } finally { + // 确保引用计数一定会减少;仅在归零且过期时销毁 + configStorage.decrementMsgAuditSdkRefCount(sdk); // 引用计数 -1 + } +} +``` + +### SDK缓存机制 + +SDK初始化后会缓存7200秒,避免频繁初始化: + +- **首次调用**:初始化新的SDK +- **7200秒内**:复用缓存的SDK(即使引用计数曾归零也不重新初始化) +- **超过7200秒**:下次 `acquireMsgAuditSdk()` 返回0,触发重新初始化,旧SDK在重新初始化时被销毁 + +新API的引用计数机制与缓存机制完美配合,确保SDK不会被提前销毁,也不会永久残留。 + +## 迁移指南 + +### 第一步:使用新API替换旧API + +查找代码中的旧API调用: + +```java +// 查找模式 +getChatDatas( +getDecryptData(.*sdk +getChatPlainText(.*sdk +getMediaFile(.*sdk +Finance.DestroySdk( +``` + +替换为对应的新API(参考前面的对照表)。 + +### 第二步:移除手动SDK管理代码 + +删除所有 `Finance.DestroySdk()` 调用,SDK生命周期由框架自动管理。 + +### 第三步:测试验证 + +1. 在测试环境验证新API功能正常 +2. 观察日志,确认没有SDK相关的错误 +3. 进行压力测试,验证多线程环境下的稳定性 + +## 常见问题 + +### Q1: 旧代码会立即停止工作吗? + +**A:** 不会。旧API被标记为 `@Deprecated`,但仍然可用,只是不推荐继续使用。建议尽快迁移到新API以避免潜在问题。 + +### Q2: 如何知道SDK是否被正确释放? + +**A:** 框架会自动管理SDK生命周期,开发者无需关心。如果需要调试,可以查看配置存储中的引用计数。 + +### Q3: 多线程环境下新API安全吗? + +**A:** 是的。新API使用了引用计数机制,配合 `synchronized` 关键字,确保多线程环境下的安全性。 + +### Q4: 性能会受影响吗? + +**A:** 不会。新API在实现上增加了引用计数的开销,但这是轻量级的操作(原子操作),对性能影响可以忽略不计。SDK缓存机制保持不变。 + +### Q5: 可以同时使用新旧API吗? + +**A:** 技术上可以,但强烈不推荐。混用可能导致SDK生命周期管理混乱,建议统一使用新API。 + +## 相关链接 + +- [企业微信会话存档官方文档](https://developer.work.weixin.qq.com/document/path/91360) +- [WxJava GitHub 仓库](https://github.com/binarywang/WxJava) +- [问题反馈](https://github.com/binarywang/WxJava/issues) + +## 版本要求 + +- **最低版本**: 4.8.0 +- **推荐版本**: 最新版本 + +## 反馈与支持 + +如果在使用过程中遇到问题,请: + +1. 查看本文档的常见问题部分 +2. 在 GitHub 上提交 Issue +3. 加入微信群获取社区支持 + +--- + +**最后更新时间**: 2026-01-14 diff --git a/docs/CP_MSG_AUDIT_THREADLOCAL_LIFECYCLE_REFACTOR.md b/docs/CP_MSG_AUDIT_THREADLOCAL_LIFECYCLE_REFACTOR.md new file mode 100644 index 0000000000..1f073905f4 --- /dev/null +++ b/docs/CP_MSG_AUDIT_THREADLOCAL_LIFECYCLE_REFACTOR.md @@ -0,0 +1,204 @@ +# 会话存档SDK生命周期重构方案 + +## Context + +当前实现(4.8.x)通过"共享SDK + 引用计数 + 7200秒过期"来管理会话存档SDK生命周期。 +该方案存在以下核心问题: + +1. ~~**频繁初始化/销毁**:每次调用 `releaseSdk()` 后引用计数归零即销毁SDK。对于"拉取→解密→下载媒体"这类典型串行调用链,每步操作都会触发重新初始化。~~ ✅ 已在 4.8.3.B+ 修复:引用计数归零时,仅在 SDK 已过期的情况下才销毁,有效期内继续复用缓存。 +2. **7200秒过期规则无依据**:官方文档FAQ明确说"不需要每次new/init sdk,可以在多次拉取中复用同一个sdk",无任何7200秒过期说明。 +3. **线程安全问题**:企微技术人员建议"一个线程一个SDK实例",当前设计多线程共享同一SDK实例,存在并发安全隐患。 + +--- + +## 推荐方案:ThreadLocal SDK 模式 + +> **核心原则**:每个线程拥有独立SDK实例,懒初始化,生命周期与线程绑定。 + +### 设计要点 + +- 使用 `ThreadLocal` 为每个线程持有独立SDK +- SDK在线程首次调用时初始化,后续所有操作复用(无需重复初始化) +- 移除7200秒过期机制 +- 移除引用计数机制(每线程独占,无需计数) +- 提供显式清理接口:`closeThreadLocalSdk()`(线程结束时调)、`closeAllSdks()`(应用关闭时调) + +### 生命周期示意 + +``` +Thread A: init SDK_A → getChatRecords → getDecryptChatData → downloadMediaFile → [任务结束后调closeThreadLocalSdk] +Thread B: init SDK_B → getChatRecords → getDecryptChatData → downloadMediaFile → ... +Thread C: init SDK_C → ... +``` + +--- + +## 涉及文件 + +| 文件 | 变更类型 | +|------|--------| +| `weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java` | 主要重构 | +| `weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java` | 新增接口方法 | +| `weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java` | 废弃旧SDK管理方法 | +| `weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java` | 废弃旧字段/方法 | +| `weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMsgAuditTest.java` | 补充测试 | +| `docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md` | 更新文档 | + +--- + +## 详细变更 + +### 1. WxCpMsgAuditServiceImpl(主要变更) + +**新增字段:** +```java +/** 每个线程持有独立SDK实例 */ +private final ThreadLocal threadLocalSdk = new ThreadLocal<>(); + +/** 跟踪所有已创建SDK,用于统一清理 */ +private final Set managedSdks = ConcurrentHashMap.newKeySet(); +``` + +**废弃字段/方法:** +- 废弃常量 `SDK_EXPIRES_TIME = 7200`(无官方依据) +- 废弃 `initSdk()`(由 `getOrInitThreadLocalSdk()` 替代) +- 废弃 `acquireSdk()` / `releaseSdk()`(由ThreadLocal模式替代) + +**新增核心方法:** + +```java +/** + * 获取当前线程的SDK,不存在则创建。SDK在线程内跨调用复用,无需每次重新初始化。 + */ +private long getOrInitThreadLocalSdk() throws WxErrorException { + Long sdk = threadLocalSdk.get(); + if (sdk != null && sdk > 0) { + return sdk; + } + long newSdk = createSdk(); + threadLocalSdk.set(newSdk); + managedSdks.add(newSdk); + log.info("线程 [{}] 初始化会话存档SDK成功,sdk={}", Thread.currentThread().getName(), newSdk); + return newSdk; +} + +/** + * 创建并初始化一个新SDK(私有,只在当前线程无SDK时调用) + */ +private long createSdk() throws WxErrorException { + WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage(); + // ... 与现有 initSdk() 内的库加载+Finance.NewSdk()+Finance.Init() 逻辑一致 ... + // 注意:Finance.loadingLibraries() 是幂等的(System.load内部防重复),多线程调用安全 +} + +/** + * 关闭当前线程持有的SDK,释放本地资源。 + * 在线程任务结束时调用(如定时任务finally块,或线程池线程销毁时)。 + */ +public void closeThreadLocalSdk() { + Long sdk = threadLocalSdk.get(); + if (sdk != null && sdk > 0) { + Finance.DestroySdk(sdk); + managedSdks.remove(sdk); + threadLocalSdk.remove(); + log.info("线程 [{}] 关闭会话存档SDK,sdk={}", Thread.currentThread().getName(), sdk); + } +} + +/** + * 关闭所有线程持有的SDK。应用关闭时调用(如Spring @PreDestroy / Shutdown Hook)。 + */ +public void closeAllSdks() { + managedSdks.forEach(sdk -> { + Finance.DestroySdk(sdk); + log.info("关闭会话存档SDK,sdk={}", sdk); + }); + managedSdks.clear(); + threadLocalSdk.remove(); +} +``` + +**更新新API方法(getChatRecords / getDecryptChatData / getChatRecordPlainText / downloadMediaFile):** +- 调用 `getOrInitThreadLocalSdk()` 替代 `acquireSdk()` +- 移除 try-finally 中的 `releaseSdk(sdk)` 调用(SDK不再每次释放) +- 方法变得更简洁:直接使用sdk,无需包装计数 + +**保留旧API方法不变(getChatDatas / getDecryptData / getChatPlainText / getMediaFile):** +- 保持 @Deprecated 标注 +- 内部调用改为 `getOrInitThreadLocalSdk()` 以保持一致性(旧方法也受益于ThreadLocal) +- 移除对 `initSdk()` 的依赖 + +### 2. WxCpMsgAuditService(接口新增) + +```java +/** + * 关闭当前线程持有的SDK,释放native资源。 + * Finance.DestroySdk() 不会随线程结束自动执行,无论线程池还是独立线程, + * 均应在任务结束的finally块中调用本方法,防止native内存、连接等资源泄漏。 + */ +void closeThreadLocalSdk(); + +/** + * 关闭所有会话存档SDK实例。 + * 适用于应用关闭时(如Spring Bean销毁阶段)统一释放资源。 + */ +void closeAllSdks(); +``` + +### 3. WxCpConfigStorage(废弃旧SDK管理API) + +对以下方法标记 `@Deprecated`(保留实现,不做破坏性删除): +- `getMsgAuditSdk()` / `updateMsgAuditSdk()` / `expireMsgAuditSdk()` / `isMsgAuditSdkExpired()` +- `acquireMsgAuditSdk()` / `releaseMsgAuditSdk()` +- `incrementMsgAuditSdkRefCount()` / `decrementMsgAuditSdkRefCount()` / `getMsgAuditSdkRefCount()` + +### 4. WxCpDefaultConfigImpl(废弃旧字段) + +- 将 `msgAuditSdk`、`msgAuditSdkExpiresTime`、`msgAuditSdkRefCount` 字段标记 `@Deprecated` +- 对应的 getter/setter/acquire/release 方法标记 `@Deprecated` +- 保留实现,确保向后兼容 + +--- + +## 使用示例(更新文档) + +```java +// ✅ 典型用法(一次任务中串行调用,SDK在同线程内复用,无重复初始化) +WxCpMsgAuditService msgAuditService = wxCpService.getMsgAuditService(); + +try { + List records = msgAuditService.getChatRecords(seq, 100L, null, null, 30L); + for (WxCpChatDatas.WxCpChatData record : records) { + WxCpChatModel model = msgAuditService.getDecryptChatData(record, 2); + if ("image".equals(model.getMsgType())) { + msgAuditService.downloadMediaFile(model.getImage().getSdkFileId(), null, null, 30L, "/tmp/img.jpg"); + } + } +} finally { + // 无论线程池还是独立线程,均建议在 finally 中显式调用。 + // Finance.DestroySdk() 不会随线程结束自动执行,依赖 closeAllSdks() 兜底会造成 + // native 内存/连接资源的延迟泄漏,对定时任务等长期运行场景尤其有害。 + msgAuditService.closeThreadLocalSdk(); +} + +// 应用关闭时(Spring @PreDestroy 或 Shutdown Hook) +// msgAuditService.closeAllSdks(); +``` + +--- + +## 注意事项 + +1. **线程池场景下必须调用 `closeThreadLocalSdk()`**:线程池中线程会被复用,如不主动清理,下次任务仍会使用旧线程的SDK。对于计划任务/批处理,建议在 finally 块中调用。 +2. **独立线程同样建议显式关闭**:`Finance.DestroySdk()` 是 native 调用,不会随线程结束自动执行,JVM GC 也不会触发它。依赖 `closeAllSdks()` 兜底意味着 native 内存、网络连接等资源在整个应用运行期间一直持有,对定时任务等高频场景会持续积累,建议统一在 finally 块中调用 `closeThreadLocalSdk()`。 +3. **多企业(多CorpId)场景**:`threadLocalSdk` 是实例字段(非static),不同 `WxCpMsgAuditServiceImpl` 实例(不同企业)的ThreadLocal独立,互不影响。 +4. **库加载幂等性**:`Finance.loadingLibraries()` 底层调用 `System.load()`,JVM保证同一库不重复加载,多线程并发调用安全。 + +--- + +## 验证方式 + +1. **单元测试**:在 `WxCpMsgAuditTest` 中添加测试,验证同线程多次调用不触发重新初始化(可通过日志或mock Finance验证) +2. **多线程压测**:多线程并发调用 `getChatRecords` + `getDecryptChatData`,观察无JVM崩溃 +3. **线程池复用测试**:使用固定线程池多次提交任务,验证 `closeThreadLocalSdk()` 后下次任务能正确重新初始化SDK +4. **应用关闭测试**:调用 `closeAllSdks()`,验证所有线程的SDK被正确销毁 diff --git a/docs/CommonUploadParam-FormFields-Usage.md b/docs/CommonUploadParam-FormFields-Usage.md new file mode 100644 index 0000000000..2d95d7c5c9 --- /dev/null +++ b/docs/CommonUploadParam-FormFields-Usage.md @@ -0,0 +1,169 @@ +# CommonUploadParam 额外表单字段功能使用示例 + +## 背景 + +微信公众号在上传永久视频素材时,需要在POST请求中同时提交文件和一个名为`description`的表单字段,该字段包含视频的描述信息(JSON格式)。 + +根据微信公众号文档: +> 在上传视频素材时需要POST另一个表单,id为description,包含素材的描述信息,内容格式为JSON,格式如下: +> ```json +> { +> "title": "VIDEO_TITLE", +> "introduction": "INTRODUCTION" +> } +> ``` + +## 解决方案 + +`CommonUploadParam` 类已经扩展支持额外的表单字段,可以在上传文件的同时提交其他表单数据。 + +## 使用示例 + +### 1. 基本用法 - 上传永久视频素材 + +```java +import me.chanjar.weixin.common.bean.CommonUploadParam; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import me.chanjar.weixin.mp.api.WxMpService; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +public class VideoMaterialUploadExample { + + public void uploadVideoMaterial(WxMpService wxMpService) throws Exception { + // 准备视频文件 + File videoFile = new File("/path/to/video.mp4"); + + // 创建上传参数 + CommonUploadParam uploadParam = CommonUploadParam.fromFile("media", videoFile); + + // 准备视频描述信息(JSON格式) + Map description = new HashMap<>(); + description.put("title", "我的视频标题"); + description.put("introduction", "这是一个精彩的视频介绍"); + String descriptionJson = WxGsonBuilder.create().toJson(description); + + // 添加description表单字段 + uploadParam.addFormField("description", descriptionJson); + + // 调用微信API上传 + String url = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=video"; + String response = wxMpService.upload(url, uploadParam); + + System.out.println("上传成功:" + response); + } +} +``` + +### 2. 链式调用风格 + +```java +import me.chanjar.weixin.common.bean.CommonUploadParam; +import com.google.gson.JsonObject; + +public class ChainStyleExample { + + public void uploadWithChainStyle(WxMpService wxMpService) throws Exception { + File videoFile = new File("/path/to/video.mp4"); + + // 准备描述信息 + JsonObject description = new JsonObject(); + description.addProperty("title", "视频标题"); + description.addProperty("introduction", "视频介绍"); + + // 使用链式调用 + String response = wxMpService.upload( + "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=video", + CommonUploadParam.fromFile("media", videoFile) + .addFormField("description", description.toString()) + ); + + System.out.println("上传成功:" + response); + } +} +``` + +### 3. 多个额外表单字段 + +```java +import me.chanjar.weixin.common.bean.CommonUploadParam; + +public class MultipleFormFieldsExample { + + public void uploadWithMultipleFields(WxMpService wxMpService) throws Exception { + File file = new File("/path/to/file.jpg"); + + // 可以添加多个表单字段 + CommonUploadParam uploadParam = CommonUploadParam.fromFile("media", file) + .addFormField("field1", "value1") + .addFormField("field2", "value2") + .addFormField("field3", "value3"); + + String response = wxMpService.upload("https://api.weixin.qq.com/some/upload/url", uploadParam); + + System.out.println("上传成功:" + response); + } +} +``` + +### 4. 从字节数组上传并添加表单字段 + +```java +import me.chanjar.weixin.common.bean.CommonUploadParam; + +public class ByteArrayUploadExample { + + public void uploadFromBytes(WxMpService wxMpService) throws Exception { + // 从字节数组创建上传参数 + byte[] fileBytes = getFileBytes(); + + CommonUploadParam uploadParam = CommonUploadParam + .fromBytes("media", "video.mp4", fileBytes) + .addFormField("description", "{\"title\":\"标题\",\"introduction\":\"介绍\"}"); + + String response = wxMpService.upload( + "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=video", + uploadParam + ); + + System.out.println("上传成功:" + response); + } + + private byte[] getFileBytes() { + // 获取文件字节数组的逻辑 + return new byte[0]; + } +} +``` + +## API 说明 + +### CommonUploadParam 类 + +#### 构造方法 +- `fromFile(String name, File file)` - 从文件创建上传参数 +- `fromBytes(String name, String fileName, byte[] bytes)` - 从字节数组创建上传参数 + +#### 方法 +- `addFormField(String fieldName, String fieldValue)` - 添加额外的表单字段,返回当前对象支持链式调用 +- `getFormFields()` - 获取所有额外的表单字段(Map类型) +- `setFormFields(Map formFields)` - 设置额外的表单字段 + +#### 属性 +- `name` - 文件对应的接口参数名称(如:media) +- `data` - 上传数据(CommonUploadData对象) +- `formFields` - 额外的表单字段(可选,Map类型) + +## 注意事项 + +1. **表单字段是可选的**:如果不需要额外的表单字段,可以不调用`addFormField`方法 +2. **JSON格式**:对于需要JSON格式的表单字段(如description),需要先将对象转换为JSON字符串 +3. **编码**:表单字段值会使用UTF-8编码 +4. **所有HTTP客户端支持**:该功能在所有HTTP客户端实现中都得到支持(OkHttp、Apache HttpClient、HttpComponents、JoddHttp) + +## 兼容性 + +- 对于通过 `fromFile`、`fromBytes` 等工厂方法创建 `CommonUploadParam` 的代码,本功能在行为层面是向后兼容的,现有代码无需修改即可继续工作。 +- 如果之前直接使用构造函数(例如 `new CommonUploadParam(name, data)`)创建对象,由于新增了 `formFields` 字段,构造函数签名可能发生变化,升级后需要改为使用上述工厂方法或根据新构造函数签名调整代码。 diff --git a/docs/ELECTRONIC_INVOICE_API_COMPARISON.md b/docs/ELECTRONIC_INVOICE_API_COMPARISON.md new file mode 100644 index 0000000000..aed15d321d --- /dev/null +++ b/docs/ELECTRONIC_INVOICE_API_COMPARISON.md @@ -0,0 +1,66 @@ +# 服务商电子发票接口体系对比 + +## 结论 + +Gitee Issue `IDMETW` 补充的官方链接 +[获取开通服务商电子发票能力邀请链接](https://pay.weixin.qq.com/doc/v3/partner/4015941495) +与 GitHub Issue [#4066](https://github.com/binarywang/WxJava/issues/4066) 给出的 +[开发接入准备](https://pay.weixin.qq.com/doc/v3/partner/4015792554) 是**同一套**微信支付 +V3 服务商电子发票产品文档:前者是 API 列表中的具体接口,后者是该产品的接入总览。 + +但是,当前 `weixin-java-mp` 的 `WxMpMerchantInvoiceService` 是另一套旧的公众号 +`/card/invoice/*` 接口;不能把它当作这两个 Issue 所要求的支付 V3 能力实现。 + +## 当前仓库既有的公众号体系 + +- 当前代码将该能力归入公众号模块: + `weixin-java-mp` 的 + `WxMpMerchantInvoiceService`。 +- 该接口的 Javadoc 明确引用公众号官方文档: + [商户开票模式说明](https://developers.weixin.qq.com/doc/offiaccount/WeChat_Invoice/E_Invoice/Vendor_and_Invoicing_Platform_Mode_Instruction.html) + 和 + [商户开票接口列表](https://developers.weixin.qq.com/doc/offiaccount/WeChat_Invoice/E_Invoice/Vendor_API_List.html)。 + 后者当前重定向至 + [新版服务号文档](https://developers.weixin.qq.com/doc/service/guide/product/weixin_invoice/E_Invoice/Vendor_API_List.html)。 +- 代码实际请求公众号 API:`/card/invoice/getauthurl`、 + `/card/invoice/getauthdata`、`/card/invoice/makeoutinvoice`、 + `/card/invoice/clearoutinvoice`、`/card/invoice/queryinvoceinfo` 等;见 + `WxMpApiUrl.Invoice`。这套接口围绕授权页、获取用户授权数据、开票、冲红和 + 查询发票信息。 +- 历史提交 `058ce62a2b932633931e30762f44c561481dde5f` 将该服务及其请求/响应 + Bean 加入 `weixin-java-mp`;提交说明关联 #1305。 + +> 说明:调研时 Gitee 页面/API 的 TLS 连接失败,未能独立读取 IDMETW 的评论; +> 但用户随后提供的 `4015941495` 链接已明确该 Issue 实际指向支付 V3 服务商电子发票。 + +## 两个 Issue 共同指向的支付 V3 体系 + +- #4066 链接的[开发接入准备](https://pay.weixin.qq.com/doc/v3/partner/4015792554) + 属于 + “微信电子发票”产品,要求在**微信支付服务商号**申请“服务商电子发票”权限, + 并提到数电发票资源。 +- IDMETW 链接的[获取开通服务商电子发票能力邀请链接](https://pay.weixin.qq.com/doc/v3/partner/4015941495) + 位于相同产品的“API 列表”下,路径为 + `GET /v3/new-tax-control-fapiao/fapiaomerchant/getspinviteurl`。 +- 同一官方文档导航的 API 列表包含:邀请子商户开通、检查子商户开票状态、创建电子 + 发票卡券模板、配置开发选项、抬头填写链接/信息、各行业开票、冲红、查询、下载/上传 + 发票文件、插入用户卡包,以及多个异步通知。 +- 例如“开具通用行业电子发票”接口为 + `POST /v3/new-tax-control-fapiao/fapiao-applications/issue-general`,请求域名为 + `https://api.mch.weixin.qq.com`,并要求微信支付 API 证书签名、服务商模式的 + `sub_mchid` 和唯一开票申请单号 `fapiao_apply_id`;官方文档: + [开具通用行业电子发票](https://pay.weixin.qq.com/doc/v3/partner/4015792574)。 + 该页还规定敏感字段使用微信支付公钥或平台证书加密。 + +## 直接差异 + +| 维度 | 既有公众号能力 | 两个 Issue 指向的微信支付能力 | +| --- | --- | --- | +| SDK 模块 | `weixin-java-mp` | 应位于 `weixin-java-pay` | +| 官方文档产品线 | `developers.weixin.qq.com` 的公众号电子发票 | `pay.weixin.qq.com/doc/v3/partner` 的微信支付合作伙伴电子发票 | +| API 形态 | `/card/invoice/*` | `/v3/new-tax-control-fapiao/*` | +| 身份/鉴权上下文 | 公众号 access token、用户授权页/授权数据 | 微信支付服务商号、子商户号、V3 签名与敏感字段加密 | +| 覆盖范围 | 授权、开票、冲红、查询及公众号商户配置 | 子商户邀约/状态、模板/开发配置、行业开票、文件和卡包、通知 | + +因此,两个 Issue 都是同一个微信支付 V3 服务商电子发票接入需求,当前由 +`weixin-java-pay` 的 `PartnerInvoiceService` 提供对应的 `new-tax-control-fapiao` API 实现。 diff --git a/docs/HTTPCLIENT_UPGRADE_GUIDE.md b/docs/HTTPCLIENT_UPGRADE_GUIDE.md new file mode 100644 index 0000000000..717b1d1709 --- /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.6.3 | `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/docs/WEIXIN_JAVA_STORE_MIGRATION.md b/docs/WEIXIN_JAVA_STORE_MIGRATION.md new file mode 100644 index 0000000000..4523660830 --- /dev/null +++ b/docs/WEIXIN_JAVA_STORE_MIGRATION.md @@ -0,0 +1,64 @@ +# 微信小店模块迁移指南 + +从本版本开始,微信小店能力由独立的 `weixin-java-store` 模块提供。原有 +`weixin-java-channel` 小店 API 仍可继续使用,且行为不变;它们已标记为 +`@Deprecated`,以便在后续版本中引导迁移。 + +## 依赖替换 + +将: + +```xml + + com.github.binarywang + weixin-java-channel + +``` + +替换或并行增加为: + +```xml + + com.github.binarywang + weixin-java-store + +``` + +新模块不依赖 `weixin-java-channel`。仅接入微信小店的店铺、商品、订单、售后、 +物流和资金等经营能力时,只需依赖 `weixin-java-store`;只有同时需要视频号直播、 +Finder 或橱窗等视频号能力时,才额外依赖 `weixin-java-channel`。两者可以同时引入, +适合按业务逐步迁移。 + +## 包名与入口替换 + +| 旧入口 | 新入口 | +| --- | --- | +| `me.chanjar.weixin.channel.api.WxChannelService` | `com.binarywang.wxjava.store.api.WxStoreService` | +| `WxChannelProductService` | `WxStoreProductService` | +| `WxChannelOrderService` | `WxStoreOrderService` | +| `WxChannelAfterSaleService` | `WxStoreAfterSaleService` | +| `WxChannelFundService` | `WxStoreFundService` | +| `WxChannelWarehouseService` | `WxStoreWarehouseService` | + +所有新模型均位于 `com.binarywang.wxjava.store.bean.*`。旧模型不会改包或删除; +由于新旧模型是独立类型,业务层应在迁移边界显式完成类型转换。 + +## 框架集成 + +微信小店提供独立的 Spring Boot 与 Solon 集成模块,并统一使用 `wx.store` +作为配置前缀: + +| 场景 | 模块 | +| --- | --- | +| Spring Boot 单账号 | `wx-java-store-spring-boot-starter` | +| Spring Boot 多账号 | `wx-java-store-multi-spring-boot-starter` | +| Solon 单账号 | `wx-java-store-solon-plugin` | +| Solon 多账号 | `wx-java-store-multi-solon-plugin` | + +原有 Channel Starter/Plugin 及其 `wx.channel` 配置保持不变,可与新的 +Store Starter/Plugin 并存。 + +## 不属于 store 的能力 + +视频号直播、Finder、联盟分销、留资组件与达人罗盘继续使用 +`weixin-java-channel`,不会被弃用,也不会由 `WxStoreService` 暴露。 diff --git a/docs/superpowers/plans/2026-08-22-channel-merchant-kf.md b/docs/superpowers/plans/2026-08-22-channel-merchant-kf.md new file mode 100644 index 0000000000..99aa235cb1 --- /dev/null +++ b/docs/superpowers/plans/2026-08-22-channel-merchant-kf.md @@ -0,0 +1,45 @@ +# 视频号小店商家客服 API Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 为 `weixin-java-channel` 增加商家客服媒体上传和消息发送 API。 + +**Architecture:** 以独立的客服子服务封装两条官方 API,通过既有 `BaseWxChannelServiceImpl` 完成鉴权、JSON 请求与 multipart 上传。请求模型保持强类型,服务层测试使用可记录调用的测试替身,避免真实网络依赖。 + +**Tech Stack:** Java 8、Maven、TestNG、Lombok、Jackson 注解。 + +## Global Constraints + +- Java 8 兼容,不新增依赖。 +- API 路径固定为 `/channels/ec/commkf/cosupload` 和 `/channels/ec/commkf/sendmsg`。 +- 使用 TestNG,所有新增测试不使用真实微信凭据。 + +--- + +### Task 1: 请求与响应模型 + +**Files:** +- Create: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfCosUploadResponse.java` +- Create: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfSendMsgParam.java` +- Create: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfSendMsgResponse.java` +- Test: `weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfBeanTest.java` + +- [ ] Write JSON encode/decode tests for `request_id`, `open_id`, `msg_type`, `text.content`, `cos_url` and `msg_id`. +- [ ] Run the test and verify it fails because the classes do not exist. +- [ ] Add the minimal annotated model classes and nested message content types. +- [ ] Run the test and verify it passes. + +### Task 2: 服务入口与请求执行 + +**Files:** +- Create: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelKfService.java` +- Create: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelKfServiceImpl.java` +- Modify: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java` +- Modify: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelServiceImpl.java` +- Modify: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java` +- Test: `weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelKfServiceImplTest.java` + +- [ ] Write tests proving the service uses the documented URLs, uploads `file`, `open_id`, `msg_type`, decodes both responses, and caches `getKfService()`. +- [ ] Run the test and verify it fails because the API is absent. +- [ ] Add the minimal service API, implementation, constants and cached service entry point. +- [ ] Run the focused tests and module test suite, then inspect `git diff --check`. diff --git a/docs/superpowers/specs/2026-08-22-channel-merchant-kf-design.md b/docs/superpowers/specs/2026-08-22-channel-merchant-kf-design.md new file mode 100644 index 0000000000..12ecfd95c8 --- /dev/null +++ b/docs/superpowers/specs/2026-08-22-channel-merchant-kf-design.md @@ -0,0 +1,18 @@ +# 视频号小店商家客服 API 设计 + +## 目标 + +实现 Issue #3991 所列的商家客服媒体上传与消息发送 API,并使调用入口、请求模型和响应解析与现有 `weixin-java-channel` 服务保持一致。 + +## 设计 + +- 在 `WxChannelService` 暴露 `getKfService()`,由 `BaseWxChannelServiceImpl` 缓存并懒加载 `WxChannelKfServiceImpl`。 +- `WxChannelKfService` 提供媒体上传(带文件名和便捷重载)以及接收强类型请求参数的消息发送方法。上传请求通过既有 `CommonUploadParam` 发送 multipart 数据。 +- 消息模型用 `@JsonProperty` 显式映射微信字段,支持 text、image、video、file、product_share 和 order_share 六类内容;响应继承项目既有基础响应。 +- API 常量使用官方文档确认的 `/channels/ec/commkf/cosupload` 和 `/channels/ec/commkf/sendmsg` 路径。 + +## 质量边界 + +- 保持 Java 8 兼容,不增加依赖,不变更现有公共 API。 +- 使用 TestNG 覆盖请求/响应 JSON 映射、服务 URL、上传表单字段及服务入口缓存;测试不依赖真实微信凭据。 +- PR 使用 `Closes #3991` 关联并关闭原始 Issue;旧 PR #4037 在新 PR 创建后以替代说明关闭。 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/ccflow.png b/images/banners/ccflow.png deleted file mode 100644 index 1209739f6a..0000000000 Binary files a/images/banners/ccflow.png and /dev/null differ diff --git a/images/banners/diboot.png b/images/banners/diboot.png deleted file mode 100644 index c22d0b8ed8..0000000000 Binary files a/images/banners/diboot.png and /dev/null differ diff --git a/images/banners/planB.jpg b/images/banners/planB.jpg deleted file mode 100644 index 139957fbef..0000000000 Binary files a/images/banners/planB.jpg and /dev/null differ diff --git a/others/mvnw b/others/mvnw old mode 100644 new mode 100755 diff --git a/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 0018b73e5e..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.19 + 1.4.21 provided diff --git a/pom.xml b/pom.xml index 6c44c374bf..75887b10fb 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.github.binarywang wx-java - 4.5.3.B + 4.8.6.B pom WxJava - Weixin/Wechat Java SDK 微信开发Java SDK @@ -12,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 @@ -102,6 +102,11 @@ wangkaikate@163.com https://github.com/0katekate0 + + Bincent + hongbin.hsu@qq.com + https://gitee.com/bincent + @@ -119,8 +124,12 @@ weixin-java-miniapp weixin-java-open weixin-java-qidian + weixin-java-aispeech weixin-java-channel + weixin-java-store spring-boot-starters + solon-plugins + wx-java-bom @@ -130,30 +139,38 @@ UTF-8 4.5.13 - 9.4.51.v20230217 - + 5.6.3 + 9.4.57.v20241219 + 1.84 + 2.3.3.RELEASE - com.github.binarywang qrcode-utils - 1.1 + 1.3 org.jodd jodd-http - 6.2.1 + 6.3.0 provided com.squareup.okhttp3 okhttp - 4.5.0 + 4.12.0 provided + + + org.apache.httpcomponents.client5 + httpclient5 + ${httpclient5.version} + + org.apache.httpcomponents httpclient @@ -172,12 +189,12 @@ commons-io commons-io - 2.7 + 2.14.0 org.apache.commons commons-lang3 - 3.10 + 3.18.0 org.slf4j @@ -187,35 +204,38 @@ com.thoughtworks.xstream xstream - 1.4.20 + 1.4.21 com.google.guava guava - 32.0.0-jre + 33.3.1-jre com.google.code.gson gson - 2.8.9 + 2.13.1 - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.13.0 + com.fasterxml.jackson + jackson-bom + 2.18.4 + pom + import - + joda-time joda-time 2.10.6 - test + + ch.qos.logback logback-classic - 1.2.9 + 1.3.12 test @@ -243,8 +263,8 @@ org.mockito - mockito-all - 1.10.19 + mockito-core + 4.11.0 test @@ -287,7 +307,7 @@ org.redisson redisson - 3.12.0 + 3.23.3 true provided @@ -305,38 +325,25 @@ - - org.springframework.data - spring-data-redis - 2.3.3.RELEASE - true - provided - org.projectlombok lombok - 1.18.24 + 1.18.30 provided org.bouncycastle - bcpkix-jdk15on - 1.70 + bcpkix-jdk18on + ${bouncycastle.version} + + + org.bouncycastle + bcprov-jdk18on + ${bouncycastle.version} - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - doclint-java8-disable @@ -355,7 +362,7 @@ org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.1.0 attach-sources @@ -386,7 +393,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.1.0 sign-artifacts @@ -426,14 +433,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 @@ -467,6 +474,21 @@ + + org.apache.maven.plugins + maven-source-plugin + 3.3.0 + + + attach-sources + + verify + + jar-no-fork + + + + diff --git a/skills/wxjava-api-contributor/SKILL.md b/skills/wxjava-api-contributor/SKILL.md new file mode 100644 index 0000000000..b6896f105d --- /dev/null +++ b/skills/wxjava-api-contributor/SKILL.md @@ -0,0 +1,14 @@ +--- +name: wxjava-api-contributor +description: 按 WxJava 的 Maven 多模块、Java 8、公共 API 兼容性和 TestNG 约定,为微信官方接口新增或维护 SDK 支持。适用于新增 Service API、请求响应 Bean、序列化、HTTP 实现、Starter 配置或回归测试时。 +--- + +# WxJava 接口贡献 + +1. 先搜索开放与已关闭 Issue,确认需求是否已有讨论、实现、回归用例或官方接口变动;再确认微信产品和目标模块。 +2. 阅读对应 README、POM、相似接口、实现与测试,并读取 [贡献约定](references/contribution.md)。 +3. 将官方接口契约映射到 Service、实现、Bean、URL、序列化和测试;列出所有可能受影响的 HTTP 客户端和 Starter。 +4. 只做完成需求所需的最小改动;更新必要 Javadoc、测试与用户可见文档。 +5. 执行 `mvn -pl -am test`,并检查 `git diff --check`。 + +保持 Java 8 与公共 API、异常语义、JSON/XML 字段兼容性。涉及多 HTTP 客户端、单/多账号 Starter 或 Solon 插件时,检查等价实现是否需要同步。PR 应关联对应 Issue,目标分支为 `develop`。 diff --git a/skills/wxjava-api-contributor/agents/openai.yaml b/skills/wxjava-api-contributor/agents/openai.yaml new file mode 100644 index 0000000000..acefb1b7e9 --- /dev/null +++ b/skills/wxjava-api-contributor/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "WxJava 接口贡献" + short_description: "按 WxJava 项目约定新增或维护 SDK 接口能力" + default_prompt: "使用 $wxjava-api-contributor 为 WxJava 新增微信接口支持。" diff --git a/skills/wxjava-api-contributor/references/contribution.md b/skills/wxjava-api-contributor/references/contribution.md new file mode 100644 index 0000000000..6b8571e28e --- /dev/null +++ b/skills/wxjava-api-contributor/references/contribution.md @@ -0,0 +1,19 @@ +# 贡献约定 + +以微信官方接口定义和本仓库同类实现为准,核对路径、方法、字段名、必填项和响应结构。优先复用既有 HTTP 执行器、异常、配置、Gson/Jackson/XStream 映射和 TestNG 测试模式。 + +公共方法需要准确 Javadoc。不要吞异常、记录敏感值或无关重构。新增公开方法和 bug 修复均应有针对性测试;涉及公共模块、BOM 或多模块时扩大验证范围。 + +## 接口增量检查表 + +1. 从微信官方文档和相关 Issue 中确认接口可用条件、HTTP 方法、URL、必填字段、签名/加密要求和响应示例。 +2. 搜索同产品的相邻能力,复用其 Service 分层、Bean 命名、请求执行和错误处理模式;不要只新增 Bean 而遗漏 Service 暴露。 +3. 若接口尚未支持,优先使用现有通用执行能力;MP/CP Wiki 说明通用执行器会处理 access token 刷新及 `errcode` 到异常的转换。 +4. 为字段边界、空值、JSON/XML 映射和异常路径添加 TestNG 回归测试。API 路径或字段问题是历史 bug 的高频来源,例如 [#3982](https://github.com/binarywang/WxJava/issues/3982)、[#4000](https://github.com/binarywang/WxJava/issues/4000)。 +5. 按 [贡献指南](https://github.com/binarywang/WxJava/blob/develop/CONTRIBUTING.md) 使用 `develop` 作为 PR 目标;说明 Issue、兼容性影响和验证命令。 + +## 一手资料入口 + +- [如何调用 MP 未支持接口](https://github.com/binarywang/WxJava/wiki/MP_%E5%A6%82%E4%BD%95%E8%B0%83%E7%94%A8%E6%9C%AA%E6%94%AF%E6%8C%81%E7%9A%84%E6%8E%A5%E5%8F%A3) +- [如何调用 CP 未支持接口](https://github.com/binarywang/WxJava/wiki/CP_%E5%A6%82%E4%BD%95%E8%B0%83%E7%94%A8%E6%9C%AA%E6%94%AF%E6%8C%81%E7%9A%84%E6%8E%A5%E5%8F%A3) +- [关闭的新接口 Issue](https://github.com/binarywang/WxJava/issues?q=is%3Aissue%20state%3Aclosed%20label%3A%E6%96%B0%E6%8E%A5%E5%8F%A3) diff --git a/skills/wxjava-integration-guide/SKILL.md b/skills/wxjava-integration-guide/SKILL.md new file mode 100644 index 0000000000..393438768f --- /dev/null +++ b/skills/wxjava-integration-guide/SKILL.md @@ -0,0 +1,14 @@ +--- +name: wxjava-integration-guide +description: 为 Java、Spring Boot 或 Solon 项目生成可验证的 WxJava 接入方案,包括模块选择、BOM、配置、最小调用代码以及单/多账号集成。适用于用户要求接入公众号、小程序、支付、企业微信、开放平台、视频号或微信小店时。 +--- + +# WxJava 接入指南 + +1. 确认微信产品、框架、单/多账号、部署形态和首个 API 调用。 +2. 读取 [接入约束](references/integration.md),选择模块和配置方式。 +3. 先输出依赖与配置,再输出最小调用;每段示例注明应放置的位置和所依赖的模块。 +4. 输出脱敏配置和最小服务端代码;凭据一律用占位符。 +5. 给出本地验证步骤与安全提醒;支付、回调和证书示例不得直接用于生产。 + +保持 Java 8 兼容。生产集群必须使用可共享的配置存储或 token 存储;不要把内存实现当作多节点部署方案。引用已有 Demo、Wiki 或 README 作为继续阅读入口;不虚构配置键、SDK 方法或版本号。 diff --git a/skills/wxjava-integration-guide/agents/openai.yaml b/skills/wxjava-integration-guide/agents/openai.yaml new file mode 100644 index 0000000000..622ec2ac98 --- /dev/null +++ b/skills/wxjava-integration-guide/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "WxJava 接入指南" + short_description: "生成可验证的 WxJava 最小接入方案与配置示例" + default_prompt: "使用 $wxjava-integration-guide 为我的项目生成 WxJava 接入代码。" diff --git a/skills/wxjava-integration-guide/references/integration.md b/skills/wxjava-integration-guide/references/integration.md new file mode 100644 index 0000000000..ae441cc278 --- /dev/null +++ b/skills/wxjava-integration-guide/references/integration.md @@ -0,0 +1,22 @@ +# 接入约束 + +优先通过 `wx-java-bom` 统一管理多个 WxJava 模块版本。核心 SDK 模块是 MP、MiniApp、Pay、CP、Open、Channel、Qidian、Aispeech;框架集成模块位于 `spring-boot-starters` 与 `solon-plugins`。 + +查阅相应模块 README、根目录 [demo.md](https://github.com/binarywang/WxJava/blob/develop/demo.md) 和目标模块测试中的示例,确认配置属性与初始化模式。所有 `appId`、`secret`、商户私钥、API v3 密钥和证书均使用占位符,不得输出到日志。 + +## 实施检查表 + +1. 先完成一条只读或低风险 API 调用,再接入消息、支付或异步回调。 +2. 单实例可使用默认配置存储;多实例或集群需使用共享的 token/config storage,避免节点各自刷新 access token。 +3. 公众号、企业微信等回调必须先按平台要求校验消息合法性,再进入业务路由;支付回调还必须按商户单号实现幂等。 +4. 需要代理或私有网络出口时,明确区分正向代理与反向代理。支付 V3 的签名路径不能因反向代理路径前缀而被错误改写。 +5. HTTP 客户端类型、Starter 配置键和 Service 实现必须取自目标模块的当前 README、POM 或相邻 Demo;不要混用不同产品模块的配置前缀。 +6. Starter 自动配置与 Demo 手动初始化必须二选一后再给示例。发生空 key、注入为空或 NPE 时,先核对启动模块、profile、配置前缀和当前 `*Properties` 类;历史 [#2177](https://github.com/binarywang/WxJava/issues/2177) 是混用两种配置模型的案例。 +7. Quarkus 或 GraalVM 场景转到 [Quarkus 支持文档](https://github.com/binarywang/WxJava/blob/develop/docs/QUARKUS_SUPPORT.md),不要套用 Spring Boot Starter 配置。 + +## 一手资料入口 + +- [MP Quick Start](https://github.com/binarywang/WxJava/wiki/MP_Quick-Start) +- [微信支付说明](https://github.com/binarywang/WxJava/wiki/%E5%BE%AE%E4%BF%A1%E6%94%AF%E4%BB%98) +- [SDK 正反向代理支持](https://github.com/binarywang/WxJava/wiki/SDK-%E9%92%88%E5%AF%B9%E5%BE%AE%E4%BF%A1-%E6%AD%A3%E5%90%91%E4%BB%A3%E7%90%86%E5%92%8C%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E6%94%AF%E6%8C%81) +- [HTTP 客户端升级指南](https://github.com/binarywang/WxJava/blob/develop/docs/HTTPCLIENT_UPGRADE_GUIDE.md) diff --git a/skills/wxjava-module-selector/SKILL.md b/skills/wxjava-module-selector/SKILL.md new file mode 100644 index 0000000000..3d78c0ebf6 --- /dev/null +++ b/skills/wxjava-module-selector/SKILL.md @@ -0,0 +1,15 @@ +--- +name: wxjava-module-selector +description: 根据微信公众号、小程序、微信支付、企业微信、开放平台、视频号或微信小店、腾讯企点和微信智能对话等业务场景,为用户选择合适的 WxJava Maven 模块、BOM 和示例入口。适用于用户询问“该用哪个模块”、依赖坐标、产品边界或单/多账号 Starter 选择时。 +--- + +# WxJava 模块选择 + +1. 识别微信产品、服务端框架、是否多账号、是否包含支付或回调;信息不足时只询问必要问题。 +2. 读取 [模块映射](references/modules.md),给出一个主推荐,以及组合模块的理由。 +3. 先区分产品边界,再选择核心 SDK;仅当项目确实依赖框架自动配置时才额外推荐 Starter 或 Solon 插件。 +4. 优先推荐 BOM;给出准确的 `groupId`、`artifactId`、相应 Demo、Wiki 或仓库文档入口。 +5. 说明服务端 SDK 的边界:移动端登录、分享等能力仍需微信官方客户端 SDK。 +6. 不臆测版本号;建议以 Maven Central 或项目 README 的当前版本为准。 + +使用“场景 → 模块 → 集成选项 → 下一步”的简短结构。涉及多账号时说明单账号与 multi Starter 的区别;不要在示例中泄露凭据。支付和回调场景必须提醒用户验证通知 URL、验签与幂等处理。 diff --git a/skills/wxjava-module-selector/agents/openai.yaml b/skills/wxjava-module-selector/agents/openai.yaml new file mode 100644 index 0000000000..a3744af616 --- /dev/null +++ b/skills/wxjava-module-selector/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "WxJava 模块选择" + short_description: "按微信业务场景选择 WxJava 模块、依赖与示例" + default_prompt: "使用 $wxjava-module-selector 为我的微信业务选择合适的 WxJava 模块。" diff --git a/skills/wxjava-module-selector/references/modules.md b/skills/wxjava-module-selector/references/modules.md new file mode 100644 index 0000000000..563cdbb954 --- /dev/null +++ b/skills/wxjava-module-selector/references/modules.md @@ -0,0 +1,37 @@ +# WxJava 模块映射 + +| 场景 | 核心模块 | +| --- | --- | +| 微信公众号 | `weixin-java-mp` | +| 微信小程序 | `weixin-java-miniapp` | +| 微信支付 | `weixin-java-pay` | +| 企业微信 | `weixin-java-cp` | +| 微信开放平台/第三方平台 | `weixin-java-open` | +| 视频号/微信小店 | `weixin-java-channel` | +| 腾讯企点/微信客服 | `weixin-java-qidian` | +| 微信智能对话/智能语音 | `weixin-java-aispeech` | + +多个模块并用时优先使用 `com.github.binarywang:wx-java-bom`。Spring Boot 集成从 `spring-boot-starters` 选择;Solon 集成从 `solon-plugins` 选择。 + +## 多账号集成范围 + +只有多个独立微信应用配置时才选择 multi 集成,并先按框架与产品核对目录是否存在: + +- Spring Boot:MP、MiniApp、CP、自建/第三方 CP、Pay、Open、Channel 都有相应 multi Starter。 +- Solon:仅 MP、MiniApp、CP、Channel 有 multi 插件;Pay、Open、Qidian 目前只有单账号插件,不要推荐不存在的 multi artifact。 + +## 选择检查点 + +- 公众号、小程序和企业微信的消息回调、token 与加解密配置彼此独立;不要因同属一个公司而复用不兼容的凭据或配置对象。 +- 企业微信的多应用应使用独立的 `WxCpConfigStorage` 与 `WxCpServiceImpl`;Wiki 明确指出复用 token、AES key 和 URL 会造成安全边界问题。 +- 支付能力通常与 MP、MiniApp 或 Open 同时使用:前者处理业务身份和消息,`weixin-java-pay` 处理商户签名、证书与支付回调。 +- 视频号/微信小店接口属于 `weixin-java-channel`;不要误归入 MP 或 Pay。 +- 腾讯企点与微信智能对话是独立 SDK 模块;不要将客服或智能对话需求默认归入 MP、CP 或 Channel。 +- 当能力在 MP 与 Open 等模块可能重叠时,按授权主体、官方 API 域和回调场景选择,不要只按“移动端”或“登录”字样判断。先在当前源码和 Issue 中确认覆盖状态,并标注“已确认 / 待查 / 需自行调用底层接口”。 +- BOM 适合同时使用多个 WxJava 模块。若项目还导入 Spring Boot 等上游 BOM,升级后执行 `mvn help:effective-pom` 与 `mvn dependency:tree`;历史 [#4058](https://github.com/binarywang/WxJava/issues/4058) 表明依赖管理顺序可能影响 Spring Data Redis 等依赖。 + +## 一手资料入口 + +- [WxJava Wiki 首页](https://github.com/binarywang/WxJava/wiki) +- [企业微信多应用配置](https://github.com/binarywang/WxJava/wiki/CP_%E5%A6%82%E4%BD%95%E6%94%AF%E6%8C%81%E5%A4%9A%E4%B8%AA%E4%BC%81%E4%B8%9A%E5%8F%B7%E5%BA%94%E7%94%A8%E6%88%96%E4%BC%81%E4%B8%9A%E5%8F%B7) +- [视频号/微信小店开发文档](https://github.com/binarywang/WxJava/wiki/0_%E8%A7%86%E9%A2%91%E5%8F%B7_%E5%BE%AE%E4%BF%A1%E5%B0%8F%E5%BA%97%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3) diff --git a/skills/wxjava-troubleshooter/SKILL.md b/skills/wxjava-troubleshooter/SKILL.md new file mode 100644 index 0000000000..26d8c941ed --- /dev/null +++ b/skills/wxjava-troubleshooter/SKILL.md @@ -0,0 +1,14 @@ +--- +name: wxjava-troubleshooter +description: 排查 WxJava 在配置初始化、access token、签名验签、支付证书、回调通知、序列化、网络请求和多账号隔离方面的问题。适用于用户提供异常、日志、请求响应或“WxJava 为什么不能调用”的场景。 +--- + +# WxJava 故障排查 + +1. 收集最小复现:产品模块、SDK 版本、JDK、框架、依赖树、异常堆栈、请求 ID 与脱敏后的配置形状。 +2. 将故障归类为依赖/初始化、token 与存储、请求契约、HTTP/代理、回调验签、支付证书或并发与生命周期。 +3. 依据 [诊断清单](references/diagnostics.md),按最低成本、最可验证的顺序排查。 +4. 每次只提出一个可验证根因,给出最小修复、预期现象和回归验证动作。 +5. 签名、支付、回调和加密场景须核对编码、字段排序、金额精度、时间戳、证书链和重放保护。 + +要求遮蔽 secret、token、私钥、证书、签名原文和个人数据。不要建议关闭 TLS 校验或验签;区分已证实结论与待验证假设。遇到历史同类问题时,给出对应 Issue 或 Wiki 链接,但必须检查当前版本的实现后再套用修复。 diff --git a/skills/wxjava-troubleshooter/agents/openai.yaml b/skills/wxjava-troubleshooter/agents/openai.yaml new file mode 100644 index 0000000000..f473f862fe --- /dev/null +++ b/skills/wxjava-troubleshooter/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "WxJava 故障排查" + short_description: "定位 WxJava 配置、回调、签名与接口调用问题" + default_prompt: "使用 $wxjava-troubleshooter 排查我的 WxJava 调用异常。" diff --git a/skills/wxjava-troubleshooter/references/diagnostics.md b/skills/wxjava-troubleshooter/references/diagnostics.md new file mode 100644 index 0000000000..acf115ae92 --- /dev/null +++ b/skills/wxjava-troubleshooter/references/diagnostics.md @@ -0,0 +1,27 @@ +# 诊断清单 + +- 配置:模块是否正确,应用标识和凭据是否配对,多账号配置是否被正确选择。 +- 请求:HTTP 方法、路径、必填字段和 JSON/XML 映射是否符合微信接口契约。 +- 认证:token 缓存、签名字段、时间戳、nonce、证书和密钥格式是否有效。 +- 网络:DNS、代理、超时、TLS 证书校验和响应关闭是否正常。 +- 回调:回调 URL 可访问、验签参数完整、幂等处理和资源释放是否存在。 + +从精确异常和最小请求入手,不用“重试”替代根因分析。 + +## 高频场景 + +| 症状 | 优先检查 | 依据 | +| --- | --- | --- | +| 多节点间歇性 token 失效 | 是否共享 token/config storage,是否让各节点并发刷新 | Wiki 的 MP/CP 配置存储说明与集群案例 | +| `NoClassDefFoundError`、`NoSuchMethodError` | `mvn dependency:tree`、HttpClient/commons-lang/xstream 冲突及 BOM 是否统一版本 | Wiki 的异常排查页与 HTTP 客户端升级指南 | +| 回调验签或重复处理 | 原始请求体、时间戳/nonce/签名、回调 URL、业务幂等键 | MP 合法性校验与支付回调说明 | +| HTTP 连接、超时或代理问题 | 连接/读取超时、代理、TLS、正反向代理是否混用 | Wiki 的 HttpClient 参数与代理文档 | +| 企业微信会话存档崩溃 | 是否仍手动销毁旧 SDK 实例,是否已迁移安全 API | [会话存档安全使用指南](https://github.com/binarywang/WxJava/blob/develop/docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md) | +| 视频号/小店接口 404 或字段不匹配 | 当前官方路径与请求字段、目标 SDK 版本、历史 Issue 是否已修复 | 关闭的 bug Issue,如 [#3982](https://github.com/binarywang/WxJava/issues/3982) | + +## 一手资料入口 + +- [常见异常首页](https://github.com/binarywang/WxJava/wiki) +- [MP 消息合法性验证](https://github.com/binarywang/WxJava/wiki/MP_%E9%AA%8C%E8%AF%81%E6%B6%88%E6%81%AF%E5%90%88%E6%B3%95%E6%80%A7) +- [HttpClient 参数配置](https://github.com/binarywang/WxJava/wiki/HttpClient%E7%9B%B8%E5%85%B3%E5%8F%82%E6%95%B0%E7%9A%84%E8%AE%BE%E7%BD%AE%E6%96%B9%E6%B3%95) +- [关闭的 bug Issue](https://github.com/binarywang/WxJava/issues?q=is%3Aissue%20state%3Aclosed%20label%3Abug) diff --git a/skills/wxjava-upgrade-guide/SKILL.md b/skills/wxjava-upgrade-guide/SKILL.md new file mode 100644 index 0000000000..624a752338 --- /dev/null +++ b/skills/wxjava-upgrade-guide/SKILL.md @@ -0,0 +1,14 @@ +--- +name: wxjava-upgrade-guide +description: 规划 WxJava 的版本升级与迁移,检查 BOM、模块依赖、Java 版本、配置与公共 API 兼容性,并提供可回滚的验证步骤。适用于用户从旧版升级、切换依赖管理方式、处理兼容性告警或制定升级发布计划时。 +--- + +# WxJava 升级迁移 + +1. 收集当前与目标版本、已用模块、JDK、框架、BOM 使用情况、依赖树、代理配置和关键调用路径。 +2. 读取 [迁移检查表](references/migration.md),识别版本、依赖和配置边界。 +3. 按 HTTP 客户端、BOM、支付/回调、多账号、企业微信会话存档等变化类型选择迁移分支。 +4. 输出分阶段变更:依赖调整、编译、关键回归测试、灰度与明确回滚条件。 +5. 将不确定项标为待查,并引导查看目标版本的 release notes、README、Javadoc 与变更记录。 + +不要猜测废弃 API 或破坏性变更;以官方发布信息、当前代码与实际编译结果为准。默认保持 Java 8,除非目标版本明确改变此约束。不能以关闭验签、固定 token 或跳过测试作为回滚方案。 diff --git a/skills/wxjava-upgrade-guide/agents/openai.yaml b/skills/wxjava-upgrade-guide/agents/openai.yaml new file mode 100644 index 0000000000..fdf8b71ecb --- /dev/null +++ b/skills/wxjava-upgrade-guide/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "WxJava 升级迁移" + short_description: "规划 WxJava 版本升级、依赖调整与兼容性迁移" + default_prompt: "使用 $wxjava-upgrade-guide 制定 WxJava 版本升级方案。" diff --git a/skills/wxjava-upgrade-guide/references/migration.md b/skills/wxjava-upgrade-guide/references/migration.md new file mode 100644 index 0000000000..443ac73a0a --- /dev/null +++ b/skills/wxjava-upgrade-guide/references/migration.md @@ -0,0 +1,37 @@ +# 迁移检查表 + +- 使用多个 WxJava 模块时,优先迁移到 `wx-java-bom`,避免模块版本漂移。 +- 清点 MP、MiniApp、Pay、CP、Open、Channel 及框架 Starter/插件的实际依赖。 +- 更新后先执行编译和受影响模块测试,再验证 token、回调、支付和关键业务链路。 +- 为依赖版本保留可快速恢复的变更记录;按灰度策略验证生产环境,且不将凭据写入代码或日志。 + +## 升级分支 + +### HTTP 客户端 + +从 4.7.x 起,项目支持并推荐 Apache HttpClient 5.x,同时保留部分 4.x 兼容性。先按 [HTTP 客户端升级指南](https://github.com/binarywang/WxJava/blob/develop/docs/HTTPCLIENT_UPGRADE_GUIDE.md) 核对模块、依赖与 `http-client-type`,再检查代理 host、port、username、password 的完整性。历史 [#3836](https://github.com/binarywang/WxJava/issues/3836) 表明可选代理配置也需要回归。 + +### BOM 与依赖冲突 + +`wx-java-bom` 从 4.8.3.B 起提供。导入 BOM 前后均执行: + +```shell +mvn help:effective-pom +mvn dependency:tree +``` + +特别回归由 Spring Boot BOM 管理的 Redis、HTTP 客户端和序列化依赖;[#4058](https://github.com/binarywang/WxJava/issues/4058) 是历史冲突案例。 + +### 企业微信会话存档 + +升级到 4.8.0 或更高版本时,查找旧的 `getChatDatas`、`getDecryptData`、`getChatPlainText`、`getMediaFile` 和手动 `Finance.DestroySdk()`。按 [ThreadLocal 生命周期迁移文档](https://github.com/binarywang/WxJava/blob/develop/docs/CP_MSG_AUDIT_THREADLOCAL_LIFECYCLE_REFACTOR.md) 切换至新 API,并做并发验证。 + +ThreadLocal SDK 不会在线程结束时自动释放。在线程池、定时任务或一次性线程中,必须在任务的 `finally` 块调用 `msgAuditService.closeThreadLocalSdk()`;应用停止时再用 `closeAllSdks()` 做全局兜底。不要在业务代码中直接调用 `Finance.DestroySdk()`。 + +### 回调与支付 + +验证不止于 HTTP 2xx:分别覆盖 token 获取、签名/证书校验、重复回调、业务状态转换和多账号路由。多账号异步任务必须显式传递 appId 或服务上下文,不能假定 ThreadLocal 自动继承。 + +## 验证与回滚 + +按“编译和单测 → 非生产凭据冒烟 → 灰度和监控”执行。出现依赖树冲突、验签/证书异常、回调错误或 native 崩溃时停止扩大灰度,恢复上一个已验证依赖组合后再以最小复现定位。 diff --git a/solon-plugins/pom.xml b/solon-plugins/pom.xml new file mode 100644 index 0000000000..3d83856a37 --- /dev/null +++ b/solon-plugins/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + com.github.binarywang + wx-java + 4.8.6.B + + 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 + wx-java-store-solon-plugin + wx-java-store-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..bc78a9c0ca --- /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.6.B + + 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..c32c4c6124 --- /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.6.B + + 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..8eb467f98f --- /dev/null +++ b/solon-plugins/wx-java-cp-multi-solon-plugin/README.md @@ -0,0 +1,108 @@ +# wx-java-cp-multi-solon-plugin + +企业微信多账号配置 + +- 实现多 WxCpService 初始化。 +- 未实现 WxCpTpService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。 +- 未实现 WxCpCgService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。 + +## 关于 corp-secret 的说明 + +企业微信中不同功能模块对应不同的 `corp-secret`,每种 Secret 只对对应模块的接口具有调用权限: + +| Secret 类型 | 获取位置 | 可调用的接口 | 是否需要 agent-id | +|---|---|---|---| +| 自建应用 Secret | 应用管理 → 自建应用 → 选择应用 → 查看 Secret | 该应用有权限的接口 | **必填** | +| 通讯录同步 Secret | 管理工具 → 通讯录同步 → 查看 Secret | 部门/成员增删改查等通讯录接口 | **不填** | +| 客户联系 Secret | 客户联系 → API → Secret | 客户联系相关接口 | 不填 | + +> **常见问题**: +> - 使用自建应用 Secret + agent-id 可以获取部门列表,但**无法更新部门**(因为写接口需要通讯录同步权限) +> - 使用通讯录同步 Secret 可以同步部门,但**调用某些需要 agent-id 的应用接口会报错** + +如需同时使用多种权限范围,可在 `wx.cp.corps` 下配置多个条目,每个条目使用对应权限的 Secret,通过不同的 `tenantId` 区分后使用。 + +> **注意**: +> 当前插件实现会校验同一 `corp-id` 下的 `agent-id` **必须唯一**,并且 **只能有一个条目不填写 `agent-id`**。 +> 如果在同一 `corp-id` 下同时配置多个未填写 `agent-id` 的条目,会因 token/ticket 缓存 key 冲突而在启动时直接抛异常。 +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-cp-multi-solon-plugin + ${version} + + ``` +2. 添加配置(app.properties) + ```properties + # 自建应用 1 配置(使用自建应用 Secret,需填写 agent-id) + wx.cp.corps.app1.corp-id = @corp-id + wx.cp.corps.app1.corp-secret = @自建应用的Secret(在"应用管理-自建应用"中查看) + wx.cp.corps.app1.agent-id = @自建应用的AgentId + ## 选填 + wx.cp.corps.app1.token = @token + wx.cp.corps.app1.aes-key = @aes-key + wx.cp.corps.app1.msg-audit-priKey = @msg-audit-priKey + wx.cp.corps.app1.msg-audit-lib-path = @msg-audit-lib-path + + # 通讯录同步配置(使用通讯录同步 Secret,不需要填写 agent-id) + # 此配置用于部门、成员的增删改查等通讯录管理操作 + wx.cp.corps.contact.corp-id = @corp-id + wx.cp.corps.contact.corp-secret = @通讯录同步的Secret(在"管理工具-通讯录同步"中查看) + ## agent-id 不填,通讯录同步不需要 agentId + + # 公共配置 + ## 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.WxCpDepartmentService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.api.WxCpUserService; +import me.chanjar.weixin.cp.bean.WxCpDepart; +import org.noear.solon.annotation.Component; +import org.noear.solon.annotation.Inject; + +@Component +public class DemoService { + @Inject + private WxCpMultiServices wxCpMultiServices; + + public void test() { + // 使用自建应用的 WxCpService(对应 corp-secret 为自建应用 Secret) + WxCpService appService = wxCpMultiServices.getWxCpService("app1"); + WxCpUserService userService = appService.getUserService(); + userService.getUserId("xxx"); + // todo ... + + // 使用通讯录同步的 WxCpService(对应 corp-secret 为通讯录同步 Secret) + // 通讯录同步 Secret 具有部门/成员增删改查等权限 + WxCpService contactService = wxCpMultiServices.getWxCpService("contact"); + WxCpDepartmentService departmentService = contactService.getDepartmentService(); + // 更新部门示例(WxCpDepart 包含 id、name、parentId 等字段) + WxCpDepart depart = new WxCpDepart(); + depart.setId(100L); + depart.setName("新部门名称"); + departmentService.update(depart); + // 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..2aa0d9623b --- /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.6.B + + 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..25b4ab3747 --- /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,170 @@ +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.Objects; +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 时错乱。 + * + *

同一企业(corpId 相同)下可配置多个条目以使用不同的权限 Secret,例如:

+ *
    + *
  • 自建应用条目:填写应用对应的 corpSecret 和 agentId
  • + *
  • 通讯录同步条目:填写通讯录同步 Secret,agentId 可不填(null)
  • + *
+ *

但同一 corpId 下不允许出现重复的 agentId(包括多个 null)。

+ * + * 查看 {@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,使用字符串转换避免 null 与 agentId=0 冲突 + .collect(Collectors.groupingBy(c -> Objects.toString(c.getAgentId(), "null"), 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..6f7f633c3f --- /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,68 @@ +package com.binarywang.solon.wxjava.cp_multi.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 企业微信企业相关配置属性 + * + *

企业微信中不同的 corpSecret 对应不同的权限范围,常见的有:

+ *
    + *
  • 自建应用 Secret:在"应用管理 - 自建应用"中查看,只能调用该应用有权限的接口
  • + *
  • 通讯录同步 Secret:在"管理工具 - 通讯录同步"中查看,用于管理部门和成员(增删改查)
  • + *
  • 客户联系 Secret:在"客户联系"中查看,用于客户联系相关接口
  • + *
+ *

如需同时使用多种权限范围(例如:既要操作通讯录,又要调用自建应用接口), + * 可在 {@code wx.cp.corps} 下配置多个条目,每个条目使用对应权限的 {@code corpSecret}, + * 其中通讯录同步的条目无需填写 {@code agentId}。

+ * + * @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(权限密钥) + * + *

企业微信针对不同的功能模块提供了不同的 Secret,每种 Secret 只对对应模块的接口有调用权限:

+ *
    + *
  • 自建应用 Secret:在"应用管理 - 自建应用"中找到对应应用,查看其 Secret, + * 使用时需同时配置对应的 {@code agentId}
  • + *
  • 通讯录同步 Secret:在"管理工具 - 通讯录同步"中查看, + * 使用此 Secret 可管理部门、成员,无需配置 {@code agentId}
  • + *
  • 其他 Secret(客户联系等):根据需要在企业微信后台查看对应 Secret
  • + *
+ */ + private String corpSecret; + /** + * 微信企业号应用 token + */ + private String token; + /** + * 微信企业号应用 ID(AgentId) + * + *

使用自建应用 Secret 时,需要填写对应应用的 AgentId。

+ *

使用通讯录同步 Secret 时,无需填写此字段。

+ */ + 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..9a11e445b7 --- /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.6.B + + 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..94da753457 --- /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.6.B + + 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..3ff7b3ed0b --- /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.6.B + + 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..9d1157e3ff --- /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.6.B + + 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..e90f60b8bd --- /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.6.B + + 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..7121f2d700 --- /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() { + String configuredHost = applicationContext.cfg().get(WxMpProperties.PREFIX + ".config-storage.redis.host"); + RedissonClient redissonClient; + if (StringUtils.isNotEmpty(configuredHost)) { + 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..9e9430ca52 --- /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.6.B + + 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..8ff3416293 --- /dev/null +++ b/solon-plugins/wx-java-pay-solon-plugin/README.md @@ -0,0 +1,45 @@ +# 使用说明 +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 + apiHostUrl: http://10.0.0.1:3128 # 可选:代理主机 + apiHostUrlPath: /api-weixin # 可选:代理入口前缀 + 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..be3a4cff16 --- /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.6.B + + 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..c311a099a2 --- /dev/null +++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java @@ -0,0 +1,70 @@ +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.setApiHostUrlPath(StringUtils.trimToNull(this.properties.getApiHostUrlPath())); + 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..fe024f59f1 --- /dev/null +++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java @@ -0,0 +1,132 @@ +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; + + /** + * 自定义API主机路径前缀(用于代理入口前缀) + * 例如:/api-weixin + */ + private String apiHostUrlPath; + + /** + * 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认添加 + */ + private boolean strictlyNeedWechatPaySerial = true; + + /** + * 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认使用 + */ + private boolean fullPublicKeyModel = true; + +} 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..0502386510 --- /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.6.B + + 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/solon-plugins/wx-java-store-multi-solon-plugin/README.md b/solon-plugins/wx-java-store-multi-solon-plugin/README.md new file mode 100644 index 0000000000..f57390930b --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/README.md @@ -0,0 +1,111 @@ +# wx-java-store-multi-solon-plugin + +## 快速开始 + +1. 引入依赖 + ```xml + + + com.github.binarywang + wx-java-store-multi-solon-plugin + ${version} + + + + + redis.clients + jedis + ${jedis.version} + + + + + org.redisson + redisson + ${redisson.version} + + + ``` +2. 添加配置(app.properties) + ```properties + # 视频号配置 + ## 应用 1 配置(必填) + wx.store.apps.tenantId1.app-id=@appId + wx.store.apps.tenantId1.secret=@secret + ## 选填 + wx.store.apps.tenantId1.use-stable-access-token=false + wx.store.apps.tenantId1.token= + wx.store.apps.tenantId1.aes-key= + ## 应用 2 配置(必填) + wx.store.apps.tenantId2.app-id=@appId + wx.store.apps.tenantId2.secret=@secret + ## 选填 + wx.store.apps.tenantId2.use-stable-access-token=false + wx.store.apps.tenantId2.token= + wx.store.apps.tenantId2.aes-key= + + # ConfigStorage 配置(选填) + ## 配置类型: memory(默认), jedis, redisson, redis_template + wx.store.config-storage.type=memory + ## 相关redis前缀配置: wx:store:multi(默认) + wx.store.config-storage.key-prefix=wx:store:multi + wx.store.config-storage.redis.host=127.0.0.1 + wx.store.config-storage.redis.port=6379 + wx.store.config-storage.redis.password=123456 + + # http 客户端配置(选填) + ## # http客户端类型: http_client(默认) + wx.store.config-storage.http-client-type=http_client + wx.store.config-storage.http-proxy-host= + wx.store.config-storage.http-proxy-port= + wx.store.config-storage.http-proxy-username= + wx.store.config-storage.http-proxy-password= + ## 最大重试次数,默认:5 次,如果小于 0,则为 0 + wx.store.config-storage.max-retry-times=5 + ## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 + wx.store.config-storage.retry-sleep-millis=1000 + ``` +3. 自动注入的类型:`WxStoreMultiServices` + +4. 使用样例 + + ```java + import com.binarywang.solon.wxjava.store.service.WxStoreMultiServices; + import com.binarywang.wxjava.store.api.WxStoreService; + import com.binarywang.wxjava.store.api.WxFinderLiveService; + import com.binarywang.wxjava.store.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 WxStoreMultiServices wxStoreMultiServices; + + public void test() throws WxErrorException { + // 应用 1 的 WxStoreService + WxStoreService wxStoreService1 = wxStoreMultiServices.getWxStoreService("tenantId1"); + WxFinderLiveService finderLiveService = wxStoreService1.getFinderLiveService(); + FinderAttrResponse response1 = finderLiveService.getFinderAttrByAppid(); + // todo ... + + // 应用 2 的 WxStoreService + WxStoreService wxStoreService2 = wxStoreMultiServices.getWxStoreService("tenantId2"); + WxFinderLiveService finderLiveService2 = wxStoreService2.getFinderLiveService(); + FinderAttrResponse response2 = finderLiveService2.getFinderAttrByAppid(); + // todo ... + + // 应用 3 的 WxStoreService + WxStoreService wxStoreService3 = wxStoreMultiServices.getWxStoreService("tenantId3"); + // 判断是否为空 + if (wxStoreService3 == null) { + // todo wxStoreService3 为空,请先配置 tenantId3 微信小店应用参数 + return; + } + WxFinderLiveService finderLiveService3 = wxStoreService3.getFinderLiveService(); + FinderAttrResponse response3 = finderLiveService3.getFinderAttrByAppid(); + // todo ... + } + } + ``` diff --git a/solon-plugins/wx-java-store-multi-solon-plugin/pom.xml b/solon-plugins/wx-java-store-multi-solon-plugin/pom.xml new file mode 100644 index 0000000000..1c17d2e8c0 --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/pom.xml @@ -0,0 +1,43 @@ + + + + wx-java-solon-plugins + com.github.binarywang + 4.8.6.B + + 4.0.0 + + wx-java-store-multi-solon-plugin + WxJava - Solon Plugin for Store::支持多账号配置 + 微信小店开发的 Solon Plugin::支持多账号配置 + + + + com.github.binarywang + weixin-java-store + ${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-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/configuration/services/AbstractWxStoreConfiguration.java b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/configuration/services/AbstractWxStoreConfiguration.java new file mode 100644 index 0000000000..f4e76d8e8e --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/configuration/services/AbstractWxStoreConfiguration.java @@ -0,0 +1,146 @@ +package com.binarywang.solon.wxjava.store.configuration.services; + +import com.binarywang.solon.wxjava.store.enums.HttpClientType; +import com.binarywang.solon.wxjava.store.properties.WxStoreMultiProperties; +import com.binarywang.solon.wxjava.store.properties.WxStoreSingleProperties; +import com.binarywang.solon.wxjava.store.service.WxStoreMultiServices; +import com.binarywang.solon.wxjava.store.service.WxStoreMultiServicesImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import com.binarywang.wxjava.store.api.WxStoreService; +import com.binarywang.wxjava.store.api.impl.WxStoreServiceHttpComponentsImpl; +import com.binarywang.wxjava.store.api.impl.WxStoreServiceHttpClientImpl; +import com.binarywang.wxjava.store.api.impl.WxStoreServiceImpl; +import com.binarywang.wxjava.store.config.WxStoreConfig; +import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * WxStoreConfigStorage 抽象配置类 + * + * @author Winnie 2024/9/13 + * @author noear + */ +@RequiredArgsConstructor +@Slf4j +public abstract class AbstractWxStoreConfiguration { + protected WxStoreMultiServices wxStoreMultiServices(WxStoreMultiProperties wxStoreMultiProperties) { + Map appsMap = wxStoreMultiProperties.getApps(); + if (appsMap == null || appsMap.isEmpty()) { + log.warn("微信小店应用参数未配置,通过 WxStoreMultiServices#getWxStoreService(\"tenantId\")获取实例将返回空"); + return new WxStoreMultiServicesImpl(); + } + /** + * 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。 + * + * 查看 {@link com.binarywang.wxjava.store.config.impl.WxStoreRedisConfigImpl#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 的唯一性"); + } + } + WxStoreMultiServicesImpl services = new WxStoreMultiServicesImpl(); + + Set> entries = appsMap.entrySet(); + for (Map.Entry entry : entries) { + String tenantId = entry.getKey(); + WxStoreSingleProperties wxStoreSingleProperties = entry.getValue(); + WxStoreDefaultConfigImpl storage = this.wxStoreConfigStorage(wxStoreMultiProperties); + this.configApp(storage, wxStoreSingleProperties); + this.configHttp(storage, wxStoreMultiProperties.getConfigStorage()); + WxStoreService wxStoreService = this.wxStoreService(storage, wxStoreMultiProperties); + services.addWxStoreService(tenantId, wxStoreService); + } + return services; + } + + /** + * 配置 WxStoreDefaultConfigImpl + * + * @param wxStoreMultiProperties 参数 + * @return WxStoreDefaultConfigImpl + */ + protected abstract WxStoreDefaultConfigImpl wxStoreConfigStorage(WxStoreMultiProperties wxStoreMultiProperties); + + public WxStoreService wxStoreService(WxStoreConfig wxStoreConfig, WxStoreMultiProperties wxStoreMultiProperties) { + WxStoreMultiProperties.ConfigStorage storage = wxStoreMultiProperties.getConfigStorage(); + HttpClientType httpClientType = storage.getHttpClientType(); + WxStoreService wxStoreService; + switch (httpClientType) { +// case OK_HTTP: +// wxStoreService = new WxStoreServiceOkHttpImpl(false, false); +// break; + case HTTP_CLIENT: + wxStoreService = new WxStoreServiceHttpClientImpl(); + break; + case HTTP_COMPONENTS: + wxStoreService = new WxStoreServiceHttpComponentsImpl(); + break; + default: + wxStoreService = new WxStoreServiceImpl(); + break; + } + + wxStoreService.setConfig(wxStoreConfig); + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxStoreService.setRetrySleepMillis(retrySleepMillis); + wxStoreService.setMaxRetryTimes(maxRetryTimes); + return wxStoreService; + } + + private void configApp(WxStoreDefaultConfigImpl config, WxStoreSingleProperties wxStoreSingleProperties) { + String appId = wxStoreSingleProperties.getAppId(); + String appSecret = wxStoreSingleProperties.getSecret(); + String token = wxStoreSingleProperties.getToken(); + String aesKey = wxStoreSingleProperties.getAesKey(); + boolean useStableAccessToken = wxStoreSingleProperties.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(WxStoreDefaultConfigImpl config, WxStoreMultiProperties.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-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/configuration/services/WxStoreInJedisConfiguration.java b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/configuration/services/WxStoreInJedisConfiguration.java new file mode 100644 index 0000000000..9e0793c2d4 --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/configuration/services/WxStoreInJedisConfiguration.java @@ -0,0 +1,77 @@ +package com.binarywang.solon.wxjava.store.configuration.services; + +import com.binarywang.solon.wxjava.store.properties.WxStoreMultiProperties; +import com.binarywang.solon.wxjava.store.properties.WxStoreMultiRedisProperties; +import com.binarywang.solon.wxjava.store.service.WxStoreMultiServices; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl; +import com.binarywang.wxjava.store.config.impl.WxStoreRedisConfigImpl; +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 = "${"+WxStoreMultiProperties.PREFIX + ".configStorage.type} = jedis", + onClass = JedisPool.class +) +@RequiredArgsConstructor +public class WxStoreInJedisConfiguration extends AbstractWxStoreConfiguration { + private final WxStoreMultiProperties wxStoreMultiProperties; + private final AppContext applicationContext; + + @Bean + public WxStoreMultiServices wxStoreMultiServices() { + return this.wxStoreMultiServices(wxStoreMultiProperties); + } + + @Override + protected WxStoreDefaultConfigImpl wxStoreConfigStorage(WxStoreMultiProperties wxStoreMultiProperties) { + return this.configRedis(wxStoreMultiProperties); + } + + private WxStoreDefaultConfigImpl configRedis(WxStoreMultiProperties wxStoreMultiProperties) { + WxStoreMultiRedisProperties wxStoreMultiRedisProperties = wxStoreMultiProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxStoreMultiRedisProperties != null && StringUtils.isNotEmpty(wxStoreMultiRedisProperties.getHost())) { + jedisPool = getJedisPool(wxStoreMultiProperties); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxStoreRedisConfigImpl(new JedisWxRedisOps(jedisPool), wxStoreMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool(WxStoreMultiProperties wxStoreMultiProperties) { + WxStoreMultiProperties.ConfigStorage storage = wxStoreMultiProperties.getConfigStorage(); + WxStoreMultiRedisProperties 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-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/configuration/services/WxStoreInMemoryConfiguration.java b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/configuration/services/WxStoreInMemoryConfiguration.java new file mode 100644 index 0000000000..64764c22a2 --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/configuration/services/WxStoreInMemoryConfiguration.java @@ -0,0 +1,40 @@ +package com.binarywang.solon.wxjava.store.configuration.services; + +import com.binarywang.solon.wxjava.store.properties.WxStoreMultiProperties; +import com.binarywang.solon.wxjava.store.service.WxStoreMultiServices; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl; +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 = "${"+WxStoreMultiProperties.PREFIX + ".configStorage.type} = memory", + onClass = JedisPool.class +) +@RequiredArgsConstructor +public class WxStoreInMemoryConfiguration extends AbstractWxStoreConfiguration { + private final WxStoreMultiProperties wxStoreMultiProperties; + + @Bean + public WxStoreMultiServices wxStoreMultiServices() { + return this.wxStoreMultiServices(wxStoreMultiProperties); + } + + @Override + protected WxStoreDefaultConfigImpl wxStoreConfigStorage(WxStoreMultiProperties wxStoreMultiProperties) { + return this.configInMemory(); + } + + private WxStoreDefaultConfigImpl configInMemory() { + return new WxStoreDefaultConfigImpl(); + } +} diff --git a/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/configuration/services/WxStoreInRedissonConfiguration.java b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/configuration/services/WxStoreInRedissonConfiguration.java new file mode 100644 index 0000000000..fbb0f46063 --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/configuration/services/WxStoreInRedissonConfiguration.java @@ -0,0 +1,65 @@ +package com.binarywang.solon.wxjava.store.configuration.services; + +import com.binarywang.solon.wxjava.store.properties.WxStoreMultiProperties; +import com.binarywang.solon.wxjava.store.properties.WxStoreMultiRedisProperties; +import com.binarywang.solon.wxjava.store.service.WxStoreMultiServices; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl; +import com.binarywang.wxjava.store.config.impl.WxStoreRedissonConfigImpl; +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 = "${"+WxStoreMultiProperties.PREFIX + ".configStorage.type} = redisson", + onClass = Redisson.class +) +@RequiredArgsConstructor +public class WxStoreInRedissonConfiguration extends AbstractWxStoreConfiguration { + private final WxStoreMultiProperties wxStoreMultiProperties; + private final AppContext applicationContext; + + @Bean + public WxStoreMultiServices wxStoreMultiServices() { + return this.wxStoreMultiServices(wxStoreMultiProperties); + } + + @Override + protected WxStoreDefaultConfigImpl wxStoreConfigStorage(WxStoreMultiProperties wxStoreMultiProperties) { + return this.configRedisson(wxStoreMultiProperties); + } + + private WxStoreDefaultConfigImpl configRedisson(WxStoreMultiProperties wxStoreMultiProperties) { + WxStoreMultiRedisProperties redisProperties = wxStoreMultiProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(wxStoreMultiProperties); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxStoreRedissonConfigImpl(redissonClient, wxStoreMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient(WxStoreMultiProperties wxStoreMultiProperties) { + WxStoreMultiProperties.ConfigStorage storage = wxStoreMultiProperties.getConfigStorage(); + WxStoreMultiRedisProperties 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-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/enums/HttpClientType.java b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/enums/HttpClientType.java new file mode 100644 index 0000000000..eef3e7fc1c --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/enums/HttpClientType.java @@ -0,0 +1,23 @@ +package com.binarywang.solon.wxjava.store.enums; + +/** + * httpclient类型 + * + * @author Winnie + * @date 2024/9/13 + */ +public enum HttpClientType { + /** + * HttpClient + */ + HTTP_CLIENT, + /** + * HttpComponents + */ + HTTP_COMPONENTS + // WxStoreServiceOkHttpImpl 实现经测试无法正常完成业务固暂不支持OK_HTTP方式 +// /** +// * OkHttp. +// */ +// OK_HTTP, +} diff --git a/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/enums/StorageType.java b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/enums/StorageType.java new file mode 100644 index 0000000000..fdb12d124c --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/enums/StorageType.java @@ -0,0 +1,26 @@ +package com.binarywang.solon.wxjava.store.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-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/integration/WxStoreMultiPluginImpl.java b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/integration/WxStoreMultiPluginImpl.java new file mode 100644 index 0000000000..e9890396ec --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/integration/WxStoreMultiPluginImpl.java @@ -0,0 +1,25 @@ +package com.binarywang.solon.wxjava.store.integration; + +import com.binarywang.solon.wxjava.store.configuration.services.WxStoreInJedisConfiguration; +import com.binarywang.solon.wxjava.store.configuration.services.WxStoreInMemoryConfiguration; +import com.binarywang.solon.wxjava.store.configuration.services.WxStoreInRedissonConfiguration; +import com.binarywang.solon.wxjava.store.properties.WxStoreMultiProperties; +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 WxStoreMultiPluginImpl implements Plugin { + @Override + public void start(AppContext context) throws Throwable { + context.beanMake(WxStoreMultiProperties.class); + + context.beanMake(WxStoreInJedisConfiguration.class); + context.beanMake(WxStoreInMemoryConfiguration.class); + context.beanMake(WxStoreInRedissonConfiguration.class); + } +} diff --git a/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/WxStoreMultiProperties.java b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/WxStoreMultiProperties.java new file mode 100644 index 0000000000..794489aef8 --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/WxStoreMultiProperties.java @@ -0,0 +1,96 @@ +package com.binarywang.solon.wxjava.store.properties; + +import com.binarywang.solon.wxjava.store.enums.HttpClientType; +import com.binarywang.solon.wxjava.store.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("${" + WxStoreMultiProperties.PREFIX +"}") +public class WxStoreMultiProperties implements Serializable { + private static final long serialVersionUID = - 8361973118805546037L; + public static final String PREFIX = "wx.store"; + + 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:store:multi"; + + /** + * redis连接配置. + */ + private final WxStoreMultiRedisProperties redis = new WxStoreMultiRedisProperties(); + + /** + * 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 com.binarywang.wxjava.store.api.WxStoreService#setMaxRetryTimes(int)}

+ *

{@link com.binarywang.wxjava.store.api.impl.BaseWxStoreServiceImpl#setMaxRetryTimes(int)}

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

{@link com.binarywang.wxjava.store.api.WxStoreService#setRetrySleepMillis(int)}

+ *

{@link com.binarywang.wxjava.store.api.impl.BaseWxStoreServiceImpl#setRetrySleepMillis(int)}

+ */ + private int retrySleepMillis = 1000; + } +} diff --git a/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/WxStoreMultiRedisProperties.java b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/WxStoreMultiRedisProperties.java new file mode 100644 index 0000000000..1df12ab720 --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/WxStoreMultiRedisProperties.java @@ -0,0 +1,63 @@ +package com.binarywang.solon.wxjava.store.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * Redis配置 + * + * @author Winnie + * @date 2024/9/13 + */ +@Data +@NoArgsConstructor +public class WxStoreMultiRedisProperties 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-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/WxStoreSingleProperties.java b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/WxStoreSingleProperties.java new file mode 100644 index 0000000000..47c31fe886 --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/WxStoreSingleProperties.java @@ -0,0 +1,43 @@ +package com.binarywang.solon.wxjava.store.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 微信小店相关配置属性 + * + * @author Winnie + * @date 2024/9/13 + */ +@Data +@NoArgsConstructor +public class WxStoreSingleProperties 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-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/service/WxStoreMultiServices.java b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/service/WxStoreMultiServices.java new file mode 100644 index 0000000000..f059819909 --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/service/WxStoreMultiServices.java @@ -0,0 +1,26 @@ +package com.binarywang.solon.wxjava.store.service; + +import com.binarywang.wxjava.store.api.WxStoreService; + +/** + * 视频号 {@link WxStoreService} 所有实例存放类. + * + * @author Winnie + * @date 2024/9/13 + */ +public interface WxStoreMultiServices { + /** + * 通过租户 Id 获取 WxStoreService + * + * @param tenantId 租户 Id + * @return WxStoreService + */ + WxStoreService getWxStoreService(String tenantId); + + /** + * 根据租户 Id,从列表中移除一个 WxStoreService 实例 + * + * @param tenantId 租户 Id + */ + void removeWxStoreService(String tenantId); +} diff --git a/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/service/WxStoreMultiServicesImpl.java b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/service/WxStoreMultiServicesImpl.java new file mode 100644 index 0000000000..daa6cb1305 --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/service/WxStoreMultiServicesImpl.java @@ -0,0 +1,36 @@ +package com.binarywang.solon.wxjava.store.service; + +import com.binarywang.wxjava.store.api.WxStoreService; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 视频号 {@link WxStoreMultiServices} 实现 + * + * @author Winnie + * @date 2024/9/13 + */ +public class WxStoreMultiServicesImpl implements WxStoreMultiServices { + private final Map services = new ConcurrentHashMap<>(); + + @Override + public WxStoreService getWxStoreService(String tenantId) { + return this.services.get(tenantId); + } + + /** + * 根据租户 Id,添加一个 WxStoreService 到列表 + * + * @param tenantId 租户 Id + * @param wxStoreService WxStoreService 实例 + */ + public void addWxStoreService(String tenantId, WxStoreService wxStoreService) { + this.services.put(tenantId, wxStoreService); + } + + @Override + public void removeWxStoreService(String tenantId) { + this.services.remove(tenantId); + } +} diff --git a/solon-plugins/wx-java-store-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-multi-store-solon-plugin.properties b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-multi-store-solon-plugin.properties new file mode 100644 index 0000000000..bee1c44b01 --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/main/resources/META-INF/solon/wx-java-multi-store-solon-plugin.properties @@ -0,0 +1,2 @@ +solon.plugin=com.binarywang.solon.wxjava.store.integration.WxStoreMultiPluginImpl +solon.plugin.priority=10 diff --git a/solon-plugins/wx-java-store-multi-solon-plugin/src/test/java/features/test/LoadTest.java b/solon-plugins/wx-java-store-multi-solon-plugin/src/test/java/features/test/LoadTest.java new file mode 100644 index 0000000000..d049f5a51a --- /dev/null +++ b/solon-plugins/wx-java-store-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-store-multi-solon-plugin/src/test/resources/app.properties b/solon-plugins/wx-java-store-multi-solon-plugin/src/test/resources/app.properties new file mode 100644 index 0000000000..f55dafd6db --- /dev/null +++ b/solon-plugins/wx-java-store-multi-solon-plugin/src/test/resources/app.properties @@ -0,0 +1,36 @@ +# 视频号配置 +## 应用 1 配置(必填) +wx.store.apps.tenantId1.app-id=appId +wx.store.apps.tenantId1.secret=secret +## 选填 +wx.store.apps.tenantId1.use-stable-access-token=false +wx.store.apps.tenantId1.token= +wx.store.apps.tenantId1.aes-key= +## 应用 2 配置(必填) +wx.store.apps.tenantId2.app-id=@appId +wx.store.apps.tenantId2.secret=@secret +## 选填 +wx.store.apps.tenantId2.use-stable-access-token=false +wx.store.apps.tenantId2.token= +wx.store.apps.tenantId2.aes-key= + +# ConfigStorage 配置(选填) +## 配置类型: memory(默认), jedis, redisson, redis_template +wx.store.config-storage.type=memory +## 相关redis前缀配置: wx:store:multi(默认) +wx.store.config-storage.key-prefix=wx:store:multi +wx.store.config-storage.redis.host=127.0.0.1 +wx.store.config-storage.redis.port=6379 +wx.store.config-storage.redis.password=123456 + +# http 客户端配置(选填) +## # http客户端类型: http_client(默认) +wx.store.config-storage.http-client-type=http_client +wx.store.config-storage.http-proxy-host= +wx.store.config-storage.http-proxy-port= +wx.store.config-storage.http-proxy-username= +wx.store.config-storage.http-proxy-password= +## 最大重试次数,默认:5 次,如果小于 0,则为 0 +wx.store.config-storage.max-retry-times=5 +## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 +wx.store.config-storage.retry-sleep-millis=1000 diff --git a/solon-plugins/wx-java-store-solon-plugin/README.md b/solon-plugins/wx-java-store-solon-plugin/README.md new file mode 100644 index 0000000000..c321c9274b --- /dev/null +++ b/solon-plugins/wx-java-store-solon-plugin/README.md @@ -0,0 +1,91 @@ +# wx-java-store-solon-plugin + +## 快速开始 +1. 引入依赖 + ```xml + + + com.github.binarywang + wx-java-store-solon-plugin + ${version} + + + + + redis.clients + jedis + ${jedis.version} + + + + + org.redisson + redisson + ${redisson.version} + + + ``` +2. 添加配置(app.properties) + ```properties + # 视频号配置(必填) + ## 微信小店的appId和secret + wx.store.app-id=@appId + wx.store.secret=@secret + # 视频号配置 选填 + ## 设置微信小店消息服务器配置的token + wx.store.token=@token + ## 设置微信小店消息服务器配置的EncodingAESKey + wx.store.aes-key= + ## 支持JSON或者XML格式,默认JSON + wx.store.msg-data-format=JSON + ## 是否使用稳定版 Access Token + wx.store.use-stable-access-token=false + + + # ConfigStorage 配置(选填) + ## 配置类型: memory(默认), jedis, redisson, redis_template + wx.store.config-storage.type=memory + ## 相关redis前缀配置: wx:store(默认) + wx.store.config-storage.key-prefix=wx:store + wx.store.config-storage.redis.host=127.0.0.1 + wx.store.config-storage.redis.port=6379 + wx.store.config-storage.redis.password=123456 + + + # http 客户端配置(选填) + ## # http客户端类型: http_client(默认) + wx.store.config-storage.http-client-type=http_client + wx.store.config-storage.http-proxy-host= + wx.store.config-storage.http-proxy-port= + wx.store.config-storage.http-proxy-username= + wx.store.config-storage.http-proxy-password= + ## 最大重试次数,默认:5 次,如果小于 0,则为 0 + wx.store.config-storage.max-retry-times=5 + ## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 + wx.store.config-storage.retry-sleep-millis=1000 + ``` +3. 自动注入的类型 +- `WxStoreService` +- `WxStoreConfig` +4. 使用样例 + +```java +import com.binarywang.wxjava.store.api.WxStoreService; +import com.binarywang.wxjava.store.bean.shop.ShopInfoResponse; +import com.binarywang.wxjava.store.util.JsonUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import org.noear.solon.annotation.Inject; + +@Component +public class DemoService { + @Inject + private WxStoreService wxStoreService; + + public String getShopInfo() throws WxErrorException { + // 获取店铺基本信息 + ShopInfoResponse response = wxStoreService.getBasicService().getShopInfo(); + // 此处为演示,如果要返回response的结果,建议自己封装一个VO,避免直接返回response + return JsonUtils.encode(response); + } +} +``` diff --git a/solon-plugins/wx-java-store-solon-plugin/pom.xml b/solon-plugins/wx-java-store-solon-plugin/pom.xml new file mode 100644 index 0000000000..d516f29789 --- /dev/null +++ b/solon-plugins/wx-java-store-solon-plugin/pom.xml @@ -0,0 +1,31 @@ + + + wx-java-solon-plugins + com.github.binarywang + 4.8.6.B + + 4.0.0 + + wx-java-store-solon-plugin + WxJava - Solon Plugin for Store + 微信小店开发的 Solon Plugin + + + + com.github.binarywang + weixin-java-store + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + diff --git a/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/WxStoreServiceAutoConfiguration.java b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/WxStoreServiceAutoConfiguration.java new file mode 100644 index 0000000000..69639a8dca --- /dev/null +++ b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/WxStoreServiceAutoConfiguration.java @@ -0,0 +1,39 @@ +package com.binarywang.solon.wxjava.store.config; + + +import com.binarywang.solon.wxjava.store.properties.WxStoreProperties; +import com.binarywang.solon.wxjava.store.enums.HttpClientType; +import lombok.AllArgsConstructor; +import com.binarywang.wxjava.store.api.WxStoreService; +import com.binarywang.wxjava.store.api.impl.WxStoreServiceHttpClientImpl; +import com.binarywang.wxjava.store.api.impl.WxStoreServiceHttpComponentsImpl; +import com.binarywang.wxjava.store.config.WxStoreConfig; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * 微信小程序平台相关服务自动注册 + * + * @author Zeyes + */ +@Configuration +@AllArgsConstructor +public class WxStoreServiceAutoConfiguration { + private final WxStoreProperties properties; + + /** + * Store Service + * + * @return Store Service + */ + @Bean + @Condition(onMissingBean=WxStoreService.class, onBean = WxStoreConfig.class) + public WxStoreService wxStoreService(WxStoreConfig wxStoreConfig) { + HttpClientType httpClientType = properties.getConfigStorage().getHttpClientType(); + WxStoreService wxStoreService = httpClientType == HttpClientType.HttpClient + ? new WxStoreServiceHttpClientImpl() : new WxStoreServiceHttpComponentsImpl(); + wxStoreService.setConfig(wxStoreConfig); + return wxStoreService; + } +} diff --git a/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/storage/AbstractWxStoreConfigStorageConfiguration.java b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/storage/AbstractWxStoreConfigStorageConfiguration.java new file mode 100644 index 0000000000..aed018d72a --- /dev/null +++ b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/storage/AbstractWxStoreConfigStorageConfiguration.java @@ -0,0 +1,40 @@ +package com.binarywang.solon.wxjava.store.config.storage; + +import com.binarywang.solon.wxjava.store.properties.WxStoreProperties; +import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +/** + * @author Zeyes + */ +public abstract class AbstractWxStoreConfigStorageConfiguration { + + protected WxStoreDefaultConfigImpl config(WxStoreDefaultConfigImpl config, WxStoreProperties 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()); + + WxStoreProperties.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-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/storage/WxStoreInJedisConfigStorageConfiguration.java b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/storage/WxStoreInJedisConfigStorageConfiguration.java new file mode 100644 index 0000000000..054dd54bdd --- /dev/null +++ b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/storage/WxStoreInJedisConfigStorageConfiguration.java @@ -0,0 +1,74 @@ +package com.binarywang.solon.wxjava.store.config.storage; + + +import com.binarywang.solon.wxjava.store.properties.RedisProperties; +import com.binarywang.solon.wxjava.store.properties.WxStoreProperties; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.WxStoreConfig; +import com.binarywang.wxjava.store.config.impl.WxStoreRedisConfigImpl; +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 = "${"+WxStoreProperties.PREFIX + ".configStorage.type} = jedis", + onClass = JedisPool.class +) +@RequiredArgsConstructor +public class WxStoreInJedisConfigStorageConfiguration extends AbstractWxStoreConfigStorageConfiguration { + private final WxStoreProperties properties; + private final AppContext applicationContext; + + @Bean + @Condition(onMissingBean=WxStoreConfig.class) + public WxStoreConfig wxStoreConfig() { + WxStoreRedisConfigImpl config = getWxStoreRedisConfig(); + return this.config(config, properties); + } + + private WxStoreRedisConfigImpl getWxStoreRedisConfig() { + 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 WxStoreRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool() { + WxStoreProperties.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-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/storage/WxStoreInMemoryConfigStorageConfiguration.java b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/storage/WxStoreInMemoryConfigStorageConfiguration.java new file mode 100644 index 0000000000..eaf95f213f --- /dev/null +++ b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/storage/WxStoreInMemoryConfigStorageConfiguration.java @@ -0,0 +1,29 @@ +package com.binarywang.solon.wxjava.store.config.storage; + + +import com.binarywang.solon.wxjava.store.properties.WxStoreProperties; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.WxStoreConfig; +import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Condition; +import org.noear.solon.annotation.Configuration; + +/** + * @author Zeyes + */ +@Configuration +@Condition( + onProperty = "${"+WxStoreProperties.PREFIX + ".configStorage.type:memory} = memory" +) +@RequiredArgsConstructor +public class WxStoreInMemoryConfigStorageConfiguration extends AbstractWxStoreConfigStorageConfiguration { + private final WxStoreProperties properties; + + @Bean + @Condition(onMissingBean = WxStoreConfig.class) + public WxStoreConfig wxStoreConfig() { + WxStoreDefaultConfigImpl config = new WxStoreDefaultConfigImpl(); + return this.config(config, properties); + } +} diff --git a/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/storage/WxStoreInRedissonConfigStorageConfiguration.java b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/storage/WxStoreInRedissonConfigStorageConfiguration.java new file mode 100644 index 0000000000..2558aafa8a --- /dev/null +++ b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/config/storage/WxStoreInRedissonConfigStorageConfiguration.java @@ -0,0 +1,62 @@ +package com.binarywang.solon.wxjava.store.config.storage; + + +import com.binarywang.solon.wxjava.store.properties.RedisProperties; +import com.binarywang.solon.wxjava.store.properties.WxStoreProperties; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.WxStoreConfig; +import com.binarywang.wxjava.store.config.impl.WxStoreRedissonConfigImpl; +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 = "${"+WxStoreProperties.PREFIX + ".configStorage.type} = redisson", + onClass = Redisson.class +) +@RequiredArgsConstructor +public class WxStoreInRedissonConfigStorageConfiguration extends AbstractWxStoreConfigStorageConfiguration { + private final WxStoreProperties properties; + private final AppContext applicationContext; + + @Bean + @Condition(onMissingBean=WxStoreConfig.class) + public WxStoreConfig wxStoreConfig() { + WxStoreRedissonConfigImpl config = getWxStoreRedissonConfig(); + return this.config(config, properties); + } + + private WxStoreRedissonConfigImpl getWxStoreRedissonConfig() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxStoreRedissonConfigImpl(redissonClient, properties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient() { + WxStoreProperties.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-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/enums/HttpClientType.java b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/enums/HttpClientType.java new file mode 100644 index 0000000000..7a5975a37a --- /dev/null +++ b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/enums/HttpClientType.java @@ -0,0 +1,17 @@ +package com.binarywang.solon.wxjava.store.enums; + +/** + * httpclient类型 + * + * @author Zeyes + */ +public enum HttpClientType { + /** + * HttpClient. + */ + HttpClient, + /** + * HttpComponents. + */ + HttpComponents, +} diff --git a/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/enums/StorageType.java b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/enums/StorageType.java new file mode 100644 index 0000000000..658f8279d9 --- /dev/null +++ b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/enums/StorageType.java @@ -0,0 +1,25 @@ +package com.binarywang.solon.wxjava.store.enums; + +/** + * storage类型 + * + * @author Zeyes + */ +public enum StorageType { + /** + * 内存 + */ + Memory, + /** + * redis(JedisClient) + */ + Jedis, + /** + * redis(Redisson) + */ + Redisson, + /** + * redis(RedisTemplate) + */ + RedisTemplate +} diff --git a/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/integration/WxStorePluginImpl.java b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/integration/WxStorePluginImpl.java new file mode 100644 index 0000000000..f8dbb12c39 --- /dev/null +++ b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/integration/WxStorePluginImpl.java @@ -0,0 +1,25 @@ +package com.binarywang.solon.wxjava.store.integration; + + +import com.binarywang.solon.wxjava.store.config.WxStoreServiceAutoConfiguration; +import com.binarywang.solon.wxjava.store.config.storage.WxStoreInJedisConfigStorageConfiguration; +import com.binarywang.solon.wxjava.store.config.storage.WxStoreInMemoryConfigStorageConfiguration; +import com.binarywang.solon.wxjava.store.config.storage.WxStoreInRedissonConfigStorageConfiguration; +import com.binarywang.solon.wxjava.store.properties.WxStoreProperties; +import org.noear.solon.core.AppContext; +import org.noear.solon.core.Plugin; + +/** + * @author noear 2024/9/2 created + */ +public class WxStorePluginImpl implements Plugin { + @Override + public void start(AppContext context) throws Throwable { + context.beanMake(WxStoreProperties.class); + context.beanMake(WxStoreServiceAutoConfiguration.class); + + context.beanMake(WxStoreInMemoryConfigStorageConfiguration.class); + context.beanMake(WxStoreInJedisConfigStorageConfiguration.class); + context.beanMake(WxStoreInRedissonConfigStorageConfiguration.class); + } +} diff --git a/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/RedisProperties.java b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/RedisProperties.java new file mode 100644 index 0000000000..5379b1ec26 --- /dev/null +++ b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/RedisProperties.java @@ -0,0 +1,42 @@ +package com.binarywang.solon.wxjava.store.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-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/WxStoreProperties.java b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/WxStoreProperties.java new file mode 100644 index 0000000000..3575b3ac9f --- /dev/null +++ b/solon-plugins/wx-java-store-solon-plugin/src/main/java/com/binarywang/solon/wxjava/store/properties/WxStoreProperties.java @@ -0,0 +1,114 @@ +package com.binarywang.solon.wxjava.store.properties; + +import com.binarywang.solon.wxjava.store.enums.HttpClientType; +import com.binarywang.solon.wxjava.store.enums.StorageType; +import lombok.Data; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; + +/** + * 属性配置类 + * + * @author Zeyes + */ +@Data +@Configuration +@Inject("${" + WxStoreProperties.PREFIX +"}") +public class WxStoreProperties { + public static final String PREFIX = "wx.store"; + + /** + * 设置微信小店的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 com.binarywang.wxjava.store.api.BaseWxStoreService#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + /** + * http 请求最大重试次数 + *
+     *   {@link com.binarywang.wxjava.store.api.BaseWxStoreService#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + } + +} diff --git a/solon-plugins/wx-java-store-solon-plugin/src/main/resources/META-INF/solon/wx-java-store-solon-plugin.properties b/solon-plugins/wx-java-store-solon-plugin/src/main/resources/META-INF/solon/wx-java-store-solon-plugin.properties new file mode 100644 index 0000000000..fd2f516d62 --- /dev/null +++ b/solon-plugins/wx-java-store-solon-plugin/src/main/resources/META-INF/solon/wx-java-store-solon-plugin.properties @@ -0,0 +1,2 @@ +solon.plugin=com.binarywang.solon.wxjava.store.integration.WxStorePluginImpl +solon.plugin.priority=10 diff --git a/solon-plugins/wx-java-store-solon-plugin/src/test/java/features/test/LoadTest.java b/solon-plugins/wx-java-store-solon-plugin/src/test/java/features/test/LoadTest.java new file mode 100644 index 0000000000..d049f5a51a --- /dev/null +++ b/solon-plugins/wx-java-store-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-store-solon-plugin/src/test/resources/app.yml b/solon-plugins/wx-java-store-solon-plugin/src/test/resources/app.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot-starters/MULTI_TENANT_MODE_IMPROVEMENT.md b/spring-boot-starters/MULTI_TENANT_MODE_IMPROVEMENT.md new file mode 100644 index 0000000000..6581f6207d --- /dev/null +++ b/spring-boot-starters/MULTI_TENANT_MODE_IMPROVEMENT.md @@ -0,0 +1,160 @@ +# 多租户模式配置改进说明 + +## 问题背景 + +用户在 issue #3835 中提出了一个架构设计问题: + +> 基础 Wx 实现类中已经有 configMap 了,可以用 configMap 来存储不同的小程序配置。不同的配置,都是复用同一个 http 客户端。为什么在各个 spring-boot-starter 中又单独创建类来存储不同的配置?从 spring 的配置来看,http 客户端只有一个,不同小程序配置可以实现多租户,所以似乎没必要单独再建新类存放?重复创建,增加了 http 客户端的成本?直接使用 Wx 实现类中已经有 configMap 不是更好吗? + +## 解决方案 + +从 4.8.0 版本开始,我们为多租户 Spring Boot Starter 提供了**两种实现模式**供用户选择: + +### 1. 隔离模式(ISOLATED,默认) + +**实现方式**:为每个租户创建独立的 WxService 实例,每个实例拥有独立的 HTTP 客户端。 + +**优点**: +- ✅ 线程安全,无需担心并发问题 +- ✅ 不依赖 ThreadLocal,适合异步/响应式编程 +- ✅ 租户间完全隔离,互不影响 + +**缺点**: +- ❌ 每个租户创建独立的 HTTP 客户端,资源占用较多 +- ❌ 适合租户数量不多的场景(建议 < 50 个租户) + +**代码实现**:`WxMaMultiServicesImpl`, `WxMpMultiServicesImpl` 等 + +### 2. 共享模式(SHARED,新增) + +**实现方式**:使用单个 WxService 实例管理所有租户配置,通过 ThreadLocal 切换租户,所有租户共享同一个 HTTP 客户端。 + +**优点**: +- ✅ 共享 HTTP 客户端,大幅节省资源 +- ✅ 适合租户数量较多的场景(支持 100+ 租户) +- ✅ 内存占用更小 + +**缺点**: +- ❌ 依赖 ThreadLocal 切换配置,在异步场景需要特别注意 +- ❌ 需要注意线程上下文传递 + +**代码实现**:`WxMaMultiServicesSharedImpl`, `WxMpMultiServicesSharedImpl` 等 + +## 使用方式 + +### 配置示例 + +```yaml +wx: + ma: # 或 mp, cp, channel + apps: + tenant1: + app-id: wxd898fcb01713c555 + app-secret: 47a2422a5d04a27e2b3ed1f1f0b0dbad + tenant2: + app-id: wx1234567890abcdef + app-secret: 1234567890abcdef1234567890abcdef + + config-storage: + type: memory + http-client-type: http_client + # 多租户模式配置(新增) + multi-tenant-mode: shared # isolated(默认)或 shared +``` + +### 代码使用(两种模式代码完全相同) + +```java +@RestController +public class WxController { + @Autowired + private WxMaMultiServices wxMaMultiServices; // 或 WxMpMultiServices + + @GetMapping("/api/{tenantId}") + public String handle(@PathVariable String tenantId) { + WxMaService wxService = wxMaMultiServices.getWxMaService(tenantId); + // 使用 wxService 调用微信 API + return wxService.getAccessToken(); + } +} +``` + +## 性能对比 + +以 100 个租户为例: + +| 指标 | 隔离模式 | 共享模式 | +|------|---------|---------| +| HTTP 客户端数量 | 100 个 | 1 个 | +| 内存占用(估算) | ~500MB | ~50MB | +| 线程安全 | ✅ 完全安全 | ⚠️ 需注意异步场景 | +| 性能 | 略高(无 ThreadLocal 切换) | 略低(有 ThreadLocal 切换) | +| 适用场景 | 中小规模 | 大规模 | + +## 支持的模块 + +目前已实现共享模式支持的模块: + +- ✅ **小程序(MiniApp)**:`wx-java-miniapp-multi-spring-boot-starter` +- ✅ **公众号(MP)**:`wx-java-mp-multi-spring-boot-starter` + +后续版本将支持: +- ⏳ 企业微信(CP) +- ⏳ 视频号(Channel) +- ⏳ 企业微信第三方应用(CP-TP) + +## 迁移指南 + +### 从旧版本升级 + +升级到 4.8.0+ 后: + +1. **默认行为不变**:如果不配置 `multi-tenant-mode`,将继续使用隔离模式(与旧版本行为一致) +2. **向后兼容**:所有现有代码无需修改 +3. **可选升级**:如需节省资源,可配置 `multi-tenant-mode: shared` 启用共享模式 + +### 选择建议 + +**使用隔离模式(ISOLATED)的场景**: +- 租户数量较少(< 50 个) +- 使用异步编程、响应式编程 +- 对线程安全有严格要求 +- 对资源占用不敏感 + +**使用共享模式(SHARED)的场景**: +- 租户数量较多(> 50 个) +- 同步编程场景 +- 对资源占用敏感 +- 可以接受 ThreadLocal 的约束 + +## 注意事项 + +### 共享模式下的异步编程 + +如果使用共享模式,在异步编程时需要注意 ThreadLocal 的传递: + +```java +// ❌ 错误:异步线程无法获取到正确的配置 +CompletableFuture.runAsync(() -> { + wxService.getUserService().getUserInfo(...); // 可能使用错误的租户配置 +}); + +// ✅ 正确:在主线程获取必要信息,传递给异步线程 +String appId = wxService.getWxMaConfig().getAppid(); +CompletableFuture.runAsync(() -> { + log.info("AppId: {}", appId); // 使用已获取的配置信息 +}); +``` + +## 详细文档 + +- 小程序模块详细说明:[spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/MULTI_TENANT_MODE.md](spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/MULTI_TENANT_MODE.md) + +## 相关链接 + +- Issue: [#3835](https://github.com/binarywang/WxJava/issues/3835) +- Pull Request: [#3840](https://github.com/binarywang/WxJava/pull/3840) + +## 致谢 + +感谢 issue 提出者对项目架构的深入思考和建议,这帮助我们提供了更灵活、更高效的多租户解决方案。 diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index 6fce17485a..24c45074bb 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -1,10 +1,12 @@ - + 4.0.0 com.github.binarywang wx-java - 4.5.3.B + 4.8.6.B pom wx-java-spring-boot-starters @@ -16,13 +18,22 @@ + 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-pay-multi-spring-boot-starter + wx-java-open-multi-spring-boot-starter wx-java-open-spring-boot-starter wx-java-qidian-spring-boot-starter + wx-java-cp-multi-spring-boot-starter + 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 + wx-java-store-spring-boot-starter + wx-java-store-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..175e183171 --- /dev/null +++ b/spring-boot-starters/wx-java-channel-multi-spring-boot-starter/pom.xml @@ -0,0 +1,73 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.6.B + + 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 + ${spring-data-redis.version} + true + 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 index bb2e8260a2..5a3f08f01d 100644 --- a/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/pom.xml @@ -3,7 +3,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.5.3.B + 4.8.6.B 4.0.0 @@ -30,6 +30,8 @@ org.springframework.data spring-data-redis + ${spring-data-redis.version} + true provided
diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/AbstractWxChannelConfigStorageConfiguration.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/AbstractWxChannelConfigStorageConfiguration.java index a87028a1cd..2a7978640d 100644 --- a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/AbstractWxChannelConfigStorageConfiguration.java +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/config/storage/AbstractWxChannelConfigStorageConfiguration.java @@ -15,6 +15,9 @@ protected WxChannelDefaultConfigImpl config(WxChannelDefaultConfigImpl config, W 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()); diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java index 63a7bf0c24..e4b3f3ad16 100644 --- a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/enums/HttpClientType.java @@ -7,7 +7,11 @@ */ public enum HttpClientType { /** - * HttpClient + * HttpClient. */ - HttpClient + HttpClient, + /** + * HttpComponents. + */ + HttpComponents, } diff --git a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelProperties.java b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelProperties.java index 98f1f3b723..f43d297e0b 100644 --- a/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelProperties.java +++ b/spring-boot-starters/wx-java-channel-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/channel/properties/WxChannelProperties.java @@ -41,6 +41,23 @@ public class WxChannelProperties { */ 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; + /** * 存储策略 */ @@ -68,7 +85,7 @@ public static class ConfigStorage { /** * http客户端类型 */ - private HttpClientType httpClientType = HttpClientType.HttpClient; + private HttpClientType httpClientType = HttpClientType.HttpComponents; /** * http代理主机 diff --git a/spring-boot-starters/wx-java-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..0f0b74695e --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/README.md @@ -0,0 +1,112 @@ +# wx-java-cp-multi-spring-boot-starter + +企业微信多账号配置 + +- 实现多 WxCpService 初始化。 +- 未实现 WxCpTpService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。 +- 未实现 WxCpCgService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。 + +## 关于 corp-secret 的说明 + +企业微信中不同功能模块对应不同的 `corp-secret`,每种 Secret 只对对应模块的接口具有调用权限: + +| Secret 类型 | 获取位置 | 可调用的接口 | 是否需要 agent-id | +|---|---|---|---| +| 自建应用 Secret | 应用管理 → 自建应用 → 选择应用 → 查看 Secret | 该应用有权限的接口 | **必填** | +| 通讯录同步 Secret | 管理工具 → 通讯录同步 → 查看 Secret | 部门/成员增删改查等通讯录接口 | **不填** | +| 客户联系 Secret | 客户联系 → API → Secret | 客户联系相关接口 | 不填 | + +> **常见问题**: +> - 使用自建应用 Secret + agent-id 可以获取部门列表,但**无法更新部门**(因为写接口需要通讯录同步权限) +> - 使用通讯录同步 Secret 可以同步部门,但**调用某些需要 agent-id 的应用接口会报错** + +如需同时使用多种权限范围,可在 `wx.cp.corps` 下配置多个条目,每个条目使用对应权限的 Secret,通过不同的 `tenantId` 区分后使用。 + +> **配置限制说明**: +> - 当前 starter 实现会校验:同一 `corp-id` 下,`agent-id` **必须唯一** +> - 同一 `corp-id` 下,**只能有一个条目不填 `agent-id`** +> - 否则会因为 token/ticket 缓存 key 冲突而在启动时直接抛异常 +> +> 因此,像"通讯录同步 Secret""客户联系 Secret"这类通常不填写 `agent-id` 的配置,**不能**在同一个 `corp-id` 下同时配置多个 `agent-id` 均为空的条目;如确有多个条目,请确保其中最多只有一个未填写 `agent-id`。 + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-cp-multi-spring-boot-starter + ${version} + + ``` +2. 添加配置(application.properties) + ```properties + # 自建应用 1 配置(使用自建应用 Secret,需填写 agent-id) + wx.cp.corps.app1.corp-id = @corp-id + wx.cp.corps.app1.corp-secret = @自建应用的Secret(在"应用管理-自建应用"中查看) + wx.cp.corps.app1.agent-id = @自建应用的AgentId + ## 选填 + wx.cp.corps.app1.token = @token + wx.cp.corps.app1.aes-key = @aes-key + wx.cp.corps.app1.msg-audit-priKey = @msg-audit-priKey + wx.cp.corps.app1.msg-audit-lib-path = @msg-audit-lib-path + + # 通讯录同步配置(使用通讯录同步 Secret,不需要填写 agent-id) + # 此配置用于部门、成员的增删改查等通讯录管理操作 + wx.cp.corps.contact.corp-id = @corp-id + wx.cp.corps.contact.corp-secret = @通讯录同步的Secret(在"管理工具-通讯录同步"中查看) + ## agent-id 不填,通讯录同步不需要 agentId + + # 公共配置 + ## 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.WxCpDepartmentService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.api.WxCpUserService; +import me.chanjar.weixin.cp.bean.WxCpDepart; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class DemoService { + @Autowired + private WxCpMultiServices wxCpMultiServices; + + public void test() { + // 使用自建应用的 WxCpService(对应 corp-secret 为自建应用 Secret) + WxCpService appService = wxCpMultiServices.getWxCpService("app1"); + WxCpUserService userService = appService.getUserService(); + userService.getUserId("xxx"); + // todo ... + + // 使用通讯录同步的 WxCpService(对应 corp-secret 为通讯录同步 Secret) + // 通讯录同步 Secret 具有部门/成员增删改查等权限 + WxCpService contactService = wxCpMultiServices.getWxCpService("contact"); + WxCpDepartmentService departmentService = contactService.getDepartmentService(); + // 更新部门示例(WxCpDepart 包含 id、name、parentId 等字段) + WxCpDepart depart = new WxCpDepart(); + depart.setId(100L); + depart.setName("新部门名称"); + departmentService.update(depart); + // 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..1b5d8971bc --- /dev/null +++ b/spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml @@ -0,0 +1,62 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.6.B + + 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 + ${spring-data-redis.version} + true + 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..a793a8501f --- /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,173 @@ +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.Objects; +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 时错乱。 + * + *

同一企业(corpId 相同)下可配置多个条目以使用不同的权限 Secret,例如:

+ *
    + *
  • 自建应用条目:填写应用对应的 corpSecret 和 agentId
  • + *
  • 通讯录同步条目:填写通讯录同步 Secret,agentId 可不填(null)
  • + *
+ *

但同一 corpId 下不允许出现重复的 agentId(包括多个 null)。

+ * + * 查看 {@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,使用字符串转换避免 null 与 agentId=0 冲突 + .collect(Collectors.groupingBy(c -> Objects.toString(c.getAgentId(), "null"), 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(); + Long 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..740f5a2a3b --- /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,74 @@ +package com.binarywang.spring.starter.wxjava.cp.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 企业微信企业相关配置属性 + * + *

企业微信中不同的 corpSecret 对应不同的权限范围,常见的有:

+ *
    + *
  • 自建应用 Secret:在"应用管理 - 自建应用"中查看,只能调用该应用有权限的接口
  • + *
  • 通讯录同步 Secret:在"管理工具 - 通讯录同步"中查看,用于管理部门和成员(增删改查)
  • + *
  • 客户联系 Secret:在"客户联系"中查看,用于客户联系相关接口
  • + *
+ *

如需同时使用多种权限范围(例如:既要操作通讯录,又要调用自建应用接口), + * 可在 {@code wx.cp.corps} 下配置多个条目,每个条目使用对应权限的 {@code corpSecret}, + * 其中通讯录同步的条目无需填写 {@code agentId}。

+ * + * @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(权限密钥) + * + *

企业微信针对不同的功能模块提供了不同的 Secret,每种 Secret 只对对应模块的接口有调用权限:

+ *
    + *
  • 自建应用 Secret:在"应用管理 - 自建应用"中找到对应应用,查看其 Secret, + * 使用时需同时配置对应的 {@code agentId}
  • + *
  • 通讯录同步 Secret:在"管理工具 - 通讯录同步"中查看, + * 使用此 Secret 可管理部门、成员,无需配置 {@code agentId}
  • + *
  • 其他 Secret(客户联系等):根据需要在企业微信后台查看对应 Secret
  • + *
+ */ + private String corpSecret; + /** + * 微信企业号应用 token + */ + private String token; + /** + * 微信企业号应用 ID(AgentId) + * + *

使用自建应用 Secret 时,需要填写对应应用的 AgentId。

+ *

使用通讯录同步 Secret 时,无需填写此字段。

+ */ + private Long 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 index a10ef65bcb..d6c1abc945 100644 --- a/spring-boot-starters/wx-java-cp-spring-boot-starter/README.md +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/README.md @@ -16,9 +16,11 @@ 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.agent-id = @agent-id + 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 客户端配置(选填) diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml index ff3c399285..08d352231c 100644 --- a/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/pom.xml @@ -4,7 +4,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.5.3.B + 4.8.6.B 4.0.0 @@ -29,6 +29,9 @@ org.springframework.data spring-data-redis + ${spring-data-redis.version} + true + provided diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpProperties.java b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpProperties.java index b87ddc2454..63e730d1b4 100644 --- a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpProperties.java +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpProperties.java @@ -34,7 +34,7 @@ public class WxCpProperties { /** * 微信企业号应用 ID */ - private Integer agentId; + private Long agentId; /** * 微信企业号应用 EncodingAESKey */ @@ -48,6 +48,12 @@ public class WxCpProperties { */ private String msgAuditLibPath; + /** + * 自定义企业微信服务器baseUrl,用于替换默认的 https://qyapi.weixin.qq.com + * 例如:http://proxy.company.com:8080 + */ + private String baseApiUrl; + /** * 配置存储策略,默认内存 */ diff --git a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/AbstractWxCpConfigStorageConfiguration.java b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/AbstractWxCpConfigStorageConfiguration.java index 0f2995e967..70f620a58d 100644 --- a/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/AbstractWxCpConfigStorageConfiguration.java +++ b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/AbstractWxCpConfigStorageConfiguration.java @@ -15,7 +15,7 @@ public abstract class AbstractWxCpConfigStorageConfiguration { protected WxCpDefaultConfigImpl config(WxCpDefaultConfigImpl config, WxCpProperties properties) { String corpId = properties.getCorpId(); String corpSecret = properties.getCorpSecret(); - Integer agentId = properties.getAgentId(); + Long agentId = properties.getAgentId(); String token = properties.getToken(); String aesKey = properties.getAesKey(); // 企业微信,私钥,会话存档路径 @@ -37,6 +37,9 @@ protected WxCpDefaultConfigImpl config(WxCpDefaultConfigImpl config, WxCpPropert if (StringUtils.isNotBlank(msgAuditLibPath)) { config.setMsgAuditLibPath(msgAuditLibPath); } + if (StringUtils.isNotBlank(properties.getBaseApiUrl())) { + config.setBaseApiUrl(properties.getBaseApiUrl()); + } WxCpProperties.ConfigStorage storage = properties.getConfigStorage(); String httpProxyHost = storage.getHttpProxyHost(); diff --git a/spring-boot-starters/wx-java-cp-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 index 75980c3616..246971baed 100644 --- 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 @@ -32,7 +32,7 @@ public class WxCpInJedisConfigStorageConfiguration extends AbstractWxCpConfigSto @Bean @ConditionalOnMissingBean(WxCpConfigStorage.class) - public WxCpConfigStorage wxOpenConfigStorage() { + public WxCpConfigStorage wxCpConfigStorage() { WxCpDefaultConfigImpl config = getConfigStorage(); 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 index 46efa050ad..879568b16a 100644 --- 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 @@ -29,7 +29,7 @@ public class WxCpInRedisTemplateConfigStorageConfiguration extends AbstractWxCpC @Bean @ConditionalOnMissingBean(WxCpConfigStorage.class) - public WxCpConfigStorage wxOpenConfigStorage() { + public WxCpConfigStorage wxCpConfigStorage() { WxCpDefaultConfigImpl config = getConfigStorage(); 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/WxCpInRedissonConfigStorageConfiguration.java b/spring-boot-starters/wx-java-cp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/storage/WxCpInRedissonConfigStorageConfiguration.java index 6f32730069..060b894fd1 100644 --- 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 @@ -34,7 +34,7 @@ public class WxCpInRedissonConfigStorageConfiguration extends AbstractWxCpConfig @Bean @ConditionalOnMissingBean(WxCpConfigStorage.class) - public WxCpConfigStorage wxOpenConfigStorage() { + public WxCpConfigStorage wxCpConfigStorage() { WxCpDefaultConfigImpl config = getConfigStorage(); return this.config(config, wxCpProperties); } 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..3666e7ba91 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml @@ -0,0 +1,62 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.6.B + + 4.0.0 + + wx-java-cp-tp-multi-spring-boot-starter + WxJava - Spring Boot Starter for WxCp::支持多账号配置 + 微信企业号开发的 Spring Boot Starter::支持多账号配置 + + + + com.github.binarywang + weixin-java-cp + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.springframework.data + spring-data-redis + ${spring-data-redis.version} + true + 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/MULTI_TENANT_MODE.md b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/MULTI_TENANT_MODE.md new file mode 100644 index 0000000000..6dd1d110c3 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/MULTI_TENANT_MODE.md @@ -0,0 +1,205 @@ +# 微信小程序多租户配置说明 + +## 多租户模式对比 + +从 4.8.0 版本开始,wx-java-miniapp-multi-spring-boot-starter 支持两种多租户实现模式: + +### 1. 隔离模式(ISOLATED,默认) + +每个租户创建独立的 `WxMaService` 实例,各自拥有独立的 HTTP 客户端。 + +**优点:** +- 线程安全,无需担心并发问题 +- 不依赖 ThreadLocal,适合异步/响应式编程 +- 租户间完全隔离,互不影响 + +**缺点:** +- 每个租户创建独立的 HTTP 客户端,资源占用较多 +- 适合租户数量不多的场景(建议 < 50 个租户) + +**适用场景:** +- SaaS 应用,租户数量较少 +- 异步编程、响应式编程场景 +- 对线程安全有严格要求 + +### 2. 共享模式(SHARED) + +使用单个 `WxMaService` 实例管理所有租户配置,所有租户共享同一个 HTTP 客户端。 + +**优点:** +- 共享 HTTP 客户端,大幅节省资源 +- 适合租户数量较多的场景(支持 100+ 租户) +- 内存占用更小 + +**缺点:** +- 依赖 ThreadLocal 切换配置,在异步场景需要特别注意 +- 需要注意线程上下文传递 + +**适用场景:** +- 租户数量较多(> 50 个) +- 同步编程场景 +- 对资源占用有严格要求 + +## 配置方式 + +### 使用隔离模式(默认) + +```yaml +wx: + ma: + # 多租户配置 + apps: + tenant1: + app-id: wxd898fcb01713c555 + app-secret: 47a2422a5d04a27e2b3ed1f1f0b0dbad + token: aBcDeFg123456 + aes-key: abcdefgh123456abcdefgh123456abc + tenant2: + app-id: wx1234567890abcdef + app-secret: 1234567890abcdef1234567890abcdef + token: token123 + aes-key: aeskey123aeskey123aeskey123aes + + # 配置存储(可选) + config-storage: + type: memory # memory, jedis, redisson, redis_template + http-client-type: http_client # http_client, ok_http, jodd_http + # multi-tenant-mode: isolated # 默认值,可以不配置 +``` + +### 使用共享模式 + +```yaml +wx: + ma: + # 多租户配置 + apps: + tenant1: + app-id: wxd898fcb01713c555 + app-secret: 47a2422a5d04a27e2b3ed1f1f0b0dbad + tenant2: + app-id: wx1234567890abcdef + app-secret: 1234567890abcdef1234567890abcdef + # ... 可配置更多租户 + + # 配置存储 + config-storage: + type: memory + http-client-type: http_client + multi-tenant-mode: shared # 启用共享模式 +``` + +## 代码使用 + +两种模式下的代码使用方式**完全相同**: + +```java +@RestController +@RequestMapping("/ma") +public class MiniAppController { + + @Autowired + private WxMaMultiServices wxMaMultiServices; + + @GetMapping("/userInfo/{tenantId}") + public String getUserInfo(@PathVariable String tenantId, @RequestParam String code) { + // 获取指定租户的 WxMaService + WxMaService wxMaService = wxMaMultiServices.getWxMaService(tenantId); + + try { + WxMaJscode2SessionResult session = wxMaService.jsCode2SessionInfo(code); + return "OpenId: " + session.getOpenid(); + } catch (WxErrorException e) { + return "错误: " + e.getMessage(); + } + } +} +``` + +## 性能对比 + +以 100 个租户为例: + +| 指标 | 隔离模式 | 共享模式 | +|------|---------|---------| +| HTTP 客户端数量 | 100 个 | 1 个 | +| 内存占用(估算) | ~500MB | ~50MB | +| 线程安全 | ✅ 完全安全 | ⚠️ 需注意异步场景 | +| 性能 | 略高(无 ThreadLocal 切换) | 略低(有 ThreadLocal 切换) | +| 适用场景 | 中小规模 | 大规模 | + +## 注意事项 + +### 共享模式下的异步编程 + +如果使用共享模式,在异步编程时需要注意 ThreadLocal 的传递: + +```java +@Service +public class MiniAppService { + + @Autowired + private WxMaMultiServices wxMaMultiServices; + + public void asyncOperation(String tenantId) { + WxMaService wxMaService = wxMaMultiServices.getWxMaService(tenantId); + + // ❌ 错误:异步线程无法获取到正确的配置 + CompletableFuture.runAsync(() -> { + // 这里 wxMaService.getWxMaConfig() 可能返回错误的配置 + wxMaService.getUserService().getUserInfo(...); + }); + + // ✅ 正确:在主线程获取配置,传递给异步线程 + WxMaConfig config = wxMaService.getWxMaConfig(); + String appId = config.getAppid(); + CompletableFuture.runAsync(() -> { + // 使用已获取的配置信息 + log.info("AppId: {}", appId); + }); + } +} +``` + +### 动态添加/删除租户 + +两种模式都支持运行时动态添加或删除租户配置。 + +## 迁移指南 + +如果您正在使用旧版本,升级到 4.8.0+ 后: + +1. **默认行为不变**:如果不配置 `multi-tenant-mode`,将继续使用隔离模式(与旧版本行为一致) +2. **向后兼容**:所有现有代码无需修改 +3. **可选升级**:如需节省资源,可配置 `multi-tenant-mode: shared` 启用共享模式 + +## 源码分析 + +issue讨论地址:[#3835](https://github.com/binarywang/WxJava/issues/3835) + +### 为什么有两种设计? + +1. **基础实现类的 `configMap`**: + - 位置:`BaseWxMaServiceImpl` + - 特点:单个 Service 实例 + 多个配置 + ThreadLocal 切换 + - 设计目的:支持在一个应用中管理多个小程序账号 + +2. **Spring Boot Starter 的 `services` Map**: + - 位置:`WxMaMultiServicesImpl` + - 特点:多个 Service 实例 + 每个实例一个配置 + - 设计目的:为 Spring Boot 提供更符合依赖注入风格的多租户支持 + +### 新版本改进 + +新版本通过配置项让用户自主选择实现方式: + +``` +用户 → WxMaMultiServices 接口 + ↓ + ┌────┴────┐ + ↓ ↓ +隔离模式 共享模式 +(多Service) (单Service+configMap) +``` + +这样既保留了线程安全的优势(隔离模式),又提供了资源节省的选项(共享模式)。 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..1c7e8f1d33 --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/pom.xml @@ -0,0 +1,74 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.6.B + + 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 + ${spring-data-redis.version} + true + 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..fba9d875ee --- /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,212 @@ +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 com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServicesSharedImpl; +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.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +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 的唯一性"); + } + } + + // 根据配置选择多租户模式 + WxMaMultiProperties.MultiTenantMode mode = wxMaMultiProperties.getConfigStorage().getMultiTenantMode(); + if (mode == WxMaMultiProperties.MultiTenantMode.SHARED) { + return createSharedMultiServices(appsMap, wxMaMultiProperties); + } else { + return createIsolatedMultiServices(appsMap, wxMaMultiProperties); + } + } + + /** + * 创建隔离模式的多租户服务(每个租户独立 WxMaService 实例) + */ + private WxMaMultiServices createIsolatedMultiServices( + Map appsMap, + WxMaMultiProperties wxMaMultiProperties) { + + 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); + } + + log.info("微信小程序多租户服务初始化完成,使用隔离模式(ISOLATED),共配置 {} 个租户", appsMap.size()); + return services; + } + + /** + * 创建共享模式的多租户服务(单个 WxMaService 实例管理多个配置) + */ + private WxMaMultiServices createSharedMultiServices( + Map appsMap, + WxMaMultiProperties wxMaMultiProperties) { + + // 创建共享的 WxMaService 实例 + WxMaMultiProperties.ConfigStorage storage = wxMaMultiProperties.getConfigStorage(); + WxMaService sharedService = createWxMaServiceByType(storage.getHttpClientType()); + configureWxMaService(sharedService, storage); + + // 准备所有租户的配置,使用 TreeMap 保证顺序一致性 + Map configsMap = new HashMap<>(); + String defaultTenantId = new TreeMap<>(appsMap).firstKey(); + + for (Map.Entry entry : appsMap.entrySet()) { + String tenantId = entry.getKey(); + WxMaSingleProperties wxMaSingleProperties = entry.getValue(); + WxMaDefaultConfigImpl config = this.wxMaConfigStorage(wxMaMultiProperties); + this.configApp(config, wxMaSingleProperties); + this.configHttp(config, storage); + configsMap.put(tenantId, config); + } + + // 设置多配置到共享的 WxMaService + sharedService.setMultiConfigs(configsMap, defaultTenantId); + + log.info("微信小程序多租户服务初始化完成,使用共享模式(SHARED),共配置 {} 个租户,共享一个 HTTP 客户端", appsMap.size()); + return new WxMaMultiServicesSharedImpl(sharedService); + } + + /** + * 根据类型创建 WxMaService 实例 + */ + private WxMaService createWxMaServiceByType(WxMaMultiProperties.HttpClientType httpClientType) { + switch (httpClientType) { + case OK_HTTP: + return new WxMaServiceOkHttpImpl(); + case JODD_HTTP: + return new WxMaServiceJoddHttpImpl(); + case HTTP_CLIENT: + return new WxMaServiceHttpClientImpl(); + default: + return new WxMaServiceImpl(); + } + } + + /** + * 配置 WxMaService 的通用参数 + */ + private void configureWxMaService(WxMaService wxMaService, WxMaMultiProperties.ConfigStorage storage) { + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxMaService.setRetrySleepMillis(retrySleepMillis); + wxMaService.setMaxRetryTimes(maxRetryTimes); + } + + /** + * 配置 WxMaDefaultConfigImpl + * + * @param wxMaMultiProperties 参数 + * @return WxMaDefaultConfigImpl + */ + protected abstract WxMaDefaultConfigImpl wxMaConfigStorage(WxMaMultiProperties wxMaMultiProperties); + + public WxMaService wxMaService(WxMaConfig wxMaConfig, WxMaMultiProperties wxMaMultiProperties) { + WxMaMultiProperties.ConfigStorage storage = wxMaMultiProperties.getConfigStorage(); + WxMaService wxMaService = createWxMaServiceByType(storage.getHttpClientType()); + wxMaService.setWxMaConfig(wxMaConfig); + configureWxMaService(wxMaService, storage); + 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..201aceb8bf --- /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,178 @@ +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; + + /** + * 多租户实现模式. + *
    + *
  • ISOLATED: 为每个租户创建独立的 WxMaService 实例(默认)
  • + *
  • SHARED: 使用单个 WxMaService 实例管理所有租户配置,共享 HTTP 客户端
  • + *
+ */ + private MultiTenantMode multiTenantMode = MultiTenantMode.ISOLATED; + } + + public enum StorageType { + /** + * 内存 + */ + MEMORY, + /** + * jedis + */ + JEDIS, + /** + * redisson + */ + REDISSON, + /** + * redisTemplate + */ + REDIS_TEMPLATE + } + + public enum HttpClientType { + /** + * HttpClient + */ + HTTP_CLIENT, + /** + * OkHttp + */ + OK_HTTP, + /** + * JoddHttp + */ + JODD_HTTP + } + + public enum MultiTenantMode { + /** + * 隔离模式:为每个租户创建独立的 WxMaService 实例. + * 优点:线程安全,不依赖 ThreadLocal + * 缺点:每个租户创建独立的 HTTP 客户端,资源占用较多 + */ + ISOLATED, + /** + * 共享模式:使用单个 WxMaService 实例管理所有租户配置. + * 优点:共享 HTTP 客户端,节省资源 + * 缺点:依赖 ThreadLocal 切换配置,异步场景需注意 + */ + SHARED + } +} 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/java/com/binarywang/spring/starter/wxjava/miniapp/service/WxMaMultiServicesSharedImpl.java b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/service/WxMaMultiServicesSharedImpl.java new file mode 100644 index 0000000000..40a01fb52e --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/service/WxMaMultiServicesSharedImpl.java @@ -0,0 +1,53 @@ +package com.binarywang.spring.starter.wxjava.miniapp.service; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import lombok.RequiredArgsConstructor; + +/** + * 微信小程序 {@link WxMaMultiServices} 共享式实现. + *

+ * 使用单个 WxMaService 实例管理多个租户配置,通过 switchover 切换租户。 + * 相比 {@link WxMaMultiServicesImpl},此实现共享 HTTP 客户端,节省资源。 + *

+ *

+ * 注意:由于使用 ThreadLocal 切换配置,在异步或多线程场景需要特别注意线程上下文切换。 + *

+ * + * @author Binary Wang + * created on 2026/1/9 + */ +@RequiredArgsConstructor +public class WxMaMultiServicesSharedImpl implements WxMaMultiServices { + private final WxMaService sharedWxMaService; + + @Override + public WxMaService getWxMaService(String tenantId) { + if (tenantId == null) { + return null; + } + // 使用 switchover 检查配置是否存在,保持与隔离模式 API 行为一致(不存在时返回 null) + if (!sharedWxMaService.switchover(tenantId)) { + return null; + } + return sharedWxMaService; + } + + @Override + public void removeWxMaService(String tenantId) { + if (tenantId != null) { + sharedWxMaService.removeConfig(tenantId); + } + } + + /** + * 添加租户配置到共享的 WxMaService 实例 + * + * @param tenantId 租户 ID + * @param wxMaService 要添加配置的 WxMaService(仅使用其配置,不使用其实例) + */ + public void addWxMaService(String tenantId, WxMaService wxMaService) { + if (tenantId != null && wxMaService != null) { + sharedWxMaService.addConfig(tenantId, wxMaService.getWxMaConfig()); + } + } +} diff --git a/spring-boot-starters/wx-java-miniapp-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 82f6bdd8b1..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 @@ -10,12 +10,13 @@ ``` 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 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 738f6e6b05..3a2bc80986 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml @@ -4,7 +4,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.5.3.B + 4.8.6.B 4.0.0 @@ -31,6 +31,8 @@ org.springframework.data spring-data-redis + ${spring-data-redis.version} + true provided diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java index 79c16fb053..f03d3f1493 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaServiceAutoConfiguration.java @@ -2,6 +2,7 @@ import cn.binarywang.wx.miniapp.api.WxMaService; import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpClientImpl; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpComponentsImpl; import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; import cn.binarywang.wx.miniapp.api.impl.WxMaServiceJoddHttpImpl; import cn.binarywang.wx.miniapp.api.impl.WxMaServiceOkHttpImpl; @@ -46,6 +47,9 @@ public WxMaService wxMaService(WxMaConfig wxMaConfig) { case HttpClient: wxMaService = new WxMaServiceHttpClientImpl(); break; + case HttpComponents: + wxMaService = new WxMaServiceHttpComponentsImpl(); + break; default: wxMaService = new WxMaServiceImpl(); break; diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/AbstractWxMaConfigStorageConfiguration.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/AbstractWxMaConfigStorageConfiguration.java index 6f44ac27ee..abcd83e848 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/AbstractWxMaConfigStorageConfiguration.java +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/storage/AbstractWxMaConfigStorageConfiguration.java @@ -2,6 +2,8 @@ import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; import org.apache.commons.lang3.StringUtils; /** @@ -10,11 +12,15 @@ public abstract class AbstractWxMaConfigStorageConfiguration { protected WxMaDefaultConfigImpl config(WxMaDefaultConfigImpl config, WxMaProperties properties) { + WxMaProperties.ConfigStorage storage = properties.getConfigStorage(); config.setAppid(StringUtils.trimToNull(properties.getAppid())); config.setSecret(StringUtils.trimToNull(properties.getSecret())); config.setToken(StringUtils.trimToNull(properties.getToken())); config.setAesKey(StringUtils.trimToNull(properties.getAesKey())); config.setMsgDataFormat(StringUtils.trimToNull(properties.getMsgDataFormat())); + config.useStableAccessToken(properties.isUseStableAccessToken()); + config.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl())); + config.setAccessTokenUrl(StringUtils.trimToNull(properties.getAccessTokenUrl())); WxMaProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); @@ -24,6 +30,19 @@ protected WxMaDefaultConfigImpl config(WxMaDefaultConfigImpl config, WxMaPropert config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); } + // 设置自定义的HttpClient超时配置 + ApacheHttpClientBuilder clientBuilder = config.getApacheHttpClientBuilder(); + if (clientBuilder == null) { + clientBuilder = DefaultApacheHttpClientBuilder.get(); + } + if (clientBuilder instanceof DefaultApacheHttpClientBuilder) { + DefaultApacheHttpClientBuilder defaultBuilder = (DefaultApacheHttpClientBuilder) clientBuilder; + defaultBuilder.setConnectionTimeout(storage.getConnectionTimeout()); + defaultBuilder.setSoTimeout(storage.getSoTimeout()); + defaultBuilder.setConnectionRequestTimeout(storage.getConnectionRequestTimeout()); + config.setApacheHttpClientBuilder(defaultBuilder); + } + int maxRetryTimes = configStorageProperties.getMaxRetryTimes(); if (configStorageProperties.getMaxRetryTimes() < 0) { maxRetryTimes = 0; diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java index b3e4b464fe..48549e4399 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java @@ -8,7 +8,7 @@ */ public enum HttpClientType { /** - * HttpClient. + * HttpClient (Apache HttpClient 4.x). */ HttpClient, /** @@ -19,4 +19,8 @@ public enum HttpClientType { * JoddHttp. */ JoddHttp, + /** + * HttpComponents (Apache HttpClient 5.x). + */ + HttpComponents, } diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaProperties.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaProperties.java index b7ccb45374..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 @@ -44,6 +44,23 @@ public class WxMaProperties { */ 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; + /** * 存储策略 */ @@ -71,7 +88,7 @@ public static class ConfigStorage { /** * http客户端类型. */ - private HttpClientType httpClientType = HttpClientType.HttpClient; + private HttpClientType httpClientType = HttpClientType.HttpComponents; /** * http代理主机. @@ -107,6 +124,21 @@ public static class ConfigStorage { * */ private int maxRetryTimes = 5; + + /** + * 连接超时时间,单位毫秒 + */ + private int connectionTimeout = 5000; + + /** + * 读数据超时时间,即socketTimeout,单位毫秒 + */ + private int soTimeout = 5000; + + /** + * 从连接池获取链接的超时时间,单位毫秒 + */ + private int connectionRequestTimeout = 5000; } } diff --git a/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/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..01790349ec --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/pom.xml @@ -0,0 +1,79 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.6.B + + 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 + ${spring-data-redis.version} + true + 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..46724c625f --- /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,231 @@ +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 com.binarywang.spring.starter.wxjava.mp.service.WxMpMultiServicesSharedImpl; +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.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +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 的唯一性"); + } + } + + // 根据配置选择多租户模式 + WxMpMultiProperties.MultiTenantMode mode = wxMpMultiProperties.getConfigStorage().getMultiTenantMode(); + if (mode == WxMpMultiProperties.MultiTenantMode.SHARED) { + return createSharedMultiServices(appsMap, wxMpMultiProperties); + } else { + return createIsolatedMultiServices(appsMap, wxMpMultiProperties); + } + } + + /** + * 创建隔离模式的多租户服务(每个租户独立 WxMpService 实例) + */ + private WxMpMultiServices createIsolatedMultiServices( + Map appsMap, + WxMpMultiProperties wxMpMultiProperties) { + + 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); + } + + log.info("微信公众号多租户服务初始化完成,使用隔离模式(ISOLATED),共配置 {} 个租户", appsMap.size()); + return services; + } + + /** + * 创建共享模式的多租户服务(单个 WxMpService 实例管理多个配置) + */ + private WxMpMultiServices createSharedMultiServices( + Map appsMap, + WxMpMultiProperties wxMpMultiProperties) { + + // 创建共享的 WxMpService 实例 + WxMpMultiProperties.ConfigStorage storage = wxMpMultiProperties.getConfigStorage(); + WxMpService sharedService = createWxMpServiceByType(storage.getHttpClientType()); + configureWxMpService(sharedService, storage); + + // 准备所有租户的配置,使用 TreeMap 保证顺序一致性 + Map configsMap = new HashMap<>(); + String defaultTenantId = new TreeMap<>(appsMap).firstKey(); + + for (Map.Entry entry : appsMap.entrySet()) { + String tenantId = entry.getKey(); + WxMpSingleProperties wxMpSingleProperties = entry.getValue(); + WxMpDefaultConfigImpl config = this.wxMpConfigStorage(wxMpMultiProperties); + this.configApp(config, wxMpSingleProperties); + this.configHttp(config, storage); + this.configHost(config, wxMpMultiProperties.getHosts()); + configsMap.put(tenantId, config); + } + + // 设置多配置到共享的 WxMpService + sharedService.setMultiConfigStorages(configsMap, defaultTenantId); + + log.info("微信公众号多租户服务初始化完成,使用共享模式(SHARED),共配置 {} 个租户,共享一个 HTTP 客户端", appsMap.size()); + return new WxMpMultiServicesSharedImpl(sharedService); + } + + /** + * 根据类型创建 WxMpService 实例 + */ + private WxMpService createWxMpServiceByType(WxMpMultiProperties.HttpClientType httpClientType) { + switch (httpClientType) { + case OK_HTTP: + return new WxMpServiceOkHttpImpl(); + case JODD_HTTP: + return new WxMpServiceJoddHttpImpl(); + case HTTP_CLIENT: + return new WxMpServiceHttpClientImpl(); + case HTTP_COMPONENTS: + return new WxMpServiceHttpComponentsImpl(); + default: + return new WxMpServiceImpl(); + } + } + + /** + * 配置 WxMpService 的通用参数 + */ + private void configureWxMpService(WxMpService wxMpService, WxMpMultiProperties.ConfigStorage storage) { + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxMpService.setRetrySleepMillis(retrySleepMillis); + wxMpService.setMaxRetryTimes(maxRetryTimes); + } + + /** + * 配置 WxMpDefaultConfigImpl + * + * @param wxMpMultiProperties 参数 + * @return WxMpDefaultConfigImpl + */ + protected abstract WxMpDefaultConfigImpl wxMpConfigStorage(WxMpMultiProperties wxMpMultiProperties); + + public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpMultiProperties wxMpMultiProperties) { + WxMpMultiProperties.ConfigStorage storage = wxMpMultiProperties.getConfigStorage(); + WxMpService wxMpService = createWxMpServiceByType(storage.getHttpClientType()); + wxMpService.setWxMpConfigStorage(configStorage); + configureWxMpService(wxMpService, storage); + 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..9dd95f9531 --- /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,182 @@ +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; + + /** + * 多租户实现模式. + *
    + *
  • ISOLATED: 为每个租户创建独立的 WxMpService 实例(默认)
  • + *
  • SHARED: 使用单个 WxMpService 实例管理所有租户配置,共享 HTTP 客户端
  • + *
+ */ + private MultiTenantMode multiTenantMode = MultiTenantMode.ISOLATED; + } + + 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 + } + + public enum MultiTenantMode { + /** + * 隔离模式:为每个租户创建独立的 WxMpService 实例. + * 优点:线程安全,不依赖 ThreadLocal + * 缺点:每个租户创建独立的 HTTP 客户端,资源占用较多 + */ + ISOLATED, + /** + * 共享模式:使用单个 WxMpService 实例管理所有租户配置. + * 优点:共享 HTTP 客户端,节省资源 + * 缺点:依赖 ThreadLocal 切换配置,异步场景需注意 + */ + SHARED + } +} 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/java/com/binarywang/spring/starter/wxjava/mp/service/WxMpMultiServicesSharedImpl.java b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/service/WxMpMultiServicesSharedImpl.java new file mode 100644 index 0000000000..ca9123c572 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/service/WxMpMultiServicesSharedImpl.java @@ -0,0 +1,53 @@ +package com.binarywang.spring.starter.wxjava.mp.service; + +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.api.WxMpService; + +/** + * 微信公众号 {@link WxMpMultiServices} 共享式实现. + *

+ * 使用单个 WxMpService 实例管理多个租户配置,通过 switchover 切换租户。 + * 相比 {@link WxMpMultiServicesImpl},此实现共享 HTTP 客户端,节省资源。 + *

+ *

+ * 注意:由于使用 ThreadLocal 切换配置,在异步或多线程场景需要特别注意线程上下文切换。 + *

+ * + * @author Binary Wang + * created on 2026/1/9 + */ +@RequiredArgsConstructor +public class WxMpMultiServicesSharedImpl implements WxMpMultiServices { + private final WxMpService sharedWxMpService; + + @Override + public WxMpService getWxMpService(String tenantId) { + if (tenantId == null) { + return null; + } + // 使用 switchover 检查配置是否存在,保持与隔离模式 API 行为一致(不存在时返回 null) + if (!sharedWxMpService.switchover(tenantId)) { + return null; + } + return sharedWxMpService; + } + + @Override + public void removeWxMpService(String tenantId) { + if (tenantId != null) { + sharedWxMpService.removeConfigStorage(tenantId); + } + } + + /** + * 添加租户配置到共享的 WxMpService 实例 + * + * @param tenantId 租户 ID + * @param wxMpService 要添加配置的 WxMpService(仅使用其配置,不使用其实例) + */ + public void addWxMpService(String tenantId, WxMpService wxMpService) { + if (tenantId != null && wxMpService != null) { + sharedWxMpService.addConfigStorage(tenantId, wxMpService.getWxMpConfigStorage()); + } + } +} diff --git a/spring-boot-starters/wx-java-mp-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 81a075432f..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-spring-boot-starter + ## 快速开始 + 1. 引入依赖 ```xml @@ -11,20 +13,21 @@ 2. 添加配置(application.properties) ```properties # 公众号配置(必填) - wx.mp.appId = appId - wx.mp.secret = @secret - wx.mp.token = @token - wx.mp.aesKey = @aesKey + 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 = Jedis # 配置类型: 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 + 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-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= @@ -35,13 +38,9 @@ #wx.mp.hosts.mp-host=http://proxy.com/ ``` 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 594a62615c..326caf55be 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.5.3.B + 4.8.6.B 4.0.0 @@ -22,11 +22,13 @@ redis.clients jedis - compile + provided org.springframework.data spring-data-redis + ${spring-data-redis.version} + true provided @@ -39,6 +41,16 @@ 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/WxMpServiceAutoConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java index 3b8733c286..dc6dcafb82 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java @@ -4,6 +4,7 @@ import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl; +import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpComponentsImpl; import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; import me.chanjar.weixin.mp.api.impl.WxMpServiceJoddHttpImpl; import me.chanjar.weixin.mp.api.impl.WxMpServiceOkHttpImpl; @@ -35,6 +36,9 @@ public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpProperties w case HttpClient: wxMpService = newWxMpServiceHttpClientImpl(); break; + case HttpComponents: + wxMpService = newWxMpServiceHttpComponentsImpl(); + break; default: wxMpService = newWxMpServiceImpl(); break; @@ -60,4 +64,8 @@ private WxMpService newWxMpServiceJoddHttpImpl() { return new WxMpServiceJoddHttpImpl(); } + private WxMpService newWxMpServiceHttpComponentsImpl() { + return new WxMpServiceHttpComponentsImpl(); + } + } diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java index 912b902624..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,160 +1,26 @@ package com.binarywang.spring.starter.wxjava.mp.config; -import com.binarywang.spring.starter.wxjava.mp.enums.StorageType; -import com.binarywang.spring.starter.wxjava.mp.properties.RedisProperties; -import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; -import com.google.common.collect.Sets; +import com.binarywang.spring.starter.wxjava.mp.config.storage.WxMpInJedisConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.mp.config.storage.WxMpInMemoryConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.mp.config.storage.WxMpInRedisTemplateConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.mp.config.storage.WxMpInRedissonConfigStorageConfiguration; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.redis.JedisWxRedisOps; -import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; -import me.chanjar.weixin.common.redis.WxRedisOps; -import me.chanjar.weixin.mp.config.WxMpHostConfig; -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.apache.commons.lang3.StringUtils; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.core.StringRedisTemplate; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; -import redis.clients.jedis.JedisSentinelPool; -import redis.clients.jedis.util.Pool; - -import java.util.Set; +import org.springframework.context.annotation.Import; /** * 微信公众号存储策略自动配置. * * @author Luo */ -@Slf4j @Configuration +@Import({ + WxMpInMemoryConfigStorageConfiguration.class, + WxMpInJedisConfigStorageConfiguration.class, + WxMpInRedisTemplateConfigStorageConfiguration.class, + WxMpInRedissonConfigStorageConfiguration.class +}) @RequiredArgsConstructor public class WxMpStorageAutoConfiguration { - private final ApplicationContext applicationContext; - - private final WxMpProperties wxMpProperties; - - @Bean - @ConditionalOnMissingBean(WxMpConfigStorage.class) - public WxMpConfigStorage wxMpConfigStorage() { - StorageType type = wxMpProperties.getConfigStorage().getType(); - WxMpConfigStorage config; - switch (type) { - case Jedis: - config = jedisConfigStorage(); - break; - case RedisTemplate: - config = redisTemplateConfigStorage(); - break; - default: - config = defaultConfigStorage(); - break; - } - // wx host config - if (null != wxMpProperties.getHosts() && StringUtils.isNotEmpty(wxMpProperties.getHosts().getApiHost())) { - WxMpHostConfig hostConfig = new WxMpHostConfig(); - hostConfig.setApiHost(wxMpProperties.getHosts().getApiHost()); - hostConfig.setMpHost(wxMpProperties.getHosts().getMpHost()); - hostConfig.setOpenHost(wxMpProperties.getHosts().getOpenHost()); - config.setHostConfig(hostConfig); - } - return config; - } - - private WxMpConfigStorage defaultConfigStorage() { - WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); - setWxMpInfo(config); - return config; - } - - private WxMpConfigStorage jedisConfigStorage() { - Pool jedisPool; - if (wxMpProperties.getConfigStorage() != null && wxMpProperties.getConfigStorage().getRedis() != null - && StringUtils.isNotEmpty(wxMpProperties.getConfigStorage().getRedis().getHost())) { - jedisPool = getJedisPool(); - } else { - jedisPool = applicationContext.getBean(JedisPool.class); - } - WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); - WxMpRedisConfigImpl wxMpRedisConfig = new WxMpRedisConfigImpl(redisOps, - wxMpProperties.getConfigStorage().getKeyPrefix()); - setWxMpInfo(wxMpRedisConfig); - return wxMpRedisConfig; - } - - private WxMpConfigStorage redisTemplateConfigStorage() { - StringRedisTemplate redisTemplate = null; - try { - redisTemplate = applicationContext.getBean(StringRedisTemplate.class); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - try { - if (null == redisTemplate) { - redisTemplate = (StringRedisTemplate) applicationContext.getBean("stringRedisTemplate"); - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - - if (null == redisTemplate) { - redisTemplate = (StringRedisTemplate) applicationContext.getBean("redisTemplate"); - } - - WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate); - WxMpRedisConfigImpl wxMpRedisConfig = new WxMpRedisConfigImpl(redisOps, - wxMpProperties.getConfigStorage().getKeyPrefix()); - - setWxMpInfo(wxMpRedisConfig); - return wxMpRedisConfig; - } - - private void setWxMpInfo(WxMpDefaultConfigImpl config) { - WxMpProperties properties = wxMpProperties; - WxMpProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); - config.setAppId(properties.getAppId()); - config.setSecret(properties.getSecret()); - config.setToken(properties.getToken()); - config.setAesKey(properties.getAesKey()); - - config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); - config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername()); - config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword()); - if (configStorageProperties.getHttpProxyPort() != null) { - config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); - } - } - - private Pool getJedisPool() { - RedisProperties redis = wxMpProperties.getConfigStorage().getRedis(); - - JedisPoolConfig config = new JedisPoolConfig(); - if (redis.getMaxActive() != null) { - config.setMaxTotal(redis.getMaxActive()); - } - if (redis.getMaxIdle() != null) { - config.setMaxIdle(redis.getMaxIdle()); - } - if (redis.getMaxWaitMillis() != null) { - config.setMaxWaitMillis(redis.getMaxWaitMillis()); - } - if (redis.getMinIdle() != null) { - config.setMinIdle(redis.getMinIdle()); - } - config.setTestOnBorrow(true); - config.setTestWhileIdle(true); - if (StringUtils.isNotEmpty(redis.getSentinelIps())) { - Set sentinels = Sets.newHashSet(redis.getSentinelIps().split(",")); - return new JedisSentinelPool(redis.getSentinelName(), sentinels); - } - return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), - redis.getDatabase()); - } } diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java new file mode 100644 index 0000000000..e39a8bf4d9 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/AbstractWxMpConfigStorageConfiguration.java @@ -0,0 +1,54 @@ +package com.binarywang.spring.starter.wxjava.mp.config.storage; + +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; +import me.chanjar.weixin.mp.config.WxMpHostConfig; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +/** + * @author zhangyl + */ +public abstract class AbstractWxMpConfigStorageConfiguration { + + protected WxMpDefaultConfigImpl config(WxMpDefaultConfigImpl config, WxMpProperties properties) { + config.setAppId(properties.getAppId()); + config.setSecret(properties.getSecret()); + config.setToken(properties.getToken()); + config.setAesKey(properties.getAesKey()); + config.setUseStableAccessToken(properties.isUseStableAccessToken()); + + WxMpProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); + config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); + config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername()); + config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword()); + if (configStorageProperties.getHttpProxyPort() != null) { + config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); + } + + // 设置自定义的 HttpClient 超时配置 + ApacheHttpClientBuilder clientBuilder = config.getApacheHttpClientBuilder(); + if (clientBuilder == null) { + clientBuilder = DefaultApacheHttpClientBuilder.get(); + } + if (clientBuilder instanceof DefaultApacheHttpClientBuilder) { + DefaultApacheHttpClientBuilder defaultBuilder = (DefaultApacheHttpClientBuilder) clientBuilder; + defaultBuilder.setConnectionTimeout(configStorageProperties.getConnectionTimeout()); + defaultBuilder.setSoTimeout(configStorageProperties.getSoTimeout()); + defaultBuilder.setConnectionRequestTimeout(configStorageProperties.getConnectionRequestTimeout()); + config.setApacheHttpClientBuilder(defaultBuilder); + } + + // wx host config + if (null != properties.getHosts() && StringUtils.isNotEmpty(properties.getHosts().getApiHost())) { + WxMpHostConfig hostConfig = new WxMpHostConfig(); + hostConfig.setApiHost(properties.getHosts().getApiHost()); + hostConfig.setOpenHost(properties.getHosts().getOpenHost()); + hostConfig.setMpHost(properties.getHosts().getMpHost()); + config.setHostConfig(hostConfig); + } + + return config; + } +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java new file mode 100644 index 0000000000..c21418a6f6 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInJedisConfigStorageConfiguration.java @@ -0,0 +1,80 @@ +package com.binarywang.spring.starter.wxjava.mp.config.storage; + +import com.binarywang.spring.starter.wxjava.mp.properties.RedisProperties; +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +/** + * @author zhangyl + */ +@Configuration +@ConditionalOnProperty( + prefix = WxMpProperties.PREFIX + ".config-storage", + name = "type", + havingValue = "jedis" +) +@ConditionalOnClass(Jedis.class) +@RequiredArgsConstructor +public class WxMpInJedisConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration { + private final WxMpProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxMpConfigStorage.class) + public WxMpConfigStorage wxMpConfigStorage() { + WxMpRedisConfigImpl config = getWxMpRedisConfigImpl(); + return this.config(config, properties); + } + + private WxMpRedisConfigImpl getWxMpRedisConfigImpl() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + jedisPool = applicationContext.getBean("wxMpJedisPool", JedisPool.class); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); + return new WxMpRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } + + @Bean + @ConditionalOnProperty(prefix = WxMpProperties.PREFIX + ".config-storage.redis", name = "host") + public JedisPool wxMpJedisPool() { + WxMpProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), + redis.getDatabase()); + } +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java new file mode 100644 index 0000000000..16eada73ae --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInMemoryConfigStorageConfiguration.java @@ -0,0 +1,33 @@ +package com.binarywang.spring.starter.wxjava.mp.config.storage; + +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author zhangyl + */ +@Configuration +@ConditionalOnProperty( + prefix = WxMpProperties.PREFIX + ".config-storage", + name = "type", + havingValue = "memory", + matchIfMissing = true +) +@RequiredArgsConstructor +public class WxMpInMemoryConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration { + private final WxMpProperties properties; + + @Bean + @ConditionalOnMissingBean(WxMpConfigStorage.class) + public WxMpConfigStorage wxMpConfigStorage() { + WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); + config(config, properties); + return config; + } +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedisTemplateConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedisTemplateConfigStorageConfiguration.java new file mode 100644 index 0000000000..0305ca4f8e --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedisTemplateConfigStorageConfiguration.java @@ -0,0 +1,46 @@ +package com.binarywang.spring.starter.wxjava.mp.config.storage; + +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * @author zhangyl + */ +@Slf4j +@Configuration +@ConditionalOnProperty( + prefix = WxMpProperties.PREFIX + ".config-storage", + name = "type", + havingValue = "redistemplate" +) +@ConditionalOnClass(StringRedisTemplate.class) +@RequiredArgsConstructor +public class WxMpInRedisTemplateConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration { + private final WxMpProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxMpConfigStorage.class) + public WxMpConfigStorage wxMpConfigStorage() { + WxMpRedisConfigImpl config = getWxMpInRedisTemplateConfigStorage(); + return this.config(config, properties); + } + + private WxMpRedisConfigImpl getWxMpInRedisTemplateConfigStorage() { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate); + return new WxMpRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java new file mode 100644 index 0000000000..75b736f53f --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/storage/WxMpInRedissonConfigStorageConfiguration.java @@ -0,0 +1,69 @@ +package com.binarywang.spring.starter.wxjava.mp.config.storage; + +import com.binarywang.spring.starter.wxjava.mp.properties.RedisProperties; +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; +import me.chanjar.weixin.mp.config.impl.WxMpRedissonConfigImpl; +import org.apache.commons.lang3.StringUtils; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author zhangyl + */ +@Configuration +@ConditionalOnProperty( + prefix = WxMpProperties.PREFIX + ".config-storage", + name = "type", + havingValue = "redisson" +) +@ConditionalOnClass({Redisson.class, RedissonClient.class}) +@RequiredArgsConstructor +public class WxMpInRedissonConfigStorageConfiguration extends AbstractWxMpConfigStorageConfiguration { + private final WxMpProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxMpConfigStorage.class) + public WxMpConfigStorage wxMpConfigStorage() { + WxMpRedissonConfigImpl config = getWxMpInRedissonConfigStorage(); + return this.config(config, properties); + } + + private WxMpRedissonConfigImpl getWxMpInRedissonConfigStorage() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = applicationContext.getBean("wxMpRedissonClient", RedissonClient.class); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxMpRedissonConfigImpl(redissonClient, properties.getConfigStorage().getKeyPrefix()); + } + + @Bean + @ConditionalOnProperty(prefix = WxMpProperties.PREFIX + ".config-storage.redis", name = "host") + public RedissonClient wxMpRedissonClient() { + WxMpProperties.ConfigStorage storage = properties.getConfigStorage(); + RedisProperties redis = storage.getRedis(); + + Config config = new Config(); + config.useSingleServer() + .setAddress("redis://" + redis.getHost() + ":" + redis.getPort()) + .setDatabase(redis.getDatabase()); + if (StringUtils.isNotBlank(redis.getPassword())) { + config.useSingleServer().setPassword(redis.getPassword()); + } + config.setTransportMode(TransportMode.NIO); + return Redisson.create(config); + } +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java index f67ef97c2e..0bf034417f 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java @@ -19,4 +19,8 @@ public enum HttpClientType { * JoddHttp. */ JoddHttp, + /** + * HttpComponents (Apache HttpClient 5.x). + */ + HttpComponents, } diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/HostConfig.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/HostConfig.java index b8c0f1594f..5b29400738 100644 --- 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 @@ -9,10 +9,19 @@ 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/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 89d0e6629d..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 @@ -41,9 +41,15 @@ public class WxMpProperties { */ private String aesKey; + /** + * 是否使用稳定版 Access Token + */ + private boolean useStableAccessToken = false; + /** * 自定义host配置 */ + @NestedConfigurationProperty private HostConfig hosts; /** @@ -74,7 +80,7 @@ public static class ConfigStorage implements Serializable { /** * http客户端类型. */ - private HttpClientType httpClientType = HttpClientType.HttpClient; + private HttpClientType httpClientType = HttpClientType.HttpComponents; /** * http代理主机. @@ -96,6 +102,21 @@ public static class ConfigStorage implements Serializable { */ private String httpProxyPassword; + /** + * 连接超时时间,单位毫秒 + */ + private int connectionTimeout = 5000; + + /** + * 读数据超时时间,即socketTimeout,单位毫秒 + */ + private int soTimeout = 5000; + + /** + * 从连接池获取链接的超时时间,单位毫秒 + */ + private int connectionRequestTimeout = 5000; + } } diff --git a/spring-boot-starters/wx-java-open-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..6f09c3bdd8 --- /dev/null +++ b/spring-boot-starters/wx-java-open-multi-spring-boot-starter/pom.xml @@ -0,0 +1,64 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.6.B + + 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 + ${spring-data-redis.version} + true + 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..f4245402e0 --- /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,162 @@ +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.WxOpenServiceApacheHttpClientImpl; +import me.chanjar.weixin.open.api.impl.WxOpenServiceHttpComponentsImpl; +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) { + WxOpenMultiProperties.HttpClientType httpClientType = wxOpenMultiProperties.getConfigStorage().getHttpClientType(); + WxOpenService wxOpenService; + if (httpClientType == WxOpenMultiProperties.HttpClientType.APACHE_HTTP) { + wxOpenService = new WxOpenServiceApacheHttpClientImpl(); + } else { + wxOpenService = new WxOpenServiceHttpComponentsImpl(); + } + 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); + + // 仅在使用 Apache HttpClient 4.x 时配置 ApacheHttpClientBuilder 超时参数 + if (storage.getHttpClientType() == WxOpenMultiProperties.HttpClientType.APACHE_HTTP) { + 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..0c6a222891 --- /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,141 @@ +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; + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.HTTP_COMPONENTS; + + /** + * 指定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 + } + + public enum HttpClientType { + /** + * Apache HttpClient 4.x + */ + APACHE_HTTP, + /** + * Apache HttpClient 5.x (HttpComponents) + */ + HTTP_COMPONENTS + } + +} 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/pom.xml b/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml index d0a22ef9dd..cf67f4d0cd 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.5.3.B + 4.8.6.B 4.0.0 @@ -30,6 +30,9 @@ org.springframework.data spring-data-redis + ${spring-data-redis.version} + true + provided diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenServiceAutoConfiguration.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenServiceAutoConfiguration.java index 22b0a6621d..e532f3c160 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenServiceAutoConfiguration.java +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenServiceAutoConfiguration.java @@ -28,6 +28,7 @@ public WxOpenService wxOpenService(WxOpenConfigStorage wxOpenConfigStorage) { } @Bean + @ConditionalOnMissingBean public WxOpenMessageRouter wxOpenMessageRouter(WxOpenService wxOpenService) { return new WxOpenMessageRouter(wxOpenService); } diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/AbstractWxOpenConfigStorageConfiguration.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/AbstractWxOpenConfigStorageConfiguration.java index ee0443c9ae..91db545ab9 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/AbstractWxOpenConfigStorageConfiguration.java +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/storage/AbstractWxOpenConfigStorageConfiguration.java @@ -1,7 +1,10 @@ package com.binarywang.spring.starter.wxjava.open.config.storage; import com.binarywang.spring.starter.wxjava.open.properties.WxOpenProperties; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; +import org.apache.commons.lang3.StringUtils; /** * @author yl @@ -28,6 +31,24 @@ protected WxOpenInMemoryConfigStorage config(WxOpenInMemoryConfigStorage config, } config.setRetrySleepMillis(retrySleepMillis); config.setMaxRetryTimes(maxRetryTimes); + + // 设置URL配置 + config.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl())); + config.setAccessTokenUrl(StringUtils.trimToNull(properties.getAccessTokenUrl())); + + // 设置自定义的HttpClient超时配置 + ApacheHttpClientBuilder clientBuilder = config.getApacheHttpClientBuilder(); + if (clientBuilder == null) { + clientBuilder = DefaultApacheHttpClientBuilder.get(); + } + if (clientBuilder instanceof DefaultApacheHttpClientBuilder) { + DefaultApacheHttpClientBuilder defaultBuilder = (DefaultApacheHttpClientBuilder) clientBuilder; + defaultBuilder.setConnectionTimeout(storage.getConnectionTimeout()); + defaultBuilder.setSoTimeout(storage.getSoTimeout()); + defaultBuilder.setConnectionRequestTimeout(storage.getConnectionRequestTimeout()); + config.setApacheHttpClientBuilder(defaultBuilder); + } + return config; } } diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java index 641c57b005..248c6eedf6 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java @@ -40,6 +40,18 @@ public class WxOpenProperties { */ private String aesKey; + /** + * 自定义API主机地址,用于替换默认的 https://api.weixin.qq.com + * 例如:http://proxy.company.com:8080 + */ + private String apiHostUrl; + + /** + * 自定义获取AccessToken地址,用于向自定义统一服务获取AccessToken + * 例如:http://proxy.company.com:8080/oauth/token + */ + private String accessTokenUrl; + /** * 存储策略. */ @@ -108,6 +120,21 @@ public static class ConfigStorage implements Serializable { */ private int maxRetryTimes = 5; + /** + * 连接超时时间,单位毫秒 + */ + private int connectionTimeout = 5000; + + /** + * 读数据超时时间,即socketTimeout,单位毫秒 + */ + private int soTimeout = 5000; + + /** + * 从连接池获取链接的超时时间,单位毫秒 + */ + private int connectionRequestTimeout = 5000; + } public enum StorageType { diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/README.md b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/README.md new file mode 100644 index 0000000000..1ae4ac6299 --- /dev/null +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/README.md @@ -0,0 +1,317 @@ +# wx-java-pay-multi-spring-boot-starter + +## 快速开始 + +本starter支持微信支付多公众号关联配置,适用于以下场景: +- 一个服务商需要为多个公众号提供支付服务 +- 一个系统需要支持多个公众号的支付业务 +- 需要根据不同的appId动态切换支付配置 + +## 使用说明 + +### 1. 引入依赖 + +在项目的 `pom.xml` 中添加以下依赖: + +```xml + + com.github.binarywang + wx-java-pay-multi-spring-boot-starter + ${version} + +``` + +### 2. 添加配置 + +在 `application.yml` 或 `application.properties` 中配置多个公众号的支付信息。 + +#### 配置示例(application.yml) + +##### V2版本配置 +```yml +wx: + pay: + configs: + # 配置1 - 可以使用appId作为key + wx1234567890abcdef: + appId: wx1234567890abcdef + mchId: 1234567890 + mchKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + keyPath: classpath:cert/app1/apiclient_cert.p12 + notifyUrl: https://example.com/pay/notify + # 配置2 - 也可以使用自定义标识作为key + config2: + appId: wx9876543210fedcba + mchId: 9876543210 + mchKey: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + keyPath: classpath:cert/app2/apiclient_cert.p12 + notifyUrl: https://example.com/pay/notify +``` + +##### V3版本配置 +```yml +wx: + pay: + configs: + # 公众号1配置 + wx1234567890abcdef: + appId: wx1234567890abcdef + mchId: 1234567890 + apiV3Key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + certSerialNo: 62C6CEAA360BCxxxxxxxxxxxxxxx + privateKeyPath: classpath:cert/app1/apiclient_key.pem + privateCertPath: classpath:cert/app1/apiclient_cert.pem + notifyUrl: https://example.com/pay/notify + # 公众号2配置 + wx9876543210fedcba: + appId: wx9876543210fedcba + mchId: 9876543210 + apiV3Key: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + certSerialNo: 73D7DFBB471CDxxxxxxxxxxxxxxx + privateKeyPath: classpath:cert/app2/apiclient_key.pem + privateCertPath: classpath:cert/app2/apiclient_cert.pem + notifyUrl: https://example.com/pay/notify +``` + +##### V3服务商版本配置 +```yml +wx: + pay: + configs: + # 服务商为公众号1提供服务 + config1: + appId: wxe97b2x9c2b3d # 服务商appId + mchId: 16486610 # 服务商商户号 + subAppId: wx118cexxe3c07679 # 子商户公众号appId + subMchId: 16496705 # 子商户号 + apiV3Key: Dc1DBwSc094jAKDGR5aqqb7PTHr + privateKeyPath: classpath:cert/apiclient_key.pem + privateCertPath: classpath:cert/apiclient_cert.pem + # 服务商为公众号2提供服务 + config2: + appId: wxe97b2x9c2b3d # 服务商appId(可以相同) + mchId: 16486610 # 服务商商户号(可以相同) + subAppId: wx228dexxf4d18890 # 子商户公众号appId(不同) + subMchId: 16496706 # 子商户号(不同) + apiV3Key: Dc1DBwSc094jAKDGR5aqqb7PTHr + privateKeyPath: classpath:cert/apiclient_key.pem + privateCertPath: classpath:cert/apiclient_cert.pem +``` + +#### 配置示例(application.properties) + +```properties +# 公众号1配置 +wx.pay.configs.wx1234567890abcdef.app-id=wx1234567890abcdef +wx.pay.configs.wx1234567890abcdef.mch-id=1234567890 +wx.pay.configs.wx1234567890abcdef.api-v3-key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +wx.pay.configs.wx1234567890abcdef.cert-serial-no=62C6CEAA360BCxxxxxxxxxxxxxxx +wx.pay.configs.wx1234567890abcdef.private-key-path=classpath:cert/app1/apiclient_key.pem +wx.pay.configs.wx1234567890abcdef.private-cert-path=classpath:cert/app1/apiclient_cert.pem +wx.pay.configs.wx1234567890abcdef.notify-url=https://example.com/pay/notify + +# 公众号2配置 +wx.pay.configs.wx9876543210fedcba.app-id=wx9876543210fedcba +wx.pay.configs.wx9876543210fedcba.mch-id=9876543210 +wx.pay.configs.wx9876543210fedcba.api-v3-key=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy +wx.pay.configs.wx9876543210fedcba.cert-serial-no=73D7DFBB471CDxxxxxxxxxxxxxxx +wx.pay.configs.wx9876543210fedcba.private-key-path=classpath:cert/app2/apiclient_key.pem +wx.pay.configs.wx9876543210fedcba.private-cert-path=classpath:cert/app2/apiclient_cert.pem +wx.pay.configs.wx9876543210fedcba.notify-url=https://example.com/pay/notify +``` + +### 3. 使用示例 + +自动注入的类型:`WxPayMultiServices` + +```java +import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServices; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; +import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result; +import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result; +import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.service.WxPayService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class PayService { + @Autowired + private WxPayMultiServices wxPayMultiServices; + + /** + * 为不同的公众号创建支付订单 + * + * @param configKey 配置标识(即 wx.pay.configs.<configKey> 中的 key,可以是 appId 或自定义标识) + */ + public void createOrder(String configKey, String openId, Integer totalFee, String body) throws Exception { + // 根据配置标识获取对应的WxPayService + WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey); + + if (wxPayService == null) { + throw new IllegalArgumentException("未找到配置标识对应的微信支付配置: " + configKey); + } + + // 使用WxPayService进行支付操作 + WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); + request.setOutTradeNo(generateOutTradeNo()); + request.setDescription(body); + request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(totalFee)); + request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(openId)); + request.setNotifyUrl(wxPayService.getConfig().getNotifyUrl()); + + // V3统一下单 + WxPayUnifiedOrderV3Result.JsapiResult result = + wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request); + + // 返回给前端用于调起支付 + // ... + } + + /** + * 服务商模式示例 + */ + public void serviceProviderExample(String configKey) throws Exception { + // 使用配置标识获取WxPayService + WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey); + + if (wxPayService == null) { + throw new IllegalArgumentException("未找到配置: " + configKey); + } + + // 获取子商户的配置信息 + String subAppId = wxPayService.getConfig().getSubAppId(); + String subMchId = wxPayService.getConfig().getSubMchId(); + + // 进行支付操作 + // ... + } + + /** + * 查询订单示例 + * + * @param configKey 配置标识(即 wx.pay.configs.<configKey> 中的 key) + */ + public void queryOrder(String configKey, String outTradeNo) throws Exception { + WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey); + + if (wxPayService == null) { + throw new IllegalArgumentException("未找到配置标识对应的微信支付配置: " + configKey); + } + + // 查询订单 + WxPayOrderQueryV3Result result = wxPayService.queryOrderV3(null, outTradeNo); + // 处理查询结果 + // ... + } + + private String generateOutTradeNo() { + // 生成商户订单号 + return "ORDER_" + System.currentTimeMillis(); + } +} +``` + +### 4. 配置说明 + +#### 必填配置项 + +| 配置项 | 说明 | 示例 | +|--------|------|------| +| appId | 公众号或小程序的appId | wx1234567890abcdef | +| mchId | 商户号 | 1234567890 | + +#### V2版本配置项 + +| 配置项 | 说明 | 是否必填 | +|--------|------|----------| +| mchKey | 商户密钥 | 是(V2) | +| keyPath | p12证书文件路径 | 部分接口需要 | + +#### V3版本配置项 + +| 配置项 | 说明 | 是否必填 | +|--------|------|----------| +| apiV3Key | API V3密钥 | 是(V3) | +| certSerialNo | 证书序列号 | 是(V3) | +| privateKeyPath | apiclient_key.pem路径 | 是(V3) | +| privateCertPath | apiclient_cert.pem路径 | 是(V3) | + +#### 服务商模式配置项 + +| 配置项 | 说明 | 是否必填 | +|--------|------|----------| +| subAppId | 子商户公众号appId | 服务商模式必填 | +| subMchId | 子商户号 | 服务商模式必填 | + +#### 可选配置项 + +| 配置项 | 说明 | 默认值 | +|--------|------|--------| +| notifyUrl | 支付结果通知URL | 无 | +| refundNotifyUrl | 退款结果通知URL | 无 | +| serviceId | 微信支付分serviceId | 无 | +| payScoreNotifyUrl | 支付分回调地址 | 无 | +| payScorePermissionNotifyUrl | 支付分授权回调地址 | 无 | +| useSandboxEnv | 是否使用沙箱环境 | false | +| apiHostUrl | 自定义API主机地址 | https://api.mch.weixin.qq.com | +| apiHostUrlPath | 自定义API主机路径前缀(代理入口前缀) | 空 | +| strictlyNeedWechatPaySerial | 是否所有V3请求都添加序列号头 | true | +| fullPublicKeyModel | 是否完全使用公钥模式 | true | +| publicKeyId | 公钥ID | 无 | +| publicKeyPath | 公钥文件路径 | 无 | + +## 常见问题 + +### 1. 如何选择配置的key? + +配置的key(即 `wx.pay.configs.` 中的 `` 部分)可以自由选择: +- 可以使用appId作为key(如 `wx.pay.configs.wx1234567890abcdef`),这样调用 `getWxPayService("wx1234567890abcdef")` 时就像直接用 appId 获取服务 +- 可以使用自定义标识(如 `wx.pay.configs.config1`),调用时使用 `getWxPayService("config1")` + +**注意**:`getWxPayService(configKey)` 方法的参数是配置文件中定义的 key,而不是 appId。只有当你使用 appId 作为配置 key 时,才能直接传入 appId。 + +### 2. V2和V3配置可以混用吗? + +可以。不同的配置可以使用不同的版本,例如: +```yml +wx: + pay: + configs: + app1: # V2配置 + appId: wx111 + mchId: 111 + mchKey: xxx + app2: # V3配置 + appId: wx222 + mchId: 222 + apiV3Key: yyy + privateKeyPath: xxx +``` + +### 3. 证书文件如何放置? + +证书文件可以放在以下位置: +- `src/main/resources` 目录下,使用 `classpath:` 前缀 +- 服务器绝对路径,直接填写完整路径 +- 建议为不同配置使用不同的目录组织证书 + +### 4. 服务商模式如何配置? + +服务商模式需要同时配置服务商信息和子商户信息: +- `appId` 和 `mchId` 填写服务商的信息 +- `subAppId` 和 `subMchId` 填写子商户的信息 + +## 注意事项 + +1. **配置安全**:生产环境中的密钥、证书等敏感信息,建议使用配置中心或环境变量管理 +2. **证书管理**:不同公众号的证书文件要分开存放,避免混淆 +3. **懒加载**:WxPayService 实例采用懒加载策略,只有在首次调用时才会创建 +4. **线程安全**:WxPayMultiServices 的实现是线程安全的 +5. **配置更新**:如需动态更新配置,可调用 `removeWxPayService(configKey)` 方法移除缓存的实例 + +## 更多信息 + +- [WxJava 项目首页](https://github.com/Wechat-Group/WxJava) +- [微信支付V2文档](https://pay.weixin.qq.com/doc/v2) +- [微信支付V3接口文档](https://pay.weixin.qq.com/doc/v3/merchant/4012062524) diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/pom.xml new file mode 100644 index 0000000000..04ea639588 --- /dev/null +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/pom.xml @@ -0,0 +1,53 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.6.B + + 4.0.0 + + wx-java-pay-multi-spring-boot-starter + WxJava - Spring Boot Starter for Pay::支持多公众号关联配置 + 微信支付开发的 Spring Boot Starter::支持多公众号关联配置 + + + + com.github.binarywang + weixin-java-pay + ${project.version} + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayMultiAutoConfiguration.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayMultiAutoConfiguration.java new file mode 100644 index 0000000000..08ddafbf9c --- /dev/null +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayMultiAutoConfiguration.java @@ -0,0 +1,38 @@ +package com.binarywang.spring.starter.wxjava.pay.config; + +import com.binarywang.spring.starter.wxjava.pay.properties.WxPayMultiProperties; +import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServices; +import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServicesImpl; +import com.github.binarywang.wxpay.service.WxPayService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 微信支付多公众号关联自动配置. + * + * @author Binary Wang + */ +@Slf4j +@Configuration +@EnableConfigurationProperties(WxPayMultiProperties.class) +@ConditionalOnClass(WxPayService.class) +@ConditionalOnProperty(prefix = WxPayMultiProperties.PREFIX, value = "enabled", matchIfMissing = true) +public class WxPayMultiAutoConfiguration { + + /** + * 构造微信支付多服务管理对象. + * + * @param wxPayMultiProperties 多配置属性 + * @return 微信支付多服务管理对象 + */ + @Bean + @ConditionalOnMissingBean(WxPayMultiServices.class) + public WxPayMultiServices wxPayMultiServices(WxPayMultiProperties wxPayMultiProperties) { + return new WxPayMultiServicesImpl(wxPayMultiProperties); + } +} diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayMultiProperties.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayMultiProperties.java new file mode 100644 index 0000000000..8d1180b0e4 --- /dev/null +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayMultiProperties.java @@ -0,0 +1,27 @@ +package com.binarywang.spring.starter.wxjava.pay.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * 微信支付多公众号关联配置属性类. + * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +@ConfigurationProperties(WxPayMultiProperties.PREFIX) +public class WxPayMultiProperties implements Serializable { + private static final long serialVersionUID = -8015955705346835955L; + public static final String PREFIX = "wx.pay"; + + /** + * 多个公众号的配置信息,key 可以是 appId 或自定义的标识. + */ + private Map configs = new HashMap<>(); +} diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPaySingleProperties.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPaySingleProperties.java new file mode 100644 index 0000000000..ef936fc234 --- /dev/null +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPaySingleProperties.java @@ -0,0 +1,130 @@ +package com.binarywang.spring.starter.wxjava.pay.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 微信支付单个公众号配置属性类. + * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +public class WxPaySingleProperties implements Serializable { + private static final long serialVersionUID = 3978986361098922525L; + + /** + * 设置微信公众号或者小程序等的appid. + */ + private String appId; + + /** + * 微信支付商户号. + */ + private String mchId; + + /** + * 微信支付商户密钥. + */ + private String mchKey; + + /** + * 服务商模式下的子商户公众账号ID,普通模式请不要配置. + */ + private String subAppId; + + /** + * 服务商模式下的子商户号,普通模式请不要配置. + */ + private String subMchId; + + /** + * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定. + */ + private String keyPath; + + /** + * 微信支付分serviceId. + */ + private String serviceId; + + /** + * 证书序列号. + */ + private String certSerialNo; + + /** + * apiV3秘钥. + */ + private String apiV3Key; + + /** + * 微信支付异步回调地址,通知url必须为直接可访问的url,不能携带参数. + */ + private String notifyUrl; + + /** + * 退款结果异步回调地址,通知url必须为直接可访问的url,不能携带参数. + */ + private String refundNotifyUrl; + + /** + * 微信支付分回调地址. + */ + private String payScoreNotifyUrl; + + /** + * 微信支付分授权回调地址. + */ + private String payScorePermissionNotifyUrl; + + /** + * apiv3 商户apiclient_key.pem. + */ + private String privateKeyPath; + + /** + * apiv3 商户apiclient_cert.pem. + */ + private String privateCertPath; + + /** + * 公钥ID. + */ + private String publicKeyId; + + /** + * pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径. + */ + private String publicKeyPath; + + /** + * 微信支付是否使用仿真测试环境. + * 默认不使用. + */ + private boolean useSandboxEnv = false; + + /** + * 自定义API主机地址,用于替换默认的 https://api.mch.weixin.qq.com. + * 例如:http://proxy.company.com:8080 + */ + private String apiHostUrl; + + /** + * 自定义API主机路径前缀(用于代理入口前缀). + * 例如:/api-weixin + */ + private String apiHostUrlPath; + + /** + * 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认添加. + */ + private boolean strictlyNeedWechatPaySerial = true; + + /** + * 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认使用. + */ + private boolean fullPublicKeyModel = true; +} diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServices.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServices.java new file mode 100644 index 0000000000..3e0b7a999f --- /dev/null +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServices.java @@ -0,0 +1,33 @@ +package com.binarywang.spring.starter.wxjava.pay.service; + +import com.github.binarywang.wxpay.service.WxPayService; + +/** + * 微信支付 {@link WxPayService} 所有实例存放类. + * + * @author Binary Wang + */ +public interface WxPayMultiServices { + /** + * 通过配置标识获取 WxPayService. + *

+ * 注意:configKey 是配置文件中定义的 key(如 wx.pay.configs.<configKey>.xxx), + * 而不是 appId。如果使用 appId 作为配置 key,则可以直接传入 appId。 + *

+ * + * @param configKey 配置标识(配置文件中 wx.pay.configs 下的 key) + * @return WxPayService + */ + WxPayService getWxPayService(String configKey); + + /** + * 根据配置标识,从列表中移除一个 WxPayService 实例. + *

+ * 注意:configKey 是配置文件中定义的 key(如 wx.pay.configs.<configKey>.xxx), + * 而不是 appId。如果使用 appId 作为配置 key,则可以直接传入 appId。 + *

+ * + * @param configKey 配置标识(配置文件中 wx.pay.configs 下的 key) + */ + void removeWxPayService(String configKey); +} diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServicesImpl.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServicesImpl.java new file mode 100644 index 0000000000..7cbcceabb4 --- /dev/null +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServicesImpl.java @@ -0,0 +1,93 @@ +package com.binarywang.spring.starter.wxjava.pay.service; + +import com.binarywang.spring.starter.wxjava.pay.properties.WxPayMultiProperties; +import com.binarywang.spring.starter.wxjava.pay.properties.WxPaySingleProperties; +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 微信支付多服务管理实现类. + * + * @author Binary Wang + */ +@Slf4j +public class WxPayMultiServicesImpl implements WxPayMultiServices { + private final Map services = new ConcurrentHashMap<>(); + private final WxPayMultiProperties wxPayMultiProperties; + + public WxPayMultiServicesImpl(WxPayMultiProperties wxPayMultiProperties) { + this.wxPayMultiProperties = wxPayMultiProperties; + } + + @Override + public WxPayService getWxPayService(String configKey) { + if (StringUtils.isBlank(configKey)) { + log.warn("配置标识为空,无法获取WxPayService"); + return null; + } + + // 使用 computeIfAbsent 实现线程安全的懒加载,避免使用 synchronized(this) 带来的性能问题 + return services.computeIfAbsent(configKey, key -> { + WxPaySingleProperties properties = wxPayMultiProperties.getConfigs().get(key); + if (properties == null) { + log.warn("未找到配置标识为[{}]的微信支付配置", key); + return null; + } + return this.buildWxPayService(properties); + }); + } + + @Override + public void removeWxPayService(String configKey) { + if (StringUtils.isBlank(configKey)) { + log.warn("配置标识为空,无法移除WxPayService"); + return; + } + services.remove(configKey); + } + + /** + * 根据配置构建 WxPayService. + * + * @param properties 单个配置属性 + * @return WxPayService + */ + private WxPayService buildWxPayService(WxPaySingleProperties properties) { + WxPayServiceImpl wxPayService = new WxPayServiceImpl(); + WxPayConfig payConfig = new WxPayConfig(); + + payConfig.setAppId(StringUtils.trimToNull(properties.getAppId())); + payConfig.setMchId(StringUtils.trimToNull(properties.getMchId())); + payConfig.setMchKey(StringUtils.trimToNull(properties.getMchKey())); + payConfig.setSubAppId(StringUtils.trimToNull(properties.getSubAppId())); + payConfig.setSubMchId(StringUtils.trimToNull(properties.getSubMchId())); + payConfig.setKeyPath(StringUtils.trimToNull(properties.getKeyPath())); + payConfig.setUseSandboxEnv(properties.isUseSandboxEnv()); + payConfig.setNotifyUrl(StringUtils.trimToNull(properties.getNotifyUrl())); + payConfig.setRefundNotifyUrl(StringUtils.trimToNull(properties.getRefundNotifyUrl())); + + // 以下是apiv3以及支付分相关 + payConfig.setServiceId(StringUtils.trimToNull(properties.getServiceId())); + payConfig.setPayScoreNotifyUrl(StringUtils.trimToNull(properties.getPayScoreNotifyUrl())); + payConfig.setPayScorePermissionNotifyUrl(StringUtils.trimToNull(properties.getPayScorePermissionNotifyUrl())); + payConfig.setPrivateKeyPath(StringUtils.trimToNull(properties.getPrivateKeyPath())); + payConfig.setPrivateCertPath(StringUtils.trimToNull(properties.getPrivateCertPath())); + payConfig.setCertSerialNo(StringUtils.trimToNull(properties.getCertSerialNo())); + payConfig.setApiV3Key(StringUtils.trimToNull(properties.getApiV3Key())); + payConfig.setPublicKeyId(StringUtils.trimToNull(properties.getPublicKeyId())); + payConfig.setPublicKeyPath(StringUtils.trimToNull(properties.getPublicKeyPath())); + payConfig.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl())); + payConfig.setApiHostUrlPath(StringUtils.trimToNull(properties.getApiHostUrlPath())); + payConfig.setStrictlyNeedWechatPaySerial(properties.isStrictlyNeedWechatPaySerial()); + payConfig.setFullPublicKeyModel(properties.isFullPublicKeyModel()); + + wxPayService.setConfig(payConfig); + return wxPayService; + } +} diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..d257d37276 --- /dev/null +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.binarywang.spring.starter.wxjava.pay.config.WxPayMultiAutoConfiguration diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..39e3342f4a --- /dev/null +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +com.binarywang.spring.starter.wxjava.pay.config.WxPayMultiAutoConfiguration + diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/WxPayMultiServicesTest.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/WxPayMultiServicesTest.java new file mode 100644 index 0000000000..87132fdcf3 --- /dev/null +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/WxPayMultiServicesTest.java @@ -0,0 +1,109 @@ +package com.binarywang.spring.starter.wxjava.pay; + +import com.binarywang.spring.starter.wxjava.pay.config.WxPayMultiAutoConfiguration; +import com.binarywang.spring.starter.wxjava.pay.properties.WxPayMultiProperties; +import com.binarywang.spring.starter.wxjava.pay.properties.WxPaySingleProperties; +import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServices; +import com.github.binarywang.wxpay.service.WxPayService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 微信支付多公众号关联配置测试. + * + * @author Binary Wang + */ +@SpringBootTest(classes = {WxPayMultiAutoConfiguration.class, WxPayMultiServicesTest.TestApplication.class}) +@TestPropertySource(properties = { + "wx.pay.configs.app1.app-id=wx1111111111111111", + "wx.pay.configs.app1.mch-id=1111111111", + "wx.pay.configs.app1.mch-key=11111111111111111111111111111111", + "wx.pay.configs.app1.notify-url=https://example.com/pay/notify", + "wx.pay.configs.app2.app-id=wx2222222222222222", + "wx.pay.configs.app2.mch-id=2222222222", + "wx.pay.configs.app2.api-host-url=http://10.0.0.1:3128", + "wx.pay.configs.app2.api-host-url-path=/api-weixin", + "wx.pay.configs.app2.apiv3-key=22222222222222222222222222222222", + "wx.pay.configs.app2.cert-serial-no=2222222222222222", + "wx.pay.configs.app2.private-key-path=classpath:cert/apiclient_key.pem", + "wx.pay.configs.app2.private-cert-path=classpath:cert/apiclient_cert.pem" +}) +public class WxPayMultiServicesTest { + + @Autowired + private WxPayMultiServices wxPayMultiServices; + + @Autowired + private WxPayMultiProperties wxPayMultiProperties; + + @Test + public void testConfiguration() { + assertNotNull(wxPayMultiServices, "WxPayMultiServices should be autowired"); + assertNotNull(wxPayMultiProperties, "WxPayMultiProperties should be autowired"); + + // 验证配置正确加载 + assertEquals(2, wxPayMultiProperties.getConfigs().size(), "Should have 2 configurations"); + + WxPaySingleProperties app1Config = wxPayMultiProperties.getConfigs().get("app1"); + assertNotNull(app1Config, "app1 configuration should exist"); + assertEquals("wx1111111111111111", app1Config.getAppId()); + assertEquals("1111111111", app1Config.getMchId()); + assertEquals("11111111111111111111111111111111", app1Config.getMchKey()); + + WxPaySingleProperties app2Config = wxPayMultiProperties.getConfigs().get("app2"); + assertNotNull(app2Config, "app2 configuration should exist"); + assertEquals("wx2222222222222222", app2Config.getAppId()); + assertEquals("2222222222", app2Config.getMchId()); + assertEquals("http://10.0.0.1:3128", app2Config.getApiHostUrl()); + assertEquals("/api-weixin", app2Config.getApiHostUrlPath()); + assertEquals("22222222222222222222222222222222", app2Config.getApiV3Key()); + } + + @Test + public void testGetWxPayService() { + WxPayService app1Service = wxPayMultiServices.getWxPayService("app1"); + assertNotNull(app1Service, "Should get WxPayService for app1"); + assertEquals("wx1111111111111111", app1Service.getConfig().getAppId()); + assertEquals("1111111111", app1Service.getConfig().getMchId()); + + WxPayService app2Service = wxPayMultiServices.getWxPayService("app2"); + assertNotNull(app2Service, "Should get WxPayService for app2"); + assertEquals("wx2222222222222222", app2Service.getConfig().getAppId()); + assertEquals("2222222222", app2Service.getConfig().getMchId()); + assertEquals("/api-weixin", app2Service.getConfig().getApiHostUrlPath()); + + // 测试相同key返回相同实例 + WxPayService app1ServiceAgain = wxPayMultiServices.getWxPayService("app1"); + assertSame(app1Service, app1ServiceAgain, "Should return the same instance for the same key"); + } + + @Test + public void testGetWxPayServiceWithInvalidKey() { + WxPayService service = wxPayMultiServices.getWxPayService("nonexistent"); + assertNull(service, "Should return null for non-existent key"); + } + + @Test + public void testRemoveWxPayService() { + // 首先获取一个服务实例 + WxPayService app1Service = wxPayMultiServices.getWxPayService("app1"); + assertNotNull(app1Service, "Should get WxPayService for app1"); + + // 移除服务 + wxPayMultiServices.removeWxPayService("app1"); + + // 再次获取时应该创建新实例 + WxPayService app1ServiceNew = wxPayMultiServices.getWxPayService("app1"); + assertNotNull(app1ServiceNew, "Should get new WxPayService for app1"); + assertNotSame(app1Service, app1ServiceNew, "Should return a new instance after removal"); + } + + @SpringBootApplication + static class TestApplication { + } +} diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/example/WxPayMultiExample.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/example/WxPayMultiExample.java new file mode 100644 index 0000000000..48ae32d5b4 --- /dev/null +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/example/WxPayMultiExample.java @@ -0,0 +1,249 @@ +package com.binarywang.spring.starter.wxjava.pay.example; + +import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServices; +import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request; +import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result; +import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result; +import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result; +import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.service.WxPayService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * 微信支付多公众号关联使用示例. + *

+ * 本示例展示了如何使用 wx-java-pay-multi-spring-boot-starter 来管理多个公众号的支付配置。 + *

+ * + * @author Binary Wang + */ +@Slf4j +@Service +public class WxPayMultiExample { + + @Autowired + private WxPayMultiServices wxPayMultiServices; + + /** + * 示例1:根据appId创建支付订单. + *

+ * 适用场景:系统需要支持多个公众号,根据用户所在的公众号动态选择支付配置 + *

+ * + * @param appId 公众号appId + * @param openId 用户的openId + * @param totalFee 支付金额(分) + * @param body 商品描述 + * @return JSAPI支付参数 + */ + public WxPayUnifiedOrderV3Result.JsapiResult createJsapiOrder(String appId, String openId, + Integer totalFee, String body) { + try { + // 根据appId获取对应的WxPayService + WxPayService wxPayService = wxPayMultiServices.getWxPayService(appId); + + if (wxPayService == null) { + log.error("未找到appId对应的微信支付配置: {}", appId); + throw new IllegalArgumentException("未找到appId对应的微信支付配置"); + } + + // 构建支付请求 + WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); + request.setOutTradeNo(generateOutTradeNo()); + request.setDescription(body); + request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(totalFee)); + request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(openId)); + request.setNotifyUrl(wxPayService.getConfig().getNotifyUrl()); + + // 调用微信支付API创建订单 + WxPayUnifiedOrderV3Result.JsapiResult result = + wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request); + + log.info("创建JSAPI支付订单成功,appId: {}, outTradeNo: {}", appId, request.getOutTradeNo()); + return result; + + } catch (Exception e) { + log.error("创建JSAPI支付订单失败,appId: {}", appId, e); + throw new RuntimeException("创建支付订单失败", e); + } + } + + /** + * 示例2:服务商模式 - 为不同子商户创建订单. + *

+ * 适用场景:服务商为多个子商户提供支付服务 + *

+ * + * @param configKey 配置标识(在配置文件中定义) + * @param subOpenId 子商户用户的openId + * @param totalFee 支付金额(分) + * @param body 商品描述 + * @return JSAPI支付参数 + */ + public WxPayUnifiedOrderV3Result.JsapiResult createPartnerOrder(String configKey, String subOpenId, + Integer totalFee, String body) { + try { + // 根据配置标识获取WxPayService + WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey); + + if (wxPayService == null) { + log.error("未找到配置: {}", configKey); + throw new IllegalArgumentException("未找到配置"); + } + + // 获取子商户信息 + String subAppId = wxPayService.getConfig().getSubAppId(); + String subMchId = wxPayService.getConfig().getSubMchId(); + log.info("使用服务商模式,子商户appId: {}, 子商户号: {}", subAppId, subMchId); + + // 构建支付请求 + WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); + request.setOutTradeNo(generateOutTradeNo()); + request.setDescription(body); + request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(totalFee)); + request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(subOpenId)); + request.setNotifyUrl(wxPayService.getConfig().getNotifyUrl()); + + // 调用微信支付API创建订单 + WxPayUnifiedOrderV3Result.JsapiResult result = + wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request); + + log.info("创建服务商支付订单成功,配置: {}, outTradeNo: {}", configKey, request.getOutTradeNo()); + return result; + + } catch (Exception e) { + log.error("创建服务商支付订单失败,配置: {}", configKey, e); + throw new RuntimeException("创建支付订单失败", e); + } + } + + /** + * 示例3:查询订单状态. + *

+ * 适用场景:查询不同公众号的订单支付状态 + *

+ * + * @param appId 公众号appId + * @param outTradeNo 商户订单号 + * @return 订单状态 + */ + public String queryOrderStatus(String appId, String outTradeNo) { + try { + WxPayService wxPayService = wxPayMultiServices.getWxPayService(appId); + + if (wxPayService == null) { + log.error("未找到appId对应的微信支付配置: {}", appId); + throw new IllegalArgumentException("未找到appId对应的微信支付配置"); + } + + // 查询订单 + WxPayOrderQueryV3Result result = wxPayService.queryOrderV3(null, outTradeNo); + String tradeState = result.getTradeState(); + + log.info("查询订单状态成功,appId: {}, outTradeNo: {}, 状态: {}", appId, outTradeNo, tradeState); + return tradeState; + + } catch (Exception e) { + log.error("查询订单状态失败,appId: {}, outTradeNo: {}", appId, outTradeNo, e); + throw new RuntimeException("查询订单失败", e); + } + } + + /** + * 示例4:申请退款. + *

+ * 适用场景:为不同公众号的订单申请退款 + *

+ * + * @param appId 公众号appId + * @param outTradeNo 商户订单号 + * @param refundFee 退款金额(分) + * @param totalFee 订单总金额(分) + * @param reason 退款原因 + * @return 退款单号 + */ + public String refund(String appId, String outTradeNo, Integer refundFee, + Integer totalFee, String reason) { + try { + WxPayService wxPayService = wxPayMultiServices.getWxPayService(appId); + + if (wxPayService == null) { + log.error("未找到appId对应的微信支付配置: {}", appId); + throw new IllegalArgumentException("未找到appId对应的微信支付配置"); + } + + // 构建退款请求 + com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request request = + new com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request(); + request.setOutTradeNo(outTradeNo); + request.setOutRefundNo(generateRefundNo()); + request.setReason(reason); + request.setNotifyUrl(wxPayService.getConfig().getRefundNotifyUrl()); + + com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request.Amount amount = + new com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request.Amount(); + amount.setRefund(refundFee); + amount.setTotal(totalFee); + amount.setCurrency("CNY"); + request.setAmount(amount); + + // 调用微信支付API申请退款 + WxPayRefundV3Result result = wxPayService.refundV3(request); + + log.info("申请退款成功,appId: {}, outTradeNo: {}, outRefundNo: {}", + appId, outTradeNo, request.getOutRefundNo()); + return request.getOutRefundNo(); + + } catch (Exception e) { + log.error("申请退款失败,appId: {}, outTradeNo: {}", appId, outTradeNo, e); + throw new RuntimeException("申请退款失败", e); + } + } + + /** + * 示例5:动态管理配置. + *

+ * 适用场景:需要在运行时更新配置(如证书更新后需要重新加载) + *

+ * + * @param configKey 配置标识 + */ + public void reloadConfig(String configKey) { + try { + // 移除缓存的WxPayService实例 + wxPayMultiServices.removeWxPayService(configKey); + log.info("移除配置成功,下次获取时将重新创建: {}", configKey); + + // 下次调用 getWxPayService 时会重新创建实例 + WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey); + if (wxPayService != null) { + log.info("重新加载配置成功: {}", configKey); + } + + } catch (Exception e) { + log.error("重新加载配置失败: {}", configKey, e); + throw new RuntimeException("重新加载配置失败", e); + } + } + + /** + * 生成商户订单号. + * + * @return 商户订单号 + */ + private String generateOutTradeNo() { + return "ORDER_" + System.currentTimeMillis(); + } + + /** + * 生成商户退款单号. + * + * @return 商户退款单号 + */ + private String generateRefundNo() { + return "REFUND_" + System.currentTimeMillis(); + } +} diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/README.md b/spring-boot-starters/wx-java-pay-spring-boot-starter/README.md index 8d96901f24..bed890d5e8 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 @@ -15,8 +15,6 @@ wx: appId: mchId: mchKey: - subAppId: - subMchId: keyPath: ``` ###### 2)V3版本 @@ -25,15 +23,23 @@ wx: pay: appId: xxxxxxxxxxx mchId: 15xxxxxxxxx #商户id + apiHostUrl: http://10.0.0.1:3128 # 可选:代理主机 + apiHostUrlPath: /api-weixin # 可选:代理入口前缀 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 14d9268575..6625201696 100644 --- a/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.5.3.B + 4.8.6.B 4.0.0 diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java index 2dd44004a6..7e748ba1a3 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,13 +49,23 @@ 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.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.setApiHostUrlPath(StringUtils.trimToNull(this.properties.getApiHostUrlPath())); + 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 940cdf5916..49045c4ee0 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 @@ -57,13 +57,28 @@ public class WxPayProperties { /** * apiV3秘钥 */ - private String apiv3Key; + private String apiV3Key; + + /** + * 微信支付异步回调地址,通知url必须为直接可访问的url,不能携带参数 + */ + private String notifyUrl; + + /** + * 退款结果异步回调地址,通知url必须为直接可访问的url,不能携带参数 + */ + private String refundNotifyUrl; /** * 微信支付分回调地址 */ private String payScoreNotifyUrl; + /** + * 微信支付分授权回调地址 + */ + private String payScorePermissionNotifyUrl; + /** * apiv3 商户apiclient_key.pem */ @@ -74,4 +89,42 @@ public class WxPayProperties { */ 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; + + /** + * 自定义API主机路径前缀(用于代理入口前缀) + * 例如:/api-weixin + */ + private String apiHostUrlPath; + + /** + * 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认添加 + */ + private boolean strictlyNeedWechatPaySerial = true; + + /** + * 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认使用 + */ + private boolean fullPublicKeyModel = true; + } 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 index d676616de6..34069fa1fe 100644 --- a/spring-boot-starters/wx-java-qidian-spring-boot-starter/README.md +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/README.md @@ -13,33 +13,33 @@ 2. 添加配置(application.properties) ```properties # 公众号配置(必填) - wx.mp.appId = appId - wx.mp.secret = @secret - wx.mp.token = @token - wx.mp.aesKey = @aesKey + wx.qidian.appId = appId + wx.qidian.secret = @secret + wx.qidian.token = @token + wx.qidian.aesKey = @aesKey # 存储配置redis(可选) - wx.mp.config-storage.type = Jedis # 配置类型: 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 + 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.mp.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379 - #wx.mp.config-storage.redis.sentinel-name=mymaster + #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.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= + 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.mp.hosts.api-host=http://proxy.com/ - #wx.mp.hosts.open-host=http://proxy.com/ - #wx.mp.hosts.mp-host=http://proxy.com/ + #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. 自动注入的类型 -- `WxMpService` -- `WxMpConfigStorage` +- `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 index 6b119d9df8..d34e2de0f6 100644 --- a/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/pom.xml @@ -3,7 +3,7 @@ wx-java-spring-boot-starters com.github.binarywang - 4.5.3.B + 4.8.6.B 4.0.0 @@ -20,11 +20,14 @@ redis.clients jedis + 4.3.2 compile org.springframework.data spring-data-redis + ${spring-data-redis.version} + true provided diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java index 84163b005a..01ba91b565 100644 --- a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/config/WxQidianStorageAutoConfiguration.java @@ -20,10 +20,11 @@ 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.JedisPoolAbstract; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisSentinelPool; +import redis.clients.jedis.util.Pool; import java.util.Set; @@ -80,7 +81,7 @@ private WxQidianConfigStorage defaultConfigStorage() { } private WxQidianConfigStorage jedisConfigStorage() { - JedisPoolAbstract jedisPool; + Pool jedisPool; if (StringUtils.isNotEmpty(redisHost) || StringUtils.isNotEmpty(redisHost2)) { jedisPool = getJedisPool(); } else { @@ -136,7 +137,7 @@ private void setWxMpInfo(WxQidianDefaultConfigImpl config) { } } - private JedisPoolAbstract getJedisPool() { + private Pool getJedisPool() { WxQidianProperties.ConfigStorage storage = wxQidianProperties.getConfigStorage(); RedisProperties redis = storage.getRedis(); @@ -156,8 +157,9 @@ private JedisPoolAbstract getJedisPool() { config.setTestOnBorrow(true); config.setTestWhileIdle(true); if (StringUtils.isNotEmpty(redis.getSentinelIps())) { + Set sentinels = Sets.newHashSet(redis.getSentinelIps().split(",")); - return new JedisSentinelPool(redis.getSentinelName(), sentinels); + return new JedisSentinelPool(redis.getSentinelName(), sentinels,config); } return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java index 1a927211cc..04589a911b 100644 --- a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/enums/HttpClientType.java @@ -19,4 +19,8 @@ public enum HttpClientType { * JoddHttp. */ JoddHttp, + /** + * HttpComponents. + */ + HttpComponents, } diff --git a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java index ddecefb7e2..bec5dfcce0 100644 --- a/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java +++ b/spring-boot-starters/wx-java-qidian-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/qidian/properties/WxQidianProperties.java @@ -72,7 +72,7 @@ public static class ConfigStorage implements Serializable { /** * http客户端类型. */ - private HttpClientType httpClientType = HttpClientType.HttpClient; + private HttpClientType httpClientType = HttpClientType.HttpComponents; /** * http代理主机. diff --git a/spring-boot-starters/wx-java-store-multi-spring-boot-starter/README.md b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/README.md new file mode 100644 index 0000000000..ecbe645a7b --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/README.md @@ -0,0 +1,123 @@ +# wx-java-store-multi-spring-boot-starter + +## 快速开始 + +1. 引入依赖 + ```xml + + + com.github.binarywang + wx-java-store-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.store.apps.tenantId1.app-id=@appId + wx.store.apps.tenantId1.secret=@secret + ## 选填 + wx.store.apps.tenantId1.use-stable-access-token=false + wx.store.apps.tenantId1.token= + wx.store.apps.tenantId1.aes-key= + ## 应用 2 配置(必填) + wx.store.apps.tenantId2.app-id=@appId + wx.store.apps.tenantId2.secret=@secret + ## 选填 + wx.store.apps.tenantId2.use-stable-access-token=false + wx.store.apps.tenantId2.token= + wx.store.apps.tenantId2.aes-key= + + # ConfigStorage 配置(选填) + ## 配置类型: memory(默认), jedis, redisson, redis_template + wx.store.config-storage.type=memory + ## 相关redis前缀配置: wx:store:multi(默认) + wx.store.config-storage.key-prefix=wx:store:multi + wx.store.config-storage.redis.host=127.0.0.1 + wx.store.config-storage.redis.port=6379 + wx.store.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.store.config-storage.http-client-type=http_client + wx.store.config-storage.http-proxy-host= + wx.store.config-storage.http-proxy-port= + wx.store.config-storage.http-proxy-username= + wx.store.config-storage.http-proxy-password= + ## 最大重试次数,默认:5 次,如果小于 0,则为 0 + wx.store.config-storage.max-retry-times=5 + ## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 + wx.store.config-storage.retry-sleep-millis=1000 + ``` +3. 自动注入的类型:`WxStoreMultiServices` + +4. 使用样例 + + ```java + import com.binarywang.spring.starter.wxjava.store.service.WxStoreMultiServices; + import com.binarywang.wxjava.store.api.WxStoreService; + import com.binarywang.wxjava.store.api.WxFinderLiveService; + import com.binarywang.wxjava.store.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 WxStoreMultiServices wxStoreMultiServices; + + public void test() throws WxErrorException { + // 应用 1 的 WxStoreService + WxStoreService wxStoreService1 = wxStoreMultiServices.getWxStoreService("tenantId1"); + WxFinderLiveService finderLiveService = wxStoreService1.getFinderLiveService(); + FinderAttrResponse response1 = finderLiveService.getFinderAttrByAppid(); + // todo ... + + // 应用 2 的 WxStoreService + WxStoreService wxStoreService2 = wxStoreMultiServices.getWxStoreService("tenantId2"); + WxFinderLiveService finderLiveService2 = wxStoreService2.getFinderLiveService(); + FinderAttrResponse response2 = finderLiveService2.getFinderAttrByAppid(); + // todo ... + + // 应用 3 的 WxStoreService + WxStoreService wxStoreService3 = wxStoreMultiServices.getWxStoreService("tenantId3"); + // 判断是否为空 + if (wxStoreService3 == null) { + // todo wxStoreService3 为空,请先配置 tenantId3 微信小店应用参数 + return; + } + WxFinderLiveService finderLiveService3 = wxStoreService3.getFinderLiveService(); + FinderAttrResponse response3 = finderLiveService3.getFinderAttrByAppid(); + // todo ... + } + } + ``` diff --git a/spring-boot-starters/wx-java-store-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/pom.xml new file mode 100644 index 0000000000..157073c876 --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/pom.xml @@ -0,0 +1,73 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.6.B + + 4.0.0 + + wx-java-store-multi-spring-boot-starter + WxJava - Spring Boot Starter for Store::支持多账号配置 + 微信小店开发的 Spring Boot Starter::支持多账号配置 + + + + com.github.binarywang + weixin-java-store + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.springframework.data + spring-data-redis + ${spring-data-redis.version} + true + 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-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/autoconfigure/WxStoreMultiAutoConfiguration.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/autoconfigure/WxStoreMultiAutoConfiguration.java new file mode 100644 index 0000000000..72b51c6cef --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/autoconfigure/WxStoreMultiAutoConfiguration.java @@ -0,0 +1,15 @@ +package com.binarywang.spring.starter.wxjava.store.autoconfigure; + +import com.binarywang.spring.starter.wxjava.store.configuration.WxStoreMultiServiceConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 微信小店自动注册 + * + * @author Winnie + * @date 2024/9/13 + */ +@Configuration +@Import(WxStoreMultiServiceConfiguration.class) +public class WxStoreMultiAutoConfiguration {} diff --git a/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/WxStoreMultiServiceConfiguration.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/WxStoreMultiServiceConfiguration.java new file mode 100644 index 0000000000..1a0dfd492a --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/WxStoreMultiServiceConfiguration.java @@ -0,0 +1,21 @@ +package com.binarywang.spring.starter.wxjava.store.configuration; + +import com.binarywang.spring.starter.wxjava.store.configuration.services.WxStoreInJedisConfiguration; +import com.binarywang.spring.starter.wxjava.store.configuration.services.WxStoreInMemoryConfiguration; +import com.binarywang.spring.starter.wxjava.store.configuration.services.WxStoreInRedisTemplateConfiguration; +import com.binarywang.spring.starter.wxjava.store.configuration.services.WxStoreInRedissonConfiguration; +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreMultiProperties; +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(WxStoreMultiProperties.class) +@Import({WxStoreInJedisConfiguration.class, WxStoreInMemoryConfiguration.class, WxStoreInRedissonConfiguration.class, WxStoreInRedisTemplateConfiguration.class}) +public class WxStoreMultiServiceConfiguration {} diff --git a/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/AbstractWxStoreConfiguration.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/AbstractWxStoreConfiguration.java new file mode 100644 index 0000000000..d082348e9f --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/AbstractWxStoreConfiguration.java @@ -0,0 +1,148 @@ +package com.binarywang.spring.starter.wxjava.store.configuration.services; + +import com.binarywang.spring.starter.wxjava.store.enums.HttpClientType; +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreMultiProperties; +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreSingleProperties; +import com.binarywang.spring.starter.wxjava.store.service.WxStoreMultiServices; +import com.binarywang.spring.starter.wxjava.store.service.WxStoreMultiServicesImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import com.binarywang.wxjava.store.api.WxStoreService; +import com.binarywang.wxjava.store.api.impl.WxStoreServiceHttpClientImpl; +import com.binarywang.wxjava.store.api.impl.WxStoreServiceHttpComponentsImpl; +import com.binarywang.wxjava.store.api.impl.WxStoreServiceImpl; +import com.binarywang.wxjava.store.config.WxStoreConfig; +import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * WxStoreConfigStorage 抽象配置类 + * + * @author Winnie + * @date 2024/9/13 + */ +@RequiredArgsConstructor +@Slf4j +public abstract class AbstractWxStoreConfiguration { + protected WxStoreMultiServices wxStoreMultiServices(WxStoreMultiProperties wxStoreMultiProperties) { + Map appsMap = wxStoreMultiProperties.getApps(); + if (appsMap == null || appsMap.isEmpty()) { + log.warn("微信小店应用参数未配置,通过 WxStoreMultiServices#getWxStoreService(\"tenantId\")获取实例将返回空"); + return new WxStoreMultiServicesImpl(); + } + /** + * 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。 + * + * 查看 {@link com.binarywang.wxjava.store.config.impl.WxStoreRedisConfigImpl#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 的唯一性"); + } + } + WxStoreMultiServicesImpl services = new WxStoreMultiServicesImpl(); + + Set> entries = appsMap.entrySet(); + for (Map.Entry entry : entries) { + String tenantId = entry.getKey(); + WxStoreSingleProperties wxStoreSingleProperties = entry.getValue(); + WxStoreDefaultConfigImpl storage = this.wxStoreConfigStorage(wxStoreMultiProperties); + this.configApp(storage, wxStoreSingleProperties); + this.configHttp(storage, wxStoreMultiProperties.getConfigStorage()); + WxStoreService wxStoreService = this.wxStoreService(storage, wxStoreMultiProperties); + services.addWxStoreService(tenantId, wxStoreService); + } + return services; + } + + /** + * 配置 WxStoreDefaultConfigImpl + * + * @param wxStoreMultiProperties 参数 + * @return WxStoreDefaultConfigImpl + */ + protected abstract WxStoreDefaultConfigImpl wxStoreConfigStorage(WxStoreMultiProperties wxStoreMultiProperties); + + public WxStoreService wxStoreService(WxStoreConfig wxStoreConfig, WxStoreMultiProperties wxStoreMultiProperties) { + WxStoreMultiProperties.ConfigStorage storage = wxStoreMultiProperties.getConfigStorage(); + HttpClientType httpClientType = storage.getHttpClientType(); + WxStoreService wxStoreService; + switch (httpClientType) { +// case OK_HTTP: +// wxStoreService = new WxStoreServiceOkHttpImpl(false, false); +// break; + case HTTP_CLIENT: + wxStoreService = new WxStoreServiceHttpClientImpl(); + break; + case HTTP_COMPONENTS: + wxStoreService = new WxStoreServiceHttpComponentsImpl(); + break; + default: + wxStoreService = new WxStoreServiceImpl(); + break; + } + + wxStoreService.setConfig(wxStoreConfig); + int maxRetryTimes = storage.getMaxRetryTimes(); + if (maxRetryTimes < 0) { + maxRetryTimes = 0; + } + int retrySleepMillis = storage.getRetrySleepMillis(); + if (retrySleepMillis < 0) { + retrySleepMillis = 1000; + } + wxStoreService.setRetrySleepMillis(retrySleepMillis); + wxStoreService.setMaxRetryTimes(maxRetryTimes); + return wxStoreService; + } + + private void configApp(WxStoreDefaultConfigImpl config, WxStoreSingleProperties wxStoreSingleProperties) { + String appId = wxStoreSingleProperties.getAppId(); + String appSecret = wxStoreSingleProperties.getSecret(); + String token = wxStoreSingleProperties.getToken(); + String aesKey = wxStoreSingleProperties.getAesKey(); + boolean useStableAccessToken = wxStoreSingleProperties.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(wxStoreSingleProperties.getApiHostUrl())); + config.setAccessTokenUrl(StringUtils.trimToNull(wxStoreSingleProperties.getAccessTokenUrl())); + } + + private void configHttp(WxStoreDefaultConfigImpl config, WxStoreMultiProperties.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-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/WxStoreInJedisConfiguration.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/WxStoreInJedisConfiguration.java new file mode 100644 index 0000000000..de8d904276 --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/WxStoreInJedisConfiguration.java @@ -0,0 +1,74 @@ +package com.binarywang.spring.starter.wxjava.store.configuration.services; + +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreMultiProperties; +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.store.service.WxStoreMultiServices; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl; +import com.binarywang.wxjava.store.config.impl.WxStoreRedisConfigImpl; +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 = WxStoreMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis") +@RequiredArgsConstructor +public class WxStoreInJedisConfiguration extends AbstractWxStoreConfiguration { + private final WxStoreMultiProperties wxStoreMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxStoreMultiServices wxStoreMultiServices() { + return this.wxStoreMultiServices(wxStoreMultiProperties); + } + + @Override + protected WxStoreDefaultConfigImpl wxStoreConfigStorage(WxStoreMultiProperties wxStoreMultiProperties) { + return this.configRedis(wxStoreMultiProperties); + } + + private WxStoreDefaultConfigImpl configRedis(WxStoreMultiProperties wxStoreMultiProperties) { + WxStoreMultiRedisProperties wxStoreMultiRedisProperties = wxStoreMultiProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (wxStoreMultiRedisProperties != null && StringUtils.isNotEmpty(wxStoreMultiRedisProperties.getHost())) { + jedisPool = getJedisPool(wxStoreMultiProperties); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + return new WxStoreRedisConfigImpl(new JedisWxRedisOps(jedisPool), wxStoreMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool(WxStoreMultiProperties wxStoreMultiProperties) { + WxStoreMultiProperties.ConfigStorage storage = wxStoreMultiProperties.getConfigStorage(); + WxStoreMultiRedisProperties 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-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/WxStoreInMemoryConfiguration.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/WxStoreInMemoryConfiguration.java new file mode 100644 index 0000000000..999b27d861 --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/WxStoreInMemoryConfiguration.java @@ -0,0 +1,36 @@ +package com.binarywang.spring.starter.wxjava.store.configuration.services; + +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreMultiProperties; +import com.binarywang.spring.starter.wxjava.store.service.WxStoreMultiServices; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl; +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 = WxStoreMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "memory", matchIfMissing = true) +@RequiredArgsConstructor +public class WxStoreInMemoryConfiguration extends AbstractWxStoreConfiguration { + private final WxStoreMultiProperties wxStoreMultiProperties; + + @Bean + public WxStoreMultiServices wxStoreMultiServices() { + return this.wxStoreMultiServices(wxStoreMultiProperties); + } + + @Override + protected WxStoreDefaultConfigImpl wxStoreConfigStorage(WxStoreMultiProperties wxStoreMultiProperties) { + return this.configInMemory(); + } + + private WxStoreDefaultConfigImpl configInMemory() { + return new WxStoreDefaultConfigImpl(); + } +} diff --git a/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/WxStoreInRedisTemplateConfiguration.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/WxStoreInRedisTemplateConfiguration.java new file mode 100644 index 0000000000..03be7e3f75 --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/WxStoreInRedisTemplateConfiguration.java @@ -0,0 +1,42 @@ +package com.binarywang.spring.starter.wxjava.store.configuration.services; + +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreMultiProperties; +import com.binarywang.spring.starter.wxjava.store.service.WxStoreMultiServices; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl; +import com.binarywang.wxjava.store.config.impl.WxStoreRedisConfigImpl; +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 = WxStoreMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redis_template") +@RequiredArgsConstructor +public class WxStoreInRedisTemplateConfiguration extends AbstractWxStoreConfiguration { + private final WxStoreMultiProperties wxStoreMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxStoreMultiServices wxStoreMultiServices() { + return this.wxStoreMultiServices(wxStoreMultiProperties); + } + + @Override + protected WxStoreDefaultConfigImpl wxStoreConfigStorage(WxStoreMultiProperties wxStoreMultiProperties) { + return this.configRedisTemplate(wxStoreMultiProperties); + } + + private WxStoreDefaultConfigImpl configRedisTemplate(WxStoreMultiProperties wxStoreMultiProperties) { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + return new WxStoreRedisConfigImpl(new RedisTemplateWxRedisOps(redisTemplate), wxStoreMultiProperties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/WxStoreInRedissonConfiguration.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/WxStoreInRedissonConfiguration.java new file mode 100644 index 0000000000..25a3568cf6 --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/configuration/services/WxStoreInRedissonConfiguration.java @@ -0,0 +1,62 @@ +package com.binarywang.spring.starter.wxjava.store.configuration.services; + +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreMultiProperties; +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreMultiRedisProperties; +import com.binarywang.spring.starter.wxjava.store.service.WxStoreMultiServices; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl; +import com.binarywang.wxjava.store.config.impl.WxStoreRedissonConfigImpl; +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 = WxStoreMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson") +@RequiredArgsConstructor +public class WxStoreInRedissonConfiguration extends AbstractWxStoreConfiguration { + private final WxStoreMultiProperties wxStoreMultiProperties; + private final ApplicationContext applicationContext; + + @Bean + public WxStoreMultiServices wxStoreMultiServices() { + return this.wxStoreMultiServices(wxStoreMultiProperties); + } + + @Override + protected WxStoreDefaultConfigImpl wxStoreConfigStorage(WxStoreMultiProperties wxStoreMultiProperties) { + return this.configRedisson(wxStoreMultiProperties); + } + + private WxStoreDefaultConfigImpl configRedisson(WxStoreMultiProperties wxStoreMultiProperties) { + WxStoreMultiRedisProperties redisProperties = wxStoreMultiProperties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(wxStoreMultiProperties); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxStoreRedissonConfigImpl(redissonClient, wxStoreMultiProperties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient(WxStoreMultiProperties wxStoreMultiProperties) { + WxStoreMultiProperties.ConfigStorage storage = wxStoreMultiProperties.getConfigStorage(); + WxStoreMultiRedisProperties 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-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/enums/HttpClientType.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/enums/HttpClientType.java new file mode 100644 index 0000000000..4e8159c600 --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/enums/HttpClientType.java @@ -0,0 +1,23 @@ +package com.binarywang.spring.starter.wxjava.store.enums; + +/** + * httpclient类型 + * + * @author Winnie + * @date 2024/9/13 + */ +public enum HttpClientType { + /** + * HttpClient. + */ + HTTP_CLIENT, + // WxStoreServiceOkHttpImpl 实现经测试无法正常完成业务固暂不支持OK_HTTP方式 +// /** +// * OkHttp. +// */ +// OK_HTTP, + /** + * HttpComponents. + */ + HTTP_COMPONENTS, +} diff --git a/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/enums/StorageType.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/enums/StorageType.java new file mode 100644 index 0000000000..b57d88da75 --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/enums/StorageType.java @@ -0,0 +1,26 @@ +package com.binarywang.spring.starter.wxjava.store.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-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/WxStoreMultiProperties.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/WxStoreMultiProperties.java new file mode 100644 index 0000000000..a958753a61 --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/WxStoreMultiProperties.java @@ -0,0 +1,96 @@ +package com.binarywang.spring.starter.wxjava.store.properties; + +import com.binarywang.spring.starter.wxjava.store.enums.HttpClientType; +import com.binarywang.spring.starter.wxjava.store.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(WxStoreMultiProperties.PREFIX) +public class WxStoreMultiProperties implements Serializable { + private static final long serialVersionUID = - 8361973118805546037L; + public static final String PREFIX = "wx.store"; + + 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:store:multi"; + + /** + * redis连接配置. + */ + @NestedConfigurationProperty + private final WxStoreMultiRedisProperties redis = new WxStoreMultiRedisProperties(); + + /** + * 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 com.binarywang.wxjava.store.api.WxStoreService#setMaxRetryTimes(int)}

+ *

{@link com.binarywang.wxjava.store.api.impl.BaseWxStoreServiceImpl#setMaxRetryTimes(int)}

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

{@link com.binarywang.wxjava.store.api.WxStoreService#setRetrySleepMillis(int)}

+ *

{@link com.binarywang.wxjava.store.api.impl.BaseWxStoreServiceImpl#setRetrySleepMillis(int)}

+ */ + private int retrySleepMillis = 1000; + } +} diff --git a/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/WxStoreMultiRedisProperties.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/WxStoreMultiRedisProperties.java new file mode 100644 index 0000000000..828325c3bf --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/WxStoreMultiRedisProperties.java @@ -0,0 +1,63 @@ +package com.binarywang.spring.starter.wxjava.store.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * Redis配置 + * + * @author Winnie + * @date 2024/9/13 + */ +@Data +@NoArgsConstructor +public class WxStoreMultiRedisProperties 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-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/WxStoreSingleProperties.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/WxStoreSingleProperties.java new file mode 100644 index 0000000000..335147c7b7 --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/WxStoreSingleProperties.java @@ -0,0 +1,55 @@ +package com.binarywang.spring.starter.wxjava.store.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 微信小店相关配置属性 + * + * @author Winnie + * @date 2024/9/13 + */ +@Data +@NoArgsConstructor +public class WxStoreSingleProperties 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-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/service/WxStoreMultiServices.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/service/WxStoreMultiServices.java new file mode 100644 index 0000000000..ff5192ac48 --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/service/WxStoreMultiServices.java @@ -0,0 +1,26 @@ +package com.binarywang.spring.starter.wxjava.store.service; + +import com.binarywang.wxjava.store.api.WxStoreService; + +/** + * 视频号 {@link WxStoreService} 所有实例存放类. + * + * @author Winnie + * @date 2024/9/13 + */ +public interface WxStoreMultiServices { + /** + * 通过租户 Id 获取 WxStoreService + * + * @param tenantId 租户 Id + * @return WxStoreService + */ + WxStoreService getWxStoreService(String tenantId); + + /** + * 根据租户 Id,从列表中移除一个 WxStoreService 实例 + * + * @param tenantId 租户 Id + */ + void removeWxStoreService(String tenantId); +} diff --git a/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/service/WxStoreMultiServicesImpl.java b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/service/WxStoreMultiServicesImpl.java new file mode 100644 index 0000000000..44fe0f947d --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/service/WxStoreMultiServicesImpl.java @@ -0,0 +1,36 @@ +package com.binarywang.spring.starter.wxjava.store.service; + +import com.binarywang.wxjava.store.api.WxStoreService; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 视频号 {@link WxStoreMultiServices} 实现 + * + * @author Winnie + * @date 2024/9/13 + */ +public class WxStoreMultiServicesImpl implements WxStoreMultiServices { + private final Map services = new ConcurrentHashMap<>(); + + @Override + public WxStoreService getWxStoreService(String tenantId) { + return this.services.get(tenantId); + } + + /** + * 根据租户 Id,添加一个 WxStoreService 到列表 + * + * @param tenantId 租户 Id + * @param wxStoreService WxStoreService 实例 + */ + public void addWxStoreService(String tenantId, WxStoreService wxStoreService) { + this.services.put(tenantId, wxStoreService); + } + + @Override + public void removeWxStoreService(String tenantId) { + this.services.remove(tenantId); + } +} diff --git a/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..40c16383ac --- /dev/null +++ b/spring-boot-starters/wx-java-store-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.store.autoconfigure.WxStoreMultiAutoConfiguration diff --git a/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..485736df0a --- /dev/null +++ b/spring-boot-starters/wx-java-store-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.store.autoconfigure.WxStoreMultiAutoConfiguration diff --git a/spring-boot-starters/wx-java-store-spring-boot-starter/README.md b/spring-boot-starters/wx-java-store-spring-boot-starter/README.md new file mode 100644 index 0000000000..f601db67d1 --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/README.md @@ -0,0 +1,102 @@ +# wx-java-store-spring-boot-starter + +## 快速开始 +1. 引入依赖 + ```xml + + + com.github.binarywang + wx-java-store-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.store.app-id=@appId + wx.store.secret=@secret + # 视频号配置 选填 + ## 设置微信小店消息服务器配置的token + wx.store.token=@token + ## 设置微信小店消息服务器配置的EncodingAESKey + wx.store.aes-key= + ## 支持JSON或者XML格式,默认JSON + wx.store.msg-data-format=JSON + ## 是否使用稳定版 Access Token + wx.store.use-stable-access-token=false + + + # ConfigStorage 配置(选填) + ## 配置类型: memory(默认), jedis, redisson, redis_template + wx.store.config-storage.type=memory + ## 相关redis前缀配置: wx:store(默认) + wx.store.config-storage.key-prefix=wx:store + wx.store.config-storage.redis.host=127.0.0.1 + wx.store.config-storage.redis.port=6379 + wx.store.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.store.config-storage.http-client-type=http_client + wx.store.config-storage.http-proxy-host= + wx.store.config-storage.http-proxy-port= + wx.store.config-storage.http-proxy-username= + wx.store.config-storage.http-proxy-password= + ## 最大重试次数,默认:5 次,如果小于 0,则为 0 + wx.store.config-storage.max-retry-times=5 + ## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000 + wx.store.config-storage.retry-sleep-millis=1000 + ``` +3. 自动注入的类型 +- `WxStoreService` +- `WxStoreConfig` +4. 使用样例 +```java +import com.binarywang.wxjava.store.api.WxStoreService; +import com.binarywang.wxjava.store.bean.shop.ShopInfoResponse; +import com.binarywang.wxjava.store.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 WxStoreService wxStoreService; + + public String getShopInfo() throws WxErrorException { + // 获取店铺基本信息 + ShopInfoResponse response = wxStoreService.getBasicService().getShopInfo(); + // 此处为演示,如果要返回response的结果,建议自己封装一个VO,避免直接返回response + return JsonUtils.encode(response); + } +} +``` diff --git a/spring-boot-starters/wx-java-store-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-store-spring-boot-starter/pom.xml new file mode 100644 index 0000000000..b59dbae37e --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/pom.xml @@ -0,0 +1,61 @@ + + + wx-java-spring-boot-starters + com.github.binarywang + 4.8.6.B + + 4.0.0 + + wx-java-store-spring-boot-starter + WxJava - Spring Boot Starter for Store + 微信小店开发的 Spring Boot Starter + + + + com.github.binarywang + weixin-java-store + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.springframework.data + spring-data-redis + ${spring-data-redis.version} + true + 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-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/WxStoreAutoConfiguration.java b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/WxStoreAutoConfiguration.java new file mode 100644 index 0000000000..28f7e0b48f --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/WxStoreAutoConfiguration.java @@ -0,0 +1,20 @@ +package com.binarywang.spring.starter.wxjava.store.config; + +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 自动配置 + * + * @author Zeyes + */ +@Configuration +@EnableConfigurationProperties(WxStoreProperties.class) +@Import({ + WxStoreStorageAutoConfiguration.class, + WxStoreServiceAutoConfiguration.class +}) +public class WxStoreAutoConfiguration { +} diff --git a/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/WxStoreServiceAutoConfiguration.java b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/WxStoreServiceAutoConfiguration.java new file mode 100644 index 0000000000..c93857c04e --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/WxStoreServiceAutoConfiguration.java @@ -0,0 +1,41 @@ +package com.binarywang.spring.starter.wxjava.store.config; + + +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreProperties; +import com.binarywang.spring.starter.wxjava.store.enums.HttpClientType; +import lombok.AllArgsConstructor; +import com.binarywang.wxjava.store.api.WxStoreService; +import com.binarywang.wxjava.store.api.impl.WxStoreServiceHttpClientImpl; +import com.binarywang.wxjava.store.api.impl.WxStoreServiceHttpComponentsImpl; +import com.binarywang.wxjava.store.config.WxStoreConfig; +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 WxStoreServiceAutoConfiguration { + private final WxStoreProperties properties; + + /** + * Store Service + * + * @return Store Service + */ + @Bean + @ConditionalOnMissingBean(WxStoreService.class) + @ConditionalOnBean(WxStoreConfig.class) + public WxStoreService wxStoreService(WxStoreConfig wxStoreConfig) { + HttpClientType httpClientType = properties.getConfigStorage().getHttpClientType(); + WxStoreService wxStoreService = httpClientType == HttpClientType.HttpClient + ? new WxStoreServiceHttpClientImpl() : new WxStoreServiceHttpComponentsImpl(); + wxStoreService.setConfig(wxStoreConfig); + return wxStoreService; + } +} diff --git a/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/WxStoreStorageAutoConfiguration.java b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/WxStoreStorageAutoConfiguration.java new file mode 100644 index 0000000000..3867ad8bb6 --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/WxStoreStorageAutoConfiguration.java @@ -0,0 +1,23 @@ +package com.binarywang.spring.starter.wxjava.store.config; + +import com.binarywang.spring.starter.wxjava.store.config.storage.WxStoreInJedisConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.store.config.storage.WxStoreInMemoryConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.store.config.storage.WxStoreInRedisTemplateConfigStorageConfiguration; +import com.binarywang.spring.starter.wxjava.store.config.storage.WxStoreInRedissonConfigStorageConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * 微信小程序存储策略自动配置 + * + * @author Zeyes + */ +@Configuration +@Import({ + WxStoreInMemoryConfigStorageConfiguration.class, + WxStoreInJedisConfigStorageConfiguration.class, + WxStoreInRedisTemplateConfigStorageConfiguration.class, + WxStoreInRedissonConfigStorageConfiguration.class +}) +public class WxStoreStorageAutoConfiguration { +} diff --git a/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/AbstractWxStoreConfigStorageConfiguration.java b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/AbstractWxStoreConfigStorageConfiguration.java new file mode 100644 index 0000000000..95452d4e47 --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/AbstractWxStoreConfigStorageConfiguration.java @@ -0,0 +1,42 @@ +package com.binarywang.spring.starter.wxjava.store.config.storage; + +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreProperties; +import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl; +import org.apache.commons.lang3.StringUtils; + +/** + * @author Zeyes + */ +public abstract class AbstractWxStoreConfigStorageConfiguration { + + protected WxStoreDefaultConfigImpl config(WxStoreDefaultConfigImpl config, WxStoreProperties 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())); + + WxStoreProperties.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-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/WxStoreInJedisConfigStorageConfiguration.java b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/WxStoreInJedisConfigStorageConfiguration.java new file mode 100644 index 0000000000..c474c5f6bd --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/WxStoreInJedisConfigStorageConfiguration.java @@ -0,0 +1,73 @@ +package com.binarywang.spring.starter.wxjava.store.config.storage; + + +import com.binarywang.spring.starter.wxjava.store.properties.RedisProperties; +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreProperties; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.WxStoreConfig; +import com.binarywang.wxjava.store.config.impl.WxStoreRedisConfigImpl; +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 = WxStoreProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis") +@ConditionalOnClass({JedisPool.class, JedisPoolConfig.class}) +@RequiredArgsConstructor +public class WxStoreInJedisConfigStorageConfiguration extends AbstractWxStoreConfigStorageConfiguration { + private final WxStoreProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxStoreConfig.class) + public WxStoreConfig wxStoreConfig() { + WxStoreRedisConfigImpl config = getWxStoreRedisConfig(); + return this.config(config, properties); + } + + private WxStoreRedisConfigImpl getWxStoreRedisConfig() { + 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 WxStoreRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } + + private JedisPool getJedisPool() { + WxStoreProperties.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-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/WxStoreInMemoryConfigStorageConfiguration.java b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/WxStoreInMemoryConfigStorageConfiguration.java new file mode 100644 index 0000000000..59802c8774 --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/WxStoreInMemoryConfigStorageConfiguration.java @@ -0,0 +1,29 @@ +package com.binarywang.spring.starter.wxjava.store.config.storage; + + +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreProperties; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.WxStoreConfig; +import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl; +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 = WxStoreProperties.PREFIX + ".config-storage", name = "type", + matchIfMissing = true, havingValue = "memory") +@RequiredArgsConstructor +public class WxStoreInMemoryConfigStorageConfiguration extends AbstractWxStoreConfigStorageConfiguration { + private final WxStoreProperties properties; + + @Bean + @ConditionalOnMissingBean(WxStoreConfig.class) + public WxStoreConfig wxStoreConfig() { + WxStoreDefaultConfigImpl config = new WxStoreDefaultConfigImpl(); + return this.config(config, properties); + } +} diff --git a/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/WxStoreInRedisTemplateConfigStorageConfiguration.java b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/WxStoreInRedisTemplateConfigStorageConfiguration.java new file mode 100644 index 0000000000..4bbcbb05bc --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/WxStoreInRedisTemplateConfigStorageConfiguration.java @@ -0,0 +1,40 @@ +package com.binarywang.spring.starter.wxjava.store.config.storage; + +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreProperties; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.WxStoreConfig; +import com.binarywang.wxjava.store.config.impl.WxStoreRedisConfigImpl; +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 = WxStoreProperties.PREFIX + ".config-storage", name = "type", havingValue = "redis_template") +@ConditionalOnClass(StringRedisTemplate.class) +@RequiredArgsConstructor +public class WxStoreInRedisTemplateConfigStorageConfiguration extends AbstractWxStoreConfigStorageConfiguration { + private final WxStoreProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxStoreConfig.class) + public WxStoreConfig wxStoreConfig() { + WxStoreRedisConfigImpl config = getWxStoreInRedisTemplateConfig(); + return this.config(config, properties); + } + + private WxStoreRedisConfigImpl getWxStoreInRedisTemplateConfig() { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate); + return new WxStoreRedisConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix()); + } +} diff --git a/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/WxStoreInRedissonConfigStorageConfiguration.java b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/WxStoreInRedissonConfigStorageConfiguration.java new file mode 100644 index 0000000000..5eacb98d91 --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/config/storage/WxStoreInRedissonConfigStorageConfiguration.java @@ -0,0 +1,62 @@ +package com.binarywang.spring.starter.wxjava.store.config.storage; + + +import com.binarywang.spring.starter.wxjava.store.properties.RedisProperties; +import com.binarywang.spring.starter.wxjava.store.properties.WxStoreProperties; +import lombok.RequiredArgsConstructor; +import com.binarywang.wxjava.store.config.WxStoreConfig; +import com.binarywang.wxjava.store.config.impl.WxStoreRedissonConfigImpl; +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 = WxStoreProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson") +@ConditionalOnClass({Redisson.class, RedissonClient.class}) +@RequiredArgsConstructor +public class WxStoreInRedissonConfigStorageConfiguration extends AbstractWxStoreConfigStorageConfiguration { + private final WxStoreProperties properties; + private final ApplicationContext applicationContext; + + @Bean + @ConditionalOnMissingBean(WxStoreConfig.class) + public WxStoreConfig wxStoreConfig() { + WxStoreRedissonConfigImpl config = getWxStoreRedissonConfig(); + return this.config(config, properties); + } + + private WxStoreRedissonConfigImpl getWxStoreRedissonConfig() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); + } + return new WxStoreRedissonConfigImpl(redissonClient, properties.getConfigStorage().getKeyPrefix()); + } + + private RedissonClient getRedissonClient() { + WxStoreProperties.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-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/enums/HttpClientType.java b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/enums/HttpClientType.java new file mode 100644 index 0000000000..5db42200d3 --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/enums/HttpClientType.java @@ -0,0 +1,17 @@ +package com.binarywang.spring.starter.wxjava.store.enums; + +/** + * httpclient类型 + * + * @author Zeyes + */ +public enum HttpClientType { + /** + * HttpClient. + */ + HttpClient, + /** + * HttpComponents. + */ + HttpComponents, +} diff --git a/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/enums/StorageType.java b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/enums/StorageType.java new file mode 100644 index 0000000000..21347d0da0 --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/enums/StorageType.java @@ -0,0 +1,25 @@ +package com.binarywang.spring.starter.wxjava.store.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-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/RedisProperties.java b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/RedisProperties.java new file mode 100644 index 0000000000..00d87c3bd9 --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/RedisProperties.java @@ -0,0 +1,42 @@ +package com.binarywang.spring.starter.wxjava.store.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-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/WxStoreProperties.java b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/WxStoreProperties.java new file mode 100644 index 0000000000..26b1e9d4ff --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/store/properties/WxStoreProperties.java @@ -0,0 +1,126 @@ +package com.binarywang.spring.starter.wxjava.store.properties; + +import com.binarywang.spring.starter.wxjava.store.enums.HttpClientType; +import com.binarywang.spring.starter.wxjava.store.enums.StorageType; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +/** + * 属性配置类 + * + * @author Zeyes + */ +@Data +@ConfigurationProperties(prefix = WxStoreProperties.PREFIX) +public class WxStoreProperties { + public static final String PREFIX = "wx.store"; + + /** + * 设置微信小店的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 com.binarywang.wxjava.store.api.BaseWxStoreService#setRetrySleepMillis(int)}
+     * 
+ */ + private int retrySleepMillis = 1000; + /** + * http 请求最大重试次数 + *
+     *   {@link com.binarywang.wxjava.store.api.BaseWxStoreService#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + } + +} diff --git a/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..cbf38ae2c2 --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.binarywang.spring.starter.wxjava.store.config.WxStoreAutoConfiguration diff --git a/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..80044c332a --- /dev/null +++ b/spring-boot-starters/wx-java-store-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.store.config.WxStoreAutoConfiguration diff --git a/weixin-graal/pom.xml b/weixin-graal/pom.xml index d95d6e86d4..f689fe886f 100644 --- a/weixin-graal/pom.xml +++ b/weixin-graal/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 4.5.3.B + 4.8.6.B weixin-graal diff --git a/weixin-java-aispeech/pom.xml b/weixin-java-aispeech/pom.xml new file mode 100644 index 0000000000..fdc8441990 --- /dev/null +++ b/weixin-java-aispeech/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + com.github.binarywang + wx-java + 4.8.6.B + + + weixin-java-aispeech + WxJava - Aispeech Java SDK + 微信智能对话 Java SDK + + + + com.github.binarywang + weixin-java-common + ${project.version} + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + org.testng + testng + test + + + org.projectlombok + lombok + + + + + + + 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-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechDialogService.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechDialogService.java new file mode 100644 index 0000000000..51d46562cb --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechDialogService.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.aispeech.api; + +import java.util.List; +import me.chanjar.weixin.aispeech.bean.dialog.AsyncTaskResult; +import me.chanjar.weixin.aispeech.bean.dialog.BotIntent; +import me.chanjar.weixin.aispeech.bean.dialog.DialogQueryRequest; +import me.chanjar.weixin.aispeech.bean.dialog.DialogResult; +import me.chanjar.weixin.aispeech.bean.dialog.PublishProgress; +import me.chanjar.weixin.common.error.WxErrorException; + +public interface WxAispeechDialogService { + String getAccessToken(String appid, String account) throws WxErrorException; + + String importBotJson(int mode, List data) throws WxErrorException; + + String publishBot() throws WxErrorException; + + PublishProgress getPublishProgress(String env) throws WxErrorException; + + AsyncTaskResult queryAsyncTask(String taskId) throws WxErrorException; + + DialogResult query(DialogQueryRequest request) throws WxErrorException; +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechKnowledgeService.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechKnowledgeService.java new file mode 100644 index 0000000000..fa27d48235 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechKnowledgeService.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.aispeech.api; + +import java.io.File; +import java.util.List; +import java.util.Map; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeInfo; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeManualCreateRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeMoveProgress; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeMoveRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeTagRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeUpdateRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeUrlCreateRequest; +import me.chanjar.weixin.common.error.WxErrorException; + +public interface WxAispeechKnowledgeService { + KnowledgeInfo createKnowledgeByFile(String knowledgeBaseId, File file, String title, String description, String metadata) + throws WxErrorException; + + KnowledgeInfo createKnowledgeByUrl(String knowledgeBaseId, KnowledgeUrlCreateRequest request) throws WxErrorException; + + KnowledgeInfo createKnowledgeByManual(String knowledgeBaseId, KnowledgeManualCreateRequest request) throws WxErrorException; + + List listKnowledge(String knowledgeBaseId, Integer page, Integer pageSize) throws WxErrorException; + + List listKnowledgeByIds(List knowledgeIds) throws WxErrorException; + + KnowledgeInfo getKnowledge(String knowledgeId) throws WxErrorException; + + KnowledgeInfo updateKnowledge(String knowledgeId, KnowledgeUpdateRequest request) throws WxErrorException; + + KnowledgeInfo updateManualKnowledge(String knowledgeId, KnowledgeManualCreateRequest request) throws WxErrorException; + + boolean deleteKnowledge(String knowledgeId) throws WxErrorException; + + boolean updateKnowledgeTags(List knowledgeIds, Long tagId) throws WxErrorException; + + List searchKnowledge(String keyword, String knowledgeBaseId, Integer page, Integer pageSize) + throws WxErrorException; + + String moveKnowledge(KnowledgeMoveRequest request) throws WxErrorException; + + KnowledgeMoveProgress getMoveProgress(String taskId) throws WxErrorException; + + boolean createKnowledgeBaseTag(String knowledgeBaseId, KnowledgeTagRequest request) throws WxErrorException; + + boolean updateKnowledgeBaseTag(String knowledgeBaseId, String tagId, KnowledgeTagRequest request) throws WxErrorException; + + String postRaw(String path, Object requestBody) throws WxErrorException; + + String getRaw(String path, Map queryParams) throws WxErrorException; +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechService.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechService.java new file mode 100644 index 0000000000..08ccf837e4 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/WxAispeechService.java @@ -0,0 +1,13 @@ +package me.chanjar.weixin.aispeech.api; + +import me.chanjar.weixin.aispeech.config.WxAispeechConfigStorage; + +public interface WxAispeechService { + WxAispeechDialogService getDialogService(); + + WxAispeechKnowledgeService getKnowledgeService(); + + WxAispeechConfigStorage getConfigStorage(); + + void setConfigStorage(WxAispeechConfigStorage configStorage); +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechDialogServiceImpl.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechDialogServiceImpl.java new file mode 100644 index 0000000000..9bd53b454e --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechDialogServiceImpl.java @@ -0,0 +1,129 @@ +package me.chanjar.weixin.aispeech.api.impl; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import me.chanjar.weixin.aispeech.api.WxAispeechDialogService; +import me.chanjar.weixin.aispeech.bean.dialog.AispeechApiResponse; +import me.chanjar.weixin.aispeech.bean.dialog.AsyncTaskResult; +import me.chanjar.weixin.aispeech.bean.dialog.BotIntent; +import me.chanjar.weixin.aispeech.bean.dialog.DialogQueryRequest; +import me.chanjar.weixin.aispeech.bean.dialog.DialogResult; +import me.chanjar.weixin.aispeech.bean.dialog.PublishProgress; +import me.chanjar.weixin.aispeech.util.WxAispeechSignUtil; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import org.apache.commons.lang3.StringUtils; + +public class WxAispeechDialogServiceImpl implements WxAispeechDialogService { + private final WxAispeechServiceImpl service; + + public WxAispeechDialogServiceImpl(WxAispeechServiceImpl service) { + this.service = service; + } + + @Override + public String getAccessToken(String appid, String account) throws WxErrorException { + Map request = new HashMap<>(); + if (StringUtils.isNotBlank(account)) { + request.put("account", account); + } + + String response = service.executeDialogPost("/v2/token", request, false, appid); + Type type = new TypeToken>() { } .getType(); + AispeechApiResponse result = WxGsonBuilder.create().fromJson(response, type); + ensureSuccess(result); + String token = result.getData().get("access_token").getAsString(); + service.getConfigStorage().setOpenAiToken(token); + return token; + } + + @Override + public String importBotJson(int mode, List data) throws WxErrorException { + Map request = new HashMap<>(); + request.put("mode", mode); + request.put("data", data); + + String response = service.executeDialogPost("/v2/bot/import/json", request, true, null); + Type type = new TypeToken>() { } .getType(); + AispeechApiResponse result = WxGsonBuilder.create().fromJson(response, type); + ensureSuccess(result); + return result.getData().get("task_id").getAsString(); + } + + @Override + public String publishBot() throws WxErrorException { + String response = service.executeDialogPost("/v2/bot/publish", "{}", true, null); + Type type = new TypeToken>() { } .getType(); + AispeechApiResponse result = WxGsonBuilder.create().fromJson(response, type); + ensureSuccess(result); + return result.getRequestId(); + } + + @Override + public PublishProgress getPublishProgress(String env) throws WxErrorException { + Map request = new HashMap<>(); + request.put("env", env); + + String response = service.executeDialogPost("/v2/bot/effective_progress", request, true, null); + Type type = new TypeToken>() { } .getType(); + AispeechApiResponse result = WxGsonBuilder.create().fromJson(response, type); + ensureSuccess(result); + return result.getData(); + } + + @Override + public AsyncTaskResult queryAsyncTask(String taskId) throws WxErrorException { + Map request = new HashMap<>(); + request.put("task_id", taskId); + + String response = service.executeDialogPost("/v2/async/fetch", request, true, null); + Type type = new TypeToken>() { } .getType(); + AispeechApiResponse result = WxGsonBuilder.create().fromJson(response, type); + ensureSuccess(result); + return result.getData(); + } + + @Override + public DialogResult query(DialogQueryRequest request) throws WxErrorException { + String json = WxGsonBuilder.create().toJson(request); + String encrypted = WxAispeechSignUtil.encryptAesCbcToBase64(json, service.getConfigStorage().getAesKey()); + String response = service.executeDialogPost("/v2/bot/query", encrypted, true, null); + + String responseJson = response; + if (!looksLikeJson(response)) { + responseJson = WxAispeechSignUtil.decryptAesCbcFromBase64(response, service.getConfigStorage().getAesKey()); + } + + Type type = new TypeToken>() { } .getType(); + AispeechApiResponse result = WxGsonBuilder.create().fromJson(responseJson, type); + ensureSuccess(result); + + DialogResult dialogResult = result.getData(); + if (dialogResult != null && looksLikeJson(dialogResult.getAnswer())) { + dialogResult.setRawAnswer(WxGsonBuilder.create().fromJson(dialogResult.getAnswer(), JsonElement.class)); + } + return dialogResult; + } + + private boolean looksLikeJson(String value) { + return StringUtils.isNotBlank(value) && (value.startsWith("{") || value.startsWith("[")); + } + + private void ensureSuccess(AispeechApiResponse response) throws WxErrorException { + if (response == null) { + throw new WxErrorException("响应为空"); + } + if (response.getCode() == null || response.getCode() != 0) { + throw new WxErrorException(WxError.builder() + .errorCode(response.getCode() == null ? -1 : response.getCode()) + .errorMsg(response.getMsg()) + .build()); + } + } +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechKnowledgeServiceImpl.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechKnowledgeServiceImpl.java new file mode 100644 index 0000000000..708f12890d --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechKnowledgeServiceImpl.java @@ -0,0 +1,184 @@ +package me.chanjar.weixin.aispeech.api.impl; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import java.io.File; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import me.chanjar.weixin.aispeech.api.WxAispeechKnowledgeService; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeInfo; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeListResult; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeManualCreateRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeMoveProgress; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeMoveRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeTagRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeUpdateRequest; +import me.chanjar.weixin.aispeech.bean.knowledge.KnowledgeUrlCreateRequest; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import org.apache.commons.lang3.StringUtils; + +public class WxAispeechKnowledgeServiceImpl implements WxAispeechKnowledgeService { + private final WxAispeechServiceImpl service; + + public WxAispeechKnowledgeServiceImpl(WxAispeechServiceImpl service) { + this.service = service; + } + + @Override + public KnowledgeInfo createKnowledgeByFile(String knowledgeBaseId, File file, String title, String description, String metadata) + throws WxErrorException { + String response = service.executeKnowledgeMultipartPost("/api/v1/knowledge-bases/" + knowledgeBaseId + "/knowledge/file", + file, title, description, metadata); + return WxGsonBuilder.create().fromJson(response, KnowledgeInfo.class); + } + + @Override + public KnowledgeInfo createKnowledgeByUrl(String knowledgeBaseId, KnowledgeUrlCreateRequest request) + throws WxErrorException { + String response = service.executeKnowledgePost("/api/v1/knowledge-bases/" + knowledgeBaseId + "/knowledge/url", request); + return WxGsonBuilder.create().fromJson(response, KnowledgeInfo.class); + } + + @Override + public KnowledgeInfo createKnowledgeByManual(String knowledgeBaseId, KnowledgeManualCreateRequest request) + throws WxErrorException { + String response = service.executeKnowledgePost("/api/v1/knowledge-bases/" + knowledgeBaseId + "/knowledge/manual", request); + return WxGsonBuilder.create().fromJson(response, KnowledgeInfo.class); + } + + @Override + public List listKnowledge(String knowledgeBaseId, Integer page, Integer pageSize) + throws WxErrorException { + Map query = new HashMap<>(); + query.put("page", page == null ? null : String.valueOf(page)); + query.put("page_size", pageSize == null ? null : String.valueOf(pageSize)); + String response = service.executeKnowledgeGet("/api/v1/knowledge-bases/" + knowledgeBaseId + "/knowledge", query); + KnowledgeListResult result = WxGsonBuilder.create().fromJson(response, KnowledgeListResult.class); + return result == null ? null : result.getData(); + } + + @Override + public List listKnowledgeByIds(List knowledgeIds) throws WxErrorException { + if (knowledgeIds == null || knowledgeIds.isEmpty()) { + return null; + } + StringJoiner joiner = new StringJoiner(","); + for (String knowledgeId : knowledgeIds) { + if (StringUtils.isNotBlank(knowledgeId)) { + joiner.add(knowledgeId); + } + } + if (joiner.length() == 0) { + return null; + } + + Map query = new HashMap<>(); + query.put("ids", joiner.toString()); + String response = service.executeKnowledgeGet("/api/v1/knowledge/batch", query); + return parseKnowledgeInfoList(response); + } + + @Override + public KnowledgeInfo getKnowledge(String knowledgeId) throws WxErrorException { + String response = service.executeKnowledgeGet("/api/v1/knowledge/" + knowledgeId, null); + return WxGsonBuilder.create().fromJson(response, KnowledgeInfo.class); + } + + @Override + public KnowledgeInfo updateKnowledge(String knowledgeId, KnowledgeUpdateRequest request) throws WxErrorException { + String response = service.executeKnowledgePut("/api/v1/knowledge/" + knowledgeId, request); + return WxGsonBuilder.create().fromJson(response, KnowledgeInfo.class); + } + + @Override + public KnowledgeInfo updateManualKnowledge(String knowledgeId, KnowledgeManualCreateRequest request) throws WxErrorException { + String response = service.executeKnowledgePut("/api/v1/knowledge/manual/" + knowledgeId, request); + return WxGsonBuilder.create().fromJson(response, KnowledgeInfo.class); + } + + @Override + public boolean deleteKnowledge(String knowledgeId) throws WxErrorException { + service.executeKnowledgeDelete("/api/v1/knowledge/" + knowledgeId); + return true; + } + + @Override + public boolean updateKnowledgeTags(List knowledgeIds, Long tagId) throws WxErrorException { + if (knowledgeIds == null || knowledgeIds.isEmpty() || tagId == null) { + return false; + } + + Map request = new HashMap<>(); + request.put("knowledge_ids", knowledgeIds); + request.put("tag_id", tagId); + String response = service.executeKnowledgePut("/api/v1/knowledge/tags", request); + return StringUtils.isNotBlank(response); + } + + @Override + public List searchKnowledge(String keyword, String knowledgeBaseId, Integer page, Integer pageSize) + throws WxErrorException { + Map query = new HashMap<>(); + query.put("keyword", keyword); + query.put("knowledge_base_id", knowledgeBaseId); + query.put("page", page == null ? null : String.valueOf(page)); + query.put("page_size", pageSize == null ? null : String.valueOf(pageSize)); + String response = service.executeKnowledgeGet("/api/v1/knowledge/search", query); + return parseKnowledgeInfoList(response); + } + + @Override + public String moveKnowledge(KnowledgeMoveRequest request) throws WxErrorException { + return service.executeKnowledgePost("/api/v1/knowledge/move", request); + } + + @Override + public KnowledgeMoveProgress getMoveProgress(String taskId) throws WxErrorException { + String response = service.executeKnowledgeGet("/api/v1/knowledge/move/progress/" + taskId, null); + return WxGsonBuilder.create().fromJson(response, KnowledgeMoveProgress.class); + } + + @Override + public boolean createKnowledgeBaseTag(String knowledgeBaseId, KnowledgeTagRequest request) throws WxErrorException { + String response = service.executeKnowledgePost("/api/v1/knowledge-bases/" + knowledgeBaseId + "/tags", request); + return StringUtils.isNotBlank(response); + } + + @Override + public boolean updateKnowledgeBaseTag(String knowledgeBaseId, String tagId, KnowledgeTagRequest request) + throws WxErrorException { + String response = service.executeKnowledgePut("/api/v1/knowledge-bases/" + knowledgeBaseId + "/tags/" + tagId, request); + return StringUtils.isNotBlank(response); + } + + @Override + public String postRaw(String path, Object requestBody) throws WxErrorException { + return service.executeKnowledgePost(path, requestBody); + } + + @Override + public String getRaw(String path, Map queryParams) throws WxErrorException { + return service.executeKnowledgeGet(path, queryParams); + } + + private List parseKnowledgeInfoList(String response) { + if (StringUtils.isBlank(response)) { + return null; + } + + JsonElement element = WxGsonBuilder.create().fromJson(response, JsonElement.class); + Type listType = new TypeToken>() { } .getType(); + if (element != null && element.isJsonObject()) { + JsonObject object = element.getAsJsonObject(); + if (object.has("data")) { + return WxGsonBuilder.create().fromJson(object.get("data"), listType); + } + } + return WxGsonBuilder.create().fromJson(element, listType); + } +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceHttpClientImpl.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceHttpClientImpl.java new file mode 100644 index 0000000000..e37d60e352 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceHttpClientImpl.java @@ -0,0 +1,4 @@ +package me.chanjar.weixin.aispeech.api.impl; + +public class WxAispeechServiceHttpClientImpl extends WxAispeechServiceHttpComponentsImpl { +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceHttpComponentsImpl.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceHttpComponentsImpl.java new file mode 100644 index 0000000000..ac91d98938 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceHttpComponentsImpl.java @@ -0,0 +1,4 @@ +package me.chanjar.weixin.aispeech.api.impl; + +public class WxAispeechServiceHttpComponentsImpl extends WxAispeechServiceImpl { +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceImpl.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceImpl.java new file mode 100644 index 0000000000..37a657cef2 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/api/impl/WxAispeechServiceImpl.java @@ -0,0 +1,250 @@ +package me.chanjar.weixin.aispeech.api.impl; + +import com.google.gson.Gson; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.UUID; +import me.chanjar.weixin.aispeech.api.WxAispeechDialogService; +import me.chanjar.weixin.aispeech.api.WxAispeechKnowledgeService; +import me.chanjar.weixin.aispeech.api.WxAispeechService; +import me.chanjar.weixin.aispeech.config.WxAispeechConfigStorage; +import me.chanjar.weixin.aispeech.util.WxAispeechSignUtil; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.hc.DefaultHttpComponentsClientBuilder; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsClientBuilder; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +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.HttpEntity; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.net.URIBuilder; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; + +public class WxAispeechServiceImpl implements WxAispeechService { + private static final Gson GSON = new Gson(); + + private final WxAispeechDialogService dialogService = new WxAispeechDialogServiceImpl(this); + private final WxAispeechKnowledgeService knowledgeService = new WxAispeechKnowledgeServiceImpl(this); + + private WxAispeechConfigStorage configStorage; + private CloseableHttpClient httpClient; + private HttpHost proxy; + + @Override + public WxAispeechDialogService getDialogService() { + return dialogService; + } + + @Override + public WxAispeechKnowledgeService getKnowledgeService() { + return knowledgeService; + } + + @Override + public WxAispeechConfigStorage getConfigStorage() { + return configStorage; + } + + @Override + public void setConfigStorage(WxAispeechConfigStorage configStorage) { + this.configStorage = configStorage; + this.initHttp(); + } + + protected void initHttp() { + HttpComponentsClientBuilder builder = configStorage.getHttpComponentsClientBuilder(); + if (builder == null) { + builder = DefaultHttpComponentsClientBuilder.get(); + } + + builder.httpProxyHost(configStorage.getHttpProxyHost()) + .httpProxyPort(configStorage.getHttpProxyPort()) + .httpProxyUsername(configStorage.getHttpProxyUsername()) + .httpProxyPassword(configStorage.getHttpProxyPassword() == null ? null : + configStorage.getHttpProxyPassword().toCharArray()); + + if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) { + this.proxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort()); + } else { + this.proxy = null; + } + + this.httpClient = builder.build(); + } + + protected String executeDialogPost(String path, Object requestBody, boolean withOpenToken, String appid) + throws WxErrorException { + String body = toBody(requestBody); + String requestId = UUID.randomUUID().toString(); + long timestamp = System.currentTimeMillis() / 1000; + String nonce = randomNonce(); + String sign = WxAispeechSignUtil.calcDialogSign(configStorage.getToken(), timestamp, nonce, body); + String resolvedAppid = StringUtils.defaultIfBlank(appid, configStorage.getAppid()); + + HttpPost request = new HttpPost(configStorage.getDialogApiBaseUrl() + path); + request.setHeader("request_id", requestId); + request.setHeader("timestamp", String.valueOf(timestamp)); + request.setHeader("nonce", nonce); + request.setHeader("sign", sign); + request.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType()); + if (withOpenToken) { + if (StringUtils.isBlank(configStorage.getOpenAiToken())) { + throw new WxErrorException("X-OPENAI-TOKEN不能为空,请先调用getAccessToken或手动设置"); + } + request.setHeader("X-OPENAI-TOKEN", configStorage.getOpenAiToken()); + } else { + if (StringUtils.isBlank(resolvedAppid)) { + throw new WxErrorException("X-APPID不能为空"); + } + request.setHeader("X-APPID", resolvedAppid); + } + request.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); + return executeRequest(request); + } + + protected String executeKnowledgeGet(String path, Map queryParams) throws WxErrorException { + try { + URIBuilder builder = new URIBuilder(configStorage.getKnowledgeApiBaseUrl() + path); + if (queryParams != null) { + for (Map.Entry entry : queryParams.entrySet()) { + if (entry.getValue() != null) { + builder.addParameter(entry.getKey(), entry.getValue()); + } + } + } + HttpGet request = new HttpGet(builder.build()); + enrichKnowledgeHeaders(request, ""); + return executeRequest(request); + } catch (Exception e) { + throw toWxErrorException(e); + } + } + + protected String executeKnowledgePost(String path, Object requestBody) throws WxErrorException { + String body = toBody(requestBody); + HttpPost request = new HttpPost(configStorage.getKnowledgeApiBaseUrl() + path); + request.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); + enrichKnowledgeHeaders(request, body); + return executeRequest(request); + } + + protected String executeKnowledgePut(String path, Object requestBody) throws WxErrorException { + String body = toBody(requestBody); + HttpPut request = new HttpPut(configStorage.getKnowledgeApiBaseUrl() + path); + request.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); + enrichKnowledgeHeaders(request, body); + return executeRequest(request); + } + + protected String executeKnowledgeMultipartPost(String path, File file, String title, String description, String metadata) + throws WxErrorException { + HttpPost request = new HttpPost(configStorage.getKnowledgeApiBaseUrl() + path); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addBinaryBody("file", file, ContentType.DEFAULT_BINARY, file.getName()); + if (StringUtils.isNotBlank(title)) { + builder.addTextBody("title", title, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + } + if (StringUtils.isNotBlank(description)) { + builder.addTextBody("description", description, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + } + if (StringUtils.isNotBlank(metadata)) { + builder.addTextBody("metadata", metadata, ContentType.APPLICATION_JSON); + } + HttpEntity entity = builder.build(); + request.setEntity(entity); + if (entity.getContentType() != null) { + request.setHeader("Content-Type", entity.getContentType()); + } + enrichKnowledgeHeaders(request, ""); + return executeRequest(request); + } + + protected String executeKnowledgeDelete(String path) throws WxErrorException { + HttpUriRequestBase request = new HttpUriRequestBase("DELETE", URI.create(configStorage.getKnowledgeApiBaseUrl() + path)); + enrichKnowledgeHeaders(request, ""); + return executeRequest(request); + } + + private void enrichKnowledgeHeaders(HttpUriRequestBase request, String body) throws WxErrorException { + if (StringUtils.isBlank(configStorage.getAppid())) { + throw new WxErrorException("知识助理请求需要配置appid"); + } + if (StringUtils.isBlank(configStorage.getSecretKey())) { + throw new WxErrorException("知识助理请求需要配置secretKey"); + } + + String requestId = UUID.randomUUID().toString(); + long timestamp = System.currentTimeMillis() / 1000; + String nonce = randomNonce(); + String signature = WxAispeechSignUtil.calcKnowledgeSignature(configStorage.getSecretKey(), timestamp, nonce, + requestId, body); + + request.setHeader("X-APPID", configStorage.getAppid()); + request.setHeader("X-Request-ID", requestId); + request.setHeader("X-Timestamp", String.valueOf(timestamp)); + request.setHeader("X-Nonce", nonce); + request.setHeader("X-Signature", signature); + if (!request.containsHeader("Content-Type")) { + request.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType()); + } + } + + private String executeRequest(HttpUriRequestBase request) throws WxErrorException { + if (this.proxy != null) { + RequestConfig requestConfig = RequestConfig.custom().setProxy(this.proxy).build(); + request.setConfig(requestConfig); + } + + try (CloseableHttpResponse response = httpClient.execute(request)) { + int statusCode = response.getCode(); + HttpEntity entity = response.getEntity(); + String body = entity == null ? "" : EntityUtils.toString(entity, StandardCharsets.UTF_8); + if (statusCode >= 200 && statusCode < 300) { + return body; + } + + throw new WxErrorException(WxError.builder().errorCode(statusCode).errorMsg(body).build()); + } catch (IOException | ParseException e) { + throw toWxErrorException(e); + } + } + + protected T fromJson(String json, Class clazz) { + return GSON.fromJson(json, clazz); + } + + private String toBody(Object requestBody) { + if (requestBody == null) { + return "{}"; + } + if (requestBody instanceof String) { + return (String) requestBody; + } + return GSON.toJson(requestBody); + } + + private WxErrorException toWxErrorException(Exception e) { + if (e instanceof WxErrorException) { + return (WxErrorException) e; + } + return new WxErrorException(e); + } + + private String randomNonce() { + return UUID.randomUUID().toString().replace("-", "").substring(0, 16); + } +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/AispeechApiResponse.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/AispeechApiResponse.java new file mode 100644 index 0000000000..24595b8b46 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/AispeechApiResponse.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.aispeech.bean.dialog; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +@Data +public class AispeechApiResponse { + private Integer code; + private String msg; + @SerializedName("request_id") + private String requestId; + private T data; + + public Integer getCode() { + return code; + } + + public String getMsg() { + return msg; + } + + public String getRequestId() { + return requestId; + } + + public T getData() { + return data; + } +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/AsyncTaskResult.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/AsyncTaskResult.java new file mode 100644 index 0000000000..a806fb368a --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/AsyncTaskResult.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.aispeech.bean.dialog; + +import com.google.gson.JsonElement; +import java.util.List; +import lombok.Data; + +@Data +public class AsyncTaskResult { + private Integer state; + private String msg; + private Integer progress; + private Long start; + private Long end; + private String url; + private Integer totalCount; + private Integer successCount; + private Integer failCount; + private JsonElement successSkillInfo; + private List successSkillInfoList; + + @Data + public static class SkillInfo { + private Long id; + private String name; + private List intents; + } + + @Data + public static class IntentInfo { + private Long id; + private String name; + } +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/BotIntent.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/BotIntent.java new file mode 100644 index 0000000000..3927461fc8 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/BotIntent.java @@ -0,0 +1,13 @@ +package me.chanjar.weixin.aispeech.bean.dialog; + +import java.util.List; +import lombok.Data; + +@Data +public class BotIntent { + private String skill; + private String intent; + private Boolean disable; + private List questions; + private List answers; +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/DialogQueryRequest.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/DialogQueryRequest.java new file mode 100644 index 0000000000..dd748957ff --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/DialogQueryRequest.java @@ -0,0 +1,19 @@ +package me.chanjar.weixin.aispeech.bean.dialog; + +import com.google.gson.annotations.SerializedName; +import java.util.List; +import lombok.Data; + +@Data +public class DialogQueryRequest { + private String query; + private String env; + @SerializedName("first_priority_skills") + private List firstPrioritySkills; + @SerializedName("second_priority_skills") + private List secondPrioritySkills; + @SerializedName("user_name") + private String userName; + private String avatar; + private String userid; +} diff --git a/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/DialogResult.java b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/DialogResult.java new file mode 100644 index 0000000000..575628dc10 --- /dev/null +++ b/weixin-java-aispeech/src/main/java/me/chanjar/weixin/aispeech/bean/dialog/DialogResult.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.aispeech.bean.dialog; + +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; +import java.util.List; +import lombok.Data; + +@Data +public class DialogResult { + private String answer; + @SerializedName("answer_type") + private String answerType; + @SerializedName("skill_name") + private String skillName; + @SerializedName("intent_name") + private String intentName; + @SerializedName("msg_id") + private String msgId; + private List
+ + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpmime + + + org.apache.httpcomponents.client5 + httpclient5 + provided + com.fasterxml.jackson.core @@ -49,7 +62,7 @@ org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on org.projectlombok @@ -71,6 +84,11 @@ testng test + + org.mockito + mockito-core + test + ch.qos.logback logback-classic @@ -106,12 +124,7 @@ jedis-lock true - - org.mockito - mockito-core - 3.3.3 - test - + @@ -120,6 +133,7 @@ org.apache.maven.plugins maven-surefire-plugin + ${maven.test.skip} src/test/resources/testng.xml 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 index 211024d33a..a908da9479 100644 --- 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 @@ -20,7 +20,13 @@ 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; @@ -53,6 +59,7 @@ Object route(final WxChannelMessage message, final String content, final String * 订单下单 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -64,6 +71,7 @@ void orderNew(final OrderIdMessage message, final String content, final String a * 订单取消 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -75,6 +83,7 @@ void orderCancel(OrderCancelMessage message, final String content, final String * 订单支付成功 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -82,10 +91,23 @@ void orderCancel(OrderCancelMessage message, final String content, final String 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管理器 @@ -97,6 +119,7 @@ void orderDelivery(OrderDeliveryMessage message, final String content, final Str * 订单确认收货 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -108,6 +131,7 @@ void orderConfirm(OrderConfirmMessage message, final String content, final Strin * 订单结算成功 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -119,6 +143,7 @@ void orderSettle(OrderSettleMessage message, final String content, final String * 订单其他信息更新 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -130,6 +155,7 @@ void orderExtInfoUpdate(OrderExtMessage message, final String content, final Str * 订单状态更新 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -141,6 +167,7 @@ void orderStatusUpdate(OrderStatusMessage message, final String content, final S * 商品审核结果 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -152,6 +179,7 @@ void spuAudit(SpuAuditMessage message, final String content, final String appId, * 商品系统下架通知 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -163,6 +191,7 @@ void spuStatusUpdate(SpuAuditMessage message, final String content, final String * 商品更新通知 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -170,10 +199,23 @@ void spuStatusUpdate(SpuAuditMessage message, final String content, final String 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管理器 @@ -185,6 +227,7 @@ void categoryAudit(CategoryAuditMessage message, final String content, final Str * 品牌更新 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -196,6 +239,7 @@ void brandUpdate(BrandMessage message, final String content, final String appId, * 售后单状态更新 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -207,6 +251,7 @@ void afterSaleStatusUpdate(AfterSaleMessage message, final String content, final * 纠纷回调 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -218,6 +263,7 @@ void complaintNotify(ComplaintMessage message, final String content, final Strin * 用户领券通知 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -229,6 +275,7 @@ void couponReceive(CouponReceiveMessage message, final String content, final Str * 创建优惠券通知 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -240,6 +287,7 @@ void couponCreate(CouponActionMessage message, final String content, final Strin * 优惠券删除通知 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -251,6 +299,7 @@ void couponDelete(CouponActionMessage message, final String content, final Strin * 优惠券过期通知 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -262,6 +311,7 @@ void couponExpire(CouponActionMessage message, final String content, final Strin * 更新优惠券信息通知 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -273,6 +323,7 @@ void couponUpdate(CouponActionMessage message, final String content, final Strin * 优惠券作废通知 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -284,6 +335,7 @@ void couponInvalid(CouponActionMessage message, final String content, final Stri * 用户优惠券过期通知 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -295,6 +347,7 @@ void userCouponExpire(UserCouponExpireMessage message, final String content, fin * 用户优惠券使用通知 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -306,6 +359,7 @@ void userCouponUse(UserCouponExpireMessage message, final String content, final * 用户优惠券返还通知 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -313,10 +367,22 @@ void userCouponUse(UserCouponExpireMessage message, final String content, final 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管理器 @@ -328,6 +394,7 @@ void accountNotify(AccountNotifyMessage message, final String content, final Str * 提现回调 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -339,6 +406,7 @@ void withdrawNotify(WithdrawNotifyMessage message, final String content, final S * 提现二维码回调 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -350,6 +418,7 @@ void qrNotify(QrNotifyMessage message, final String content, final String appId, * 团长商品变更 * * @param message 消息 + * @param content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -357,11 +426,97 @@ void qrNotify(QrNotifyMessage message, final String content, final String appId, 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 content 消息原始内容 * @param appId appId * @param context 上下文 * @param sessionManager session管理器 @@ -369,4 +524,17 @@ void supplierItemUpdate(SupplierItemMessage message, final String content, final */ 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 index f745ff3e41..07278da7ef 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/BaseWxChannelService.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/BaseWxChannelService.java @@ -41,8 +41,7 @@ public interface BaseWxChannelService extends WxService { *
    * 获取access_token,本方法线程安全.
    * 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限
-   *
-   * 另:本service的所有方法都会在access_token过期是调用此方法
+   * 使用【稳定版接口】获取access_token时,限制【20次/日】,连续使用该模式时,请保证调用时间隔至少为30s,否则不会刷新
    *
    * 程序员在非必要情况下尽量不要主动调用此方法
    *
@@ -132,5 +131,5 @@ public interface BaseWxChannelService extends WxService {
    *
    * @return . request http
    */
-  RequestHttp getRequestHttp();
+  RequestHttp getRequestHttp();
 }
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/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 index 063dd53948..545d30b737 100644 --- 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 @@ -12,7 +12,9 @@ * 视频号小店 地址管理服务 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreAddressService}。 */ +@Deprecated public interface WxChannelAddressService { /** diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAfterSaleService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAfterSaleService.java index ac1e61729b..2ff04faf1d 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAfterSaleService.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelAfterSaleService.java @@ -2,8 +2,8 @@ import java.util.List; -import me.chanjar.weixin.channel.bean.after.AfterSaleInfoResponse; -import me.chanjar.weixin.channel.bean.after.AfterSaleListResponse; + +import me.chanjar.weixin.channel.bean.after.*; import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; import me.chanjar.weixin.channel.bean.complaint.ComplaintOrderResponse; import me.chanjar.weixin.common.error.WxErrorException; @@ -12,7 +12,9 @@ * 视频号小店 售后服务接口 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreAfterSaleService}。 */ +@Deprecated public interface WxChannelAfterSaleService { /** @@ -24,10 +26,22 @@ public interface WxChannelAfterSaleService { * @return 售后单列表 * * @throws WxErrorException 异常 + * @deprecated 使用 {@link WxChannelAfterSaleService#listIds(AfterSaleListParam)} */ + @Deprecated AfterSaleListResponse listIds(Long beginCreateTime, Long endCreateTime, String nextKey) throws WxErrorException; + /** + * 获取售后单列表 + * + * @param param 参数 + * @return 售后单列表 + * + * @throws WxErrorException 异常 + */ + AfterSaleListResponse listIds(AfterSaleListParam param) throws WxErrorException; + /** * 获取售后单详情 * @@ -39,26 +53,47 @@ AfterSaleListResponse listIds(Long beginCreateTime, Long endCreateTime, String n 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) 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) throws WxErrorException; + WxChannelBaseResponse reject(String afterSaleOrderId, String rejectReason, Integer rejectReasonType) throws WxErrorException; + + /** + * 拒绝售后(支持拒绝凭证) + * 文档地址 https://developers.weixin.qq.com/doc/channels/API/aftersale/rejectapply.html + * + * @param afterSaleOrderId 售后单号 + * @param rejectReason 拒绝原因 + * @param rejectReasonType 拒绝原因枚举值 + * @param rejectCertificates 拒绝凭证图片列表,可使用图片上传接口获取media_id + * @see #getRejectReason() + * @return BaseResponse + * + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse reject(String afterSaleOrderId, String rejectReason, Integer rejectReasonType, + List rejectCertificates) throws WxErrorException; /** * 上传退款凭证 @@ -108,4 +143,134 @@ WxChannelBaseResponse addComplaintEvidence(String complaintId, String content, L * @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; + + /** + * 获取保障单列表。 + * 文档地址:https://developers.weixin.qq.com/doc/channels/API/channels-shop-aftersale/guarantee/api_searchguaranteeorder + * + * @param param 查询参数 + * @return 保障单列表 + * @throws WxErrorException 异常 + */ + default GuaranteeOrderListResponse listGuaranteeOrder(GuaranteeOrderListParam param) throws WxErrorException { + throw new UnsupportedOperationException(); + } + + /** + * 获取保障单详情。 + * 文档地址:https://developers.weixin.qq.com/doc/channels/API/channels-shop-aftersale/guarantee/api_getguaranteeorder + * + * @param guaranteeOrderId 保障单号 + * @return 保障单详情 + * @throws WxErrorException 异常 + */ + default GuaranteeOrderInfoResponse getGuaranteeOrder(String guaranteeOrderId) throws WxErrorException { + throw new UnsupportedOperationException(); + } + + /** + * 同意保障单申请。 + * 文档地址:https://developers.weixin.qq.com/doc/channels/API/channels-shop-aftersale/guarantee/api_merchantacceptguarantee + * + * @param guaranteeOrderId 保障单号 + * @return 响应结果 + * @throws WxErrorException 异常 + */ + default WxChannelBaseResponse acceptGuarantee(String guaranteeOrderId) throws WxErrorException { + throw new UnsupportedOperationException(); + } + + /** + * 商家协商保障单。 + * 文档地址:https://developers.weixin.qq.com/doc/channels/API/channels-shop-aftersale/guarantee/api_merchantmodifyguarantee + * + * @param request 协商参数 + * @return 响应结果 + * @throws WxErrorException 异常 + */ + default WxChannelBaseResponse modifyGuarantee(GuaranteeModifyRequest request) throws WxErrorException { + throw new UnsupportedOperationException(); + } + + /** + * 商家举证保障单。 + * 文档地址:https://developers.weixin.qq.com/doc/channels/API/channels-shop-aftersale/guarantee/api_merchantproofguarantee + * + * @param request 举证参数 + * @return 响应结果 + * @throws WxErrorException 异常 + */ + default WxChannelBaseResponse proofGuarantee(GuaranteeProofRequest request) throws WxErrorException { + throw new UnsupportedOperationException(); + } + + /** + * 拒绝保障单申请。 + * 文档地址:https://developers.weixin.qq.com/doc/channels/API/channels-shop-aftersale/guarantee/api_merchantrefuseguarantee + * + * @param request 拒绝参数 + * @return 响应结果 + * @throws WxErrorException 异常 + */ + default WxChannelBaseResponse refuseGuarantee(GuaranteeRefuseRequest request) throws WxErrorException { + throw new UnsupportedOperationException(); + } } 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 index a687aaeb5c..2f1a37f024 100644 --- 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 @@ -5,14 +5,19 @@ 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.ShopH5UrlResponse; import me.chanjar.weixin.channel.bean.shop.ShopInfoResponse; +import me.chanjar.weixin.channel.bean.shop.ShopQrCodeResponse; +import me.chanjar.weixin.channel.bean.shop.ShopTagLinkResponse; import me.chanjar.weixin.common.error.WxErrorException; /** * 基础接口 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreBasicService}。 */ +@Deprecated public interface WxChannelBasicService { /** @@ -22,6 +27,31 @@ public interface WxChannelBasicService { */ ShopInfoResponse getShopInfo() throws WxErrorException; + /** + * 获取店铺H5链接 + * + * @return 店铺H5链接 + * @throws WxErrorException 异常 + */ + ShopH5UrlResponse getShopH5Url() throws WxErrorException; + + /** + * 获取店铺二维码 + * + * @param qrcodeType 二维码类型,1:二维码;2:标准物料;3:送礼物物料 + * @return 店铺二维码 + * @throws WxErrorException 异常 + */ + ShopQrCodeResponse getShopQrCode(int qrcodeType) throws WxErrorException; + + /** + * 获取店铺口令 + * + * @return 店铺口令 + * @throws WxErrorException 异常 + */ + ShopTagLinkResponse getShopTagLink() 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 index 905d354955..8a5dda562c 100644 --- 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 @@ -13,7 +13,9 @@ * 视频号小店 品牌服务接口 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreBrandService}。 */ +@Deprecated public interface WxChannelBrandService { /** diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCategoryService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCategoryService.java index ddbc99e5d4..f4129ffc19 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCategoryService.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCategoryService.java @@ -4,18 +4,19 @@ 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.CategoryDetailResult; -import me.chanjar.weixin.channel.bean.category.CategoryQualificationResponse; -import me.chanjar.weixin.channel.bean.category.PassCategoryResponse; -import me.chanjar.weixin.channel.bean.category.ShopCategory; +import me.chanjar.weixin.channel.bean.category.*; import me.chanjar.weixin.common.error.WxErrorException; /** * 视频号小店 商品类目相关接口 * * @author Zeyes + * @see 新旧类目树差异 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreCategoryService}。 */ +@Deprecated public interface WxChannelCategoryService { /** @@ -30,12 +31,27 @@ public interface WxChannelCategoryService { /** * 获取商品类目列表(全量) 有频率限制 * - * @param parentId 类目父id + * @param fCatId 类目父id * @return 类目列表 * * @throws WxErrorException 异常 + * @deprecated 接口返回更新,请使用 {@link #listAvailableCategories(String)} */ - List listAvailableCategory(String parentId) throws WxErrorException; + @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; /** * 获取类目信息 @@ -58,10 +74,23 @@ public interface WxChannelCategoryService { * * @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; + /** * 取消类目提审 * @@ -90,4 +119,16 @@ AuditApplyResponse addCategory(String level1, String level2, String level3, List * @throws WxErrorException 异常 */ PassCategoryResponse listPassCategory() throws WxErrorException; + + /** + * 获取店铺的类目权限列表 + * + * @param isFilterStatus 是否按状态筛选 + * @param status 类目状态(当 isFilterStatus 为 true 时有效) + * @return 类目权限列表 + * + * @throws WxErrorException 异常 + */ + RelationCategoryResponse listRelationCategory(Boolean isFilterStatus, Integer status) throws WxErrorException; + } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/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..e742281289 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelCompassShopService.java @@ -0,0 +1,128 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreCompassShopService}。 + */ +@Deprecated +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 index df59fdc8b9..309ac713a6 100644 --- 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 @@ -15,7 +15,9 @@ * 视频号小店 优惠券服务 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreCouponService}。 */ +@Deprecated public interface WxChannelCouponService { /** diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelEwaybillService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelEwaybillService.java new file mode 100644 index 0000000000..cb96f5e3e0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelEwaybillService.java @@ -0,0 +1,80 @@ +package me.chanjar.weixin.channel.api; + +import java.util.List; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.ewaybill.AccountInfoResponse; +import me.chanjar.weixin.channel.bean.ewaybill.AddSubOrderRequest; +import me.chanjar.weixin.channel.bean.ewaybill.CreateOrderRequest; +import me.chanjar.weixin.channel.bean.ewaybill.CreateOrderResponse; +import me.chanjar.weixin.channel.bean.ewaybill.DeliveryListResponse; +import me.chanjar.weixin.channel.bean.ewaybill.PrintOrderRequest; +import me.chanjar.weixin.channel.bean.ewaybill.BatchPrintOrderRequest; +import me.chanjar.weixin.channel.bean.ewaybill.OrderDetailResponse; +import me.chanjar.weixin.channel.bean.ewaybill.PreCreateRequest; +import me.chanjar.weixin.channel.bean.ewaybill.PreCreateResponse; +import me.chanjar.weixin.channel.bean.ewaybill.PrintContentResponse; +import me.chanjar.weixin.channel.bean.ewaybill.TemplateConfigResponse; +import me.chanjar.weixin.channel.bean.ewaybill.TemplateCreateRequest; +import me.chanjar.weixin.channel.bean.ewaybill.TemplateIdResponse; +import me.chanjar.weixin.channel.bean.ewaybill.TemplateInfoResponse; +import me.chanjar.weixin.channel.bean.ewaybill.TemplateUpdateRequest; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店电子面单服务接口 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreEwaybillService}。 + */ +@Deprecated +public interface WxChannelEwaybillService { + + /** 获取可用的标准面单模板。 @return 模板配置 @throws WxErrorException 微信接口调用失败 */ + TemplateConfigResponse getTemplateConfig() throws WxErrorException; + + /** 创建商家面单模板。 @param req 官方模板创建字段 @return 新模板 ID @throws WxErrorException 调用失败 */ + TemplateIdResponse createTemplate(TemplateCreateRequest req) throws WxErrorException; + + /** 删除商家面单模板。 @param templateId 模板 ID @return 操作结果 @throws WxErrorException 调用失败 */ + WxChannelBaseResponse deleteTemplate(String templateId) throws WxErrorException; + + /** 更新商家面单模板。 @param req 官方模板更新字段 @return 操作结果 @throws WxErrorException 调用失败 */ + WxChannelBaseResponse updateTemplate(TemplateUpdateRequest req) throws WxErrorException; + + /** 查询标准模板信息。 @param templateCode 标准模板编码 @return 模板详情 @throws WxErrorException 调用失败 */ + TemplateInfoResponse getTemplate(String templateCode) throws WxErrorException; + + /** 按模板 ID 查询商家模板。 @param templateId 模板 ID @return 模板详情 @throws WxErrorException 调用失败 */ + TemplateInfoResponse getTemplateById(String templateId) throws WxErrorException; + + /** 查询已开通电子面单的网点和账号。 @return 账号信息 @throws WxErrorException 调用失败 */ + AccountInfoResponse getAccount() throws WxErrorException; + + /** 查询已开通电子面单的快递公司。 @return 快递公司列表 @throws WxErrorException 调用失败 */ + DeliveryListResponse getDeliveryList() throws WxErrorException; + + /** 预取电子面单号。 @param req 官方预取号字段 @return 预取号结果 @throws WxErrorException 调用失败 */ + PreCreateResponse preCreateOrder(PreCreateRequest req) throws WxErrorException; + + /** 获取电子面单号。 @param req 官方取号字段,含收寄件信息 @return 面单号结果 @throws WxErrorException 调用失败 */ + CreateOrderResponse createOrder(CreateOrderRequest req) throws WxErrorException; + + /** 追加电子面单子件。 @param req 官方子件字段 @return 操作结果 @throws WxErrorException 调用失败 */ + WxChannelBaseResponse addSubOrder(AddSubOrderRequest req) throws WxErrorException; + + /** 取消电子面单下单。 @param waybillId 运单 ID @return 操作结果 @throws WxErrorException 调用失败 */ + WxChannelBaseResponse cancelOrder(PrintOrderRequest req) throws WxErrorException; + + /** 查询电子面单详情。 @param waybillId 运单 ID @return 面单详情 @throws WxErrorException 调用失败 */ + OrderDetailResponse getOrder(String ewaybillOrderId) throws WxErrorException; + + /** 获取打印报文。 @param waybillIds 运单 ID 列表 @param templateId 可选模板 ID @return 打印内容 @throws WxErrorException 调用失败 */ + PrintContentResponse getPrintContent(String ewaybillOrderId, String templateId) + throws WxErrorException; + + /** 通知单个运单打印成功。 @param waybillId 运单 ID @return 操作结果 @throws WxErrorException 调用失败 */ + WxChannelBaseResponse printOrder(PrintOrderRequest req) throws WxErrorException; + + /** 批量通知运单打印成功。 @param waybillIds 运单 ID 列表 @return 操作结果 @throws WxErrorException 调用失败 */ + WxChannelBaseResponse batchPrintOrder(BatchPrintOrderRequest req) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelFavoriteService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelFavoriteService.java new file mode 100644 index 0000000000..57998a76d1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelFavoriteService.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.favorite.FavoriteCountResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 收藏管理接口 + * + * @author GitHub Copilot + * @link 收藏管理接口文档 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreFavoriteService}。 + */ +@Deprecated +public interface WxChannelFavoriteService { + + /** + * 获取店铺收藏的人数 + * + * @return 店铺收藏人数响应 + * @throws WxErrorException 异常 + */ + FavoriteCountResponse getFavoriteCount() 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 index 188b33464b..0bd617f465 100644 --- 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 @@ -11,7 +11,9 @@ * 视频号小店 运费模板服务接口 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreFreightTemplateService}。 */ +@Deprecated public interface WxChannelFreightTemplateService { /** 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 index cb0f5aab79..7dcb1c76eb 100644 --- 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 @@ -24,7 +24,9 @@ * 资金相关服务 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreFundService}。 */ +@Deprecated public interface WxChannelFundService { /** diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelGiftService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelGiftService.java new file mode 100644 index 0000000000..ffea27a3a5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelGiftService.java @@ -0,0 +1,104 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.GiftActivityAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftActivityInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductGetResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductListParam; +import me.chanjar.weixin.channel.bean.product.GiftProductListResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店赠品与买赠活动服务。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreGiftService}。 + */ +@Deprecated +public interface WxChannelGiftService { + + /** + * 添加非卖商品。 + * + * @param info 赠品信息 + * @return 添加赠品响应 + * @throws WxErrorException 异常 + */ + GiftProductAddResponse addGiftProduct(GiftProductInfo info) throws WxErrorException; + + /** + * 更新非卖商品。 + * + * @param info 赠品信息 + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateGiftProduct(GiftProductInfo info) throws WxErrorException; + + /** + * 在售商品转赠品。 + * + * @param productId 商品ID + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse setProductAsGift(String productId) throws WxErrorException; + + /** + * 获取赠品。 + * + * @param productId 赠品商品ID + * @return 赠品详情响应 + * @throws WxErrorException 异常 + */ + GiftProductGetResponse getGiftProduct(String productId) throws WxErrorException; + + /** + * 获取赠品列表。 + * + * @param param 查询参数 + * @return 赠品列表 + * @throws WxErrorException 异常 + */ + GiftProductListResponse listGiftProduct(GiftProductListParam param) throws WxErrorException; + + /** + * 更新赠品库存。 + * + * @param productId 赠品商品ID + * @param skuId 赠品sku_id + * @param diffType 修改类型 1增加 2减少 3设置 + * @param num 增加、减少或者设置的库存值 + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateGiftStock(String productId, String skuId, Integer diffType, Integer num) + throws WxErrorException; + + /** + * 创建赠品活动。 + * + * @param info 活动信息 + * @return 创建赠品活动响应 + * @throws WxErrorException 异常 + */ + GiftActivityAddResponse addGiftActivity(GiftActivityInfo info) throws WxErrorException; + + /** + * 删除赠品活动。 + * + * @param activityId 活动ID + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse deleteGiftActivity(String activityId) throws WxErrorException; + + /** + * 停止赠品活动。 + * + * @param activityId 活动ID + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse stopGiftActivity(String activityId) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelKfService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelKfService.java new file mode 100644 index 0000000000..e1d28825e8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelKfService.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgParam; +import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店商家客服服务。 + * + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreKfService}。 + */ +@Deprecated +public interface WxChannelKfService { + + /** + * 上传多媒体资源。 + * + * @param openId 用户 open_id + * @param msgType 文件类型,仅支持 video、file、image + * @param file 文件字节内容 + * @return COS 地址 + * @throws WxErrorException 微信异常 + */ + String uploadMedia(String openId, String msgType, byte[] file) throws WxErrorException; + + /** + * 上传多媒体资源。 + * + * @param openId 用户 open_id + * @param msgType 文件类型,仅支持 video、file、image + * @param fileName 文件名 + * @param file 文件字节内容 + * @return COS 地址 + * @throws WxErrorException 微信异常 + */ + String uploadMedia(String openId, String msgType, String fileName, byte[] file) throws WxErrorException; + + /** + * 发送客服消息。 + * + * @param param 请求参数 + * @return 发送结果 + * @throws WxErrorException 微信异常 + */ + WxChannelKfSendMsgResponse sendMessage(WxChannelKfSendMsgParam param) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java new file mode 100644 index 0000000000..ef333f92f1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java @@ -0,0 +1,64 @@ +package me.chanjar.weixin.channel.api; + +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.limit.LimitTaskUpdateParam; +import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店限时抢购服务。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreLimitedDiscountService}。 + */ +@Deprecated +public interface WxChannelLimitedDiscountService { + + /** + * 添加限时抢购任务。 + * + * @param param 限时抢购任务 + * @return 添加任务响应 + * @throws WxErrorException 异常 + */ + LimitTaskAddResponse addLimitTask(LimitTaskParam param) throws WxErrorException; + + /** + * 拉取限时抢购任务列表。 + * + * @param pageSize 每页数量 + * @param nextKey 翻页上下文 + * @param status 抢购活动状态 + * @return 任务列表响应 + * @throws WxErrorException 异常 + */ + LimitTaskListResponse listLimitTask(Integer pageSize, String nextKey, Integer status) throws WxErrorException; + + /** + * 停止限时抢购任务。 + * + * @param taskId 限时抢购任务ID + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse stopLimitTask(String taskId) throws WxErrorException; + + /** + * 删除限时抢购任务。 + * + * @param taskId 限时抢购任务ID + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse deleteLimitTask(String taskId) throws WxErrorException; + + /** + * 更新限时抢购任务。 + * + * @param param 更新任务参数 + * @return 更新任务响应 + * @throws WxErrorException 异常 + */ + LimitTaskUpdateResponse updateLimitTask(LimitTaskUpdateParam param) 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 index e280ace2fc..c7f43496f2 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelOrderService.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelOrderService.java @@ -3,14 +3,22 @@ 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.OrderCompensationDeliveryParam; 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.PreShipmentChangeSkuResponse; +import me.chanjar.weixin.channel.bean.order.PresentSubOrderResponse; +import me.chanjar.weixin.channel.bean.order.PrivateNumberGetPhoneResponse; +import me.chanjar.weixin.channel.bean.order.RealNumberViewAuditResponse; +import me.chanjar.weixin.channel.bean.order.VirtualTelNumberResponse; import me.chanjar.weixin.common.error.WxErrorException; /** @@ -18,7 +26,9 @@ * * @author Zeyes * @link 订单接口文档 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreOrderService}。 */ +@Deprecated public interface WxChannelOrderService { /** @@ -31,6 +41,17 @@ public interface WxChannelOrderService { */ OrderInfoResponse getOrder(String orderId) throws WxErrorException; + /** + * 获取订单详情 + * + * @param orderId 订单id + * @param encodeSensitiveInfo 是否编码敏感信息 + * @return 订单详情 + * + * @throws WxErrorException 异常 + */ + OrderInfoResponse getOrder(String orderId, Boolean encodeSensitiveInfo) throws WxErrorException; + /** * 获取订单列表 * @@ -96,6 +117,26 @@ WxChannelBaseResponse updatePrice(String orderId, Integer expressFee, 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; + + /** + * 礼物订单新增备注信息 + * + * @param orderId 礼物订单ID + * @param note 备注内容 + * @return BaseResponse + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse addPresentNote(String orderId, String note) throws WxErrorException; + + /** + * 获取礼物单的子单列表 + * + * @param orderId 礼物订单ID + * @return 子单列表 + * @throws WxErrorException 异常 + */ + PresentSubOrderResponse getPresentSubOrders(String orderId) throws WxErrorException; + + /** + * 获取待发货前更换SKU待处理请求 + * + * @param orderId 订单ID + * @return 换SKU信息 + * @throws WxErrorException 异常 + */ + PreShipmentChangeSkuResponse getPreShipmentChangeSku(String orderId) throws WxErrorException; + + /** + * 同意待发货前更换SKU请求 + * + * @param orderId 订单ID + * @return BaseResponse + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse approvePreShipmentChangeSku(String orderId) throws WxErrorException; + + /** + * 拒绝待发货前更换SKU请求 + * + * @param orderId 订单ID + * @param rejectReason 拒绝原因 + * @return BaseResponse + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse rejectPreShipmentChangeSku(String orderId, String rejectReason) throws WxErrorException; + + /** + * 申请查看订单真实号码 + * + * @param orderId 订单ID + * @return BaseResponse + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse applyRealNumber(String orderId) throws WxErrorException; + + /** + * 查看订单真实号审核状态 + * + * @param orderId 订单ID + * @return 审核状态 + * @throws WxErrorException 异常 + */ + RealNumberViewAuditResponse getRealNumberViewAudit(String orderId) throws WxErrorException; + + /** + * 订单再次申请虚拟号 + * + * @param orderId 订单ID + * @return BaseResponse + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse applyVirtualNumberAgain(String orderId) throws WxErrorException; + + /** + * 订单虚拟号延期 + * + * @param orderId 订单ID + * @return BaseResponse + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse delayVirtualNumber(String orderId) throws WxErrorException; + + /** + * 添加待认证的手机号 + * + * @param phone 手机号 + * @return BaseResponse + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse addPrivatePhone(String phone) throws WxErrorException; + + /** + * 获取短信验证码 + * + * @param phone 手机号 + * @return BaseResponse + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse sendPrivatePhoneVerifyCode(String phone) throws WxErrorException; + + /** + * 获取小店手机号认证状态 + * + * @return 手机号认证状态 + * @throws WxErrorException 异常 + */ + PrivateNumberGetPhoneResponse getPrivatePhone() throws WxErrorException; + + /** + * 订单补发货 + * + * @param param 补发货参数 + * @return BaseResponse + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse compensationDelivery(OrderCompensationDeliveryParam param) throws WxErrorException; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java new file mode 100644 index 0000000000..0bb0af5620 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java @@ -0,0 +1,79 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.assistant.BeginTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.assistant.CancelTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckParam; +import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewParam; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingParam; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendParam; +import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店商品辅助功能服务。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreProductAssistantService}。 + */ +@Deprecated +public interface WxChannelProductAssistantService { + + /** + * 发品前校验。 + * + * @param param 校验参数 + * @return 校验结果 + * @throws WxErrorException 异常 + */ + CategoryPreCheckResponse categoryPreCheck(CategoryPreCheckParam param) throws WxErrorException; + + /** + * 获取商品品牌推荐。 + * + * @param param 推荐参数 + * @return 推荐结果 + * @throws WxErrorException 异常 + */ + ProductBrandRecommendResponse getProductBrandRecommend(ProductBrandRecommendParam param) + throws WxErrorException; + + /** + * 获取站内外商品属性映射。 + * + * @param param 映射参数 + * @return 映射结果 + * @throws WxErrorException 异常 + */ + ExternalProductMappingResponse externalProductMapping(ExternalProductMappingParam param) + throws WxErrorException; + + /** + * 获取商品属性映射及推荐。 + * + * @param param 映射参数 + * @return 映射结果 + * @throws WxErrorException 异常 + */ + ExternalProductMappingNewResponse externalProductMappingNew(ExternalProductMappingNewParam param) + throws WxErrorException; + + /** + * 将定时开售商品改为立即开售。 + * + * @param param 开售参数 + * @return 操作结果 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse beginTimingSale(BeginTimingSaleParam param) throws WxErrorException; + + /** + * 取消商品定时开售。 + * + * @param param 取消参数 + * @return 操作结果 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse cancelTimingSale(CancelTimingSaleParam param) 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 index b962b9ec85..095a7c482e 100644 --- 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 @@ -1,22 +1,59 @@ 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.GiftActivityAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftActivityInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductGetResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductListParam; +import me.chanjar.weixin.channel.bean.product.GiftProductListResponse; +import me.chanjar.weixin.channel.bean.product.AddProductThirdPartySourceParam; +import me.chanjar.weixin.channel.bean.product.AddProductThirdPartySourceResponse; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingNewParam; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingNewResponse; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingParam; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditQuotaResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategyResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategySetParam; +import me.chanjar.weixin.channel.bean.product.ProductBrandRecommendParam; +import me.chanjar.weixin.channel.bean.product.ProductBrandRecommendResponse; +import me.chanjar.weixin.channel.bean.product.ProductCategoryClassifyParam; +import me.chanjar.weixin.channel.bean.product.ProductCategoryClassifyResponse; +import me.chanjar.weixin.channel.bean.product.ProductCategoryPreCheckParam; +import me.chanjar.weixin.channel.bean.product.ProductCategoryPreCheckResponse; +import me.chanjar.weixin.channel.bean.product.ProductSchemeParam; +import me.chanjar.weixin.channel.bean.product.ProductSchemeResponse; +import me.chanjar.weixin.channel.bean.product.ProductStockFlowParam; +import me.chanjar.weixin.channel.bean.product.ProductStockFlowResponse; +import me.chanjar.weixin.channel.bean.product.ProductTimingSaleParam; +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 商品状态流转图 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreProductService}。 */ +@Deprecated public interface WxChannelProductService { /** @@ -27,6 +64,28 @@ public interface WxChannelProductService { * * @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; /** @@ -36,15 +95,28 @@ public interface WxChannelProductService { * @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 * @@ -127,6 +199,245 @@ WxChannelBaseResponse updateStock(String productId, String skuId, Integer diffTy */ 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; + + /** + * 获取商品的移动应用跳转 scheme 码. + * + * @param param 商品 ID、来源 appid、过期时间和附加信息 + * @return 商品跳转 scheme 码 + * @throws WxErrorException 调用微信接口失败 + */ + ProductSchemeResponse getProductScheme(ProductSchemeParam param) throws WxErrorException; + + /** + * 商品类目推荐. + * + * @param param 请求类型、商品标题、主图和可选类目 ID;当请求类型为 2 时必须提供类目 ID + * @return 推荐类目及店铺经营权限 + * @throws WxErrorException 调用微信接口失败 + */ + ProductCategoryClassifyResponse classifyProductCategory(ProductCategoryClassifyParam param) throws WxErrorException; + + /** + * 将定时开售商品改为立即开售. + * + * @param param 商品 ID 和定时开售任务 ID + * @return 操作结果 + * @throws WxErrorException 调用微信接口失败 + */ + WxChannelBaseResponse beginTimingSale(ProductTimingSaleParam param) throws WxErrorException; + + /** + * 取消商品开售. + * + * @param productId 商品 ID + * @return 操作结果 + * @throws WxErrorException 调用微信接口失败 + */ + WxChannelBaseResponse cancelTimingSale(String productId) throws WxErrorException; + + /** + * 查询站内外商品属性映射. + * + * @param param 叶子类目 ID、外部类目和外部属性 + * @return 对应的站内属性及可选属性值 + * @throws WxErrorException 调用微信接口失败 + */ + ExternalProductMappingResponse externalProductMapping(ExternalProductMappingParam param) throws WxErrorException; + + /** + * 发品前校验店铺类目资质. + * + * @param param 待发布商品的叶子类目 ID + * @return 校验结果和未通过原因 + * @throws WxErrorException 调用微信接口失败 + */ + ProductCategoryPreCheckResponse categoryPreCheck(ProductCategoryPreCheckParam param) throws WxErrorException; + + /** + * 获取店铺维度的商品上架策略. + * + * @return 当前上架策略 + * @throws WxErrorException 调用微信接口失败 + */ + ProductAuditStrategyResponse getProductAuditStrategy() throws WxErrorException; + + /** + * 设置店铺维度的商品上架策略. + * + * @param param 要设置的上架策略 + * @return 操作结果 + * @throws WxErrorException 调用微信接口失败 + */ + WxChannelBaseResponse setProductAuditStrategy(ProductAuditStrategySetParam param) throws WxErrorException; + + /** + * 获取当前店铺的商品提审限额. + * + * @return 提审总额度和新品剩余额度 + * @throws WxErrorException 调用微信接口失败 + */ + ProductAuditQuotaResponse getProductAuditQuota() throws WxErrorException; + + /** + * 商品属性映射及推荐. + * + * @param param 叶子类目、商品标题、主图及可选的外部属性 + * @return 推荐的站内属性 + * @throws WxErrorException 调用微信接口失败 + */ + ExternalProductMappingNewResponse externalProductMappingNew(ExternalProductMappingNewParam param) + throws WxErrorException; + + /** + * 根据商品信息推荐店铺已有资质的品牌. + * + * @param param 商品叶子类目、标题和图片 + * @return 推荐品牌 + * @throws WxErrorException 调用微信接口失败 + */ + ProductBrandRecommendResponse productBrandRecommend(ProductBrandRecommendParam param) throws WxErrorException; + + /** + * 新增第三方货源信息. + * + * @param param 场景、发布方式、货主及货源商品信息 + * @return 包含第三方货源 ID 的操作结果 + * @throws WxErrorException 调用微信接口失败 + */ + AddProductThirdPartySourceResponse addProductThirdPartySource(AddProductThirdPartySourceParam param) + throws WxErrorException; + + /** + * 获取商品库存流水. + * + * @param param 商品、SKU、库存类型、时间范围和分页参数;pageSize 必填,stockType 为 1 时 finderId 必填,库存类型非 0 和 1 时 stockTypeId 必填 + * @return 库存流水及下一页标识 + * @throws WxErrorException 调用微信接口失败 + */ + ProductStockFlowResponse getStockFlow(ProductStockFlowParam param) throws WxErrorException; + + /** + * 添加非卖商品 + * + * @param info 赠品信息 + * @return 添加赠品响应 + * @throws WxErrorException 异常 + */ + GiftProductAddResponse addGiftProduct(GiftProductInfo info) throws WxErrorException; + + /** + * 更新非卖商品 + * + * @param info 赠品信息 + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateGiftProduct(GiftProductInfo info) throws WxErrorException; + + /** + * 在售商品转赠品 + * + * @param productId 商品ID + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse setProductAsGift(String productId) throws WxErrorException; + + /** + * 获取赠品 + * + * @param productId 赠品商品ID + * @return 赠品详情响应 + * @throws WxErrorException 异常 + */ + GiftProductGetResponse getGiftProduct(String productId) throws WxErrorException; + + /** + * 获取赠品列表 + * + * @param param 查询参数 + * @return 赠品列表 + * @throws WxErrorException 异常 + */ + GiftProductListResponse listGiftProduct(GiftProductListParam param) throws WxErrorException; + + /** + * 更新赠品库存 + * + * @param productId 赠品商品ID + * @param skuId 赠品sku_id + * @param diffType 修改类型 1增加 2减少 3设置 + * 建议使用1或2,不建议使用3,因为使用3在高并发场景可能会出现预期外表现 + * @param num 增加、减少或者设置的库存值 + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateGiftStock(String productId, String skuId, Integer diffType, Integer num) + throws WxErrorException; + + /** + * 创建赠品活动 + * + * @param info 活动信息 + * @return 创建赠品活动响应 + * @throws WxErrorException 异常 + */ + GiftActivityAddResponse addGiftActivity(GiftActivityInfo info) throws WxErrorException; + + /** + * 删除赠品活动 + * + * @param activityId 活动ID + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse deleteGiftActivity(String activityId) throws WxErrorException; + + /** + * 停止赠品活动 + * + * @param activityId 活动ID + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse stopGiftActivity(String activityId) throws WxErrorException; + /** * 添加限时抢购任务 * diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java new file mode 100644 index 0000000000..77b012de1f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java @@ -0,0 +1,58 @@ +package me.chanjar.weixin.channel.api; + +import java.util.List; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse; +import me.chanjar.weixin.channel.bean.product.SkuStockResponse; +import me.chanjar.weixin.channel.bean.product.stock.StockFlowParam; +import me.chanjar.weixin.channel.bean.product.stock.StockFlowResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店商品库存服务。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreProductStockService}。 + */ +@Deprecated +public interface WxChannelProductStockService { + + /** + * 更新商品库存。 + * + * @param productId 商品ID + * @param skuId 商品sku_id + * @param diffType 修改类型 1增加 2减少 3设置 + * @param num 增加、减少或者设置的库存值 + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateStock(String productId, String skuId, Integer diffType, Integer num) + throws WxErrorException; + + /** + * 获取商品实时库存。 + * + * @param productId 商品ID + * @param skuId 商品sku_id + * @return 库存响应 + * @throws WxErrorException 异常 + */ + SkuStockResponse getSkuStock(String productId, String skuId) throws WxErrorException; + + /** + * 批量获取库存信息。 + * + * @param productIds 商品ID列表,单次请求不超过50个 + * @return 库存信息 + * @throws WxErrorException 异常 + */ + SkuStockBatchResponse getSkuStockBatch(List productIds) throws WxErrorException; + + /** + * 获取商品库存流水。 + * + * @param param 库存流水查询参数 + * @return 库存流水响应 + * @throws WxErrorException 异常 + */ + StockFlowResponse getStockFlow(StockFlowParam param) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelQicService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelQicService.java new file mode 100644 index 0000000000..e1e6521614 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelQicService.java @@ -0,0 +1,69 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.qic.InspectCodeResponse; +import me.chanjar.weixin.channel.bean.qic.InspectConfigResponse; +import me.chanjar.weixin.channel.bean.qic.RegisterLogisticsRequest; +import me.chanjar.weixin.channel.bean.qic.SubmitConfigResponse; +import me.chanjar.weixin.channel.bean.qic.SubmitInspectRequest; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 质检管理接口. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreQicService}。 + */ +@Deprecated +public interface WxChannelQicService { + + /** + * 查询质检仓配置. + * + * @return 质检仓配置 + * @throws WxErrorException 异常 + */ + InspectConfigResponse getInspectConfig() throws WxErrorException; + + /** + * 查询送检配置模板信息. + * + * @param orderId 订单号(可选) + * @return 送检配置模板信息 + * @throws WxErrorException 异常 + */ + SubmitConfigResponse getSubmitConfig(String orderId) throws WxErrorException; + + /** + * 查询送检配置模板信息. + * + * @return 送检配置模板信息 + * @throws WxErrorException 异常 + */ + SubmitConfigResponse getSubmitConfig() throws WxErrorException; + + /** + * 打印质检码. + * + * @param orderId 订单号 + * @return 质检码详情 + * @throws WxErrorException 异常 + */ + InspectCodeResponse printInspectCode(String orderId) throws WxErrorException; + + /** + * 绑定送检信息. + * + * @param request 送检信息请求 + * @return 基础响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse submitInspectInfo(SubmitInspectRequest request) throws WxErrorException; + + /** + * 自寄快递送检. + * + * @param request 自寄快递请求 + * @return 基础响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse registerLogistics(RegisterLogisticsRequest request) 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 index 8f960f4795..7d18e61c57 100644 --- 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 @@ -7,90 +7,216 @@ */ public interface WxChannelService extends BaseWxChannelService { + /** + * 商家客服服务。 + * + * @return 商家客服服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getKfService()}。 + */ + @Deprecated + default WxChannelKfService getKfService() { + throw new UnsupportedOperationException("WxChannelService implementation does not support getKfService()"); + } + /** * 基础接口服务 * * @return 基础接口服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getBasicService()}。 */ + @Deprecated WxChannelBasicService getBasicService(); /** * 商品类目服务 * * @return 商品类目服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getCategoryService()}。 */ + @Deprecated WxChannelCategoryService getCategoryService(); /** * 品牌服务 * * @return 品牌服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getBrandService()}。 */ + @Deprecated WxChannelBrandService getBrandService(); /** * 商品服务 * * @return 商品服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getProductService()}。 */ + @Deprecated WxChannelProductService getProductService(); + /** + * 赠品与买赠活动服务 + * + * @return 赠品与买赠活动服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getGiftService()}。 + */ + @Deprecated + default WxChannelGiftService getGiftService() { + throw new UnsupportedOperationException("Gift service is not supported by this implementation"); + } + + /** + * 限时抢购服务 + * + * @return 限时抢购服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getLimitedDiscountService()}。 + */ + @Deprecated + default WxChannelLimitedDiscountService getLimitedDiscountService() { + throw new UnsupportedOperationException("Limited discount service is not supported by this implementation"); + } + + /** + * 商品库存服务 + * + * @return 商品库存服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getProductStockService()}。 + */ + @Deprecated + default WxChannelProductStockService getProductStockService() { + throw new UnsupportedOperationException("Product stock service is not supported by this implementation"); + } + + /** + * 商品辅助功能服务 + * + * @return 商品辅助功能服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getProductAssistantService()}。 + */ + @Deprecated + default WxChannelProductAssistantService getProductAssistantService() { + throw new UnsupportedOperationException("Product assistant service is not supported by this implementation"); + } + /** * 仓库服务 * * @return 仓库服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getWarehouseService()}。 */ + @Deprecated WxChannelWarehouseService getWarehouseService(); /** * 订单服务 * * @return 订单服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getOrderService()}。 */ + @Deprecated WxChannelOrderService getOrderService(); /** * 售后服务 * * @return 售后服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getAfterSaleService()}。 */ + @Deprecated WxChannelAfterSaleService getAfterSaleService(); /** * 运费模板服务 * * @return 运费模板服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getFreightTemplateService()}。 */ + @Deprecated WxChannelFreightTemplateService getFreightTemplateService(); /** * 地址服务 * * @return 地址服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getAddressService()}。 */ + @Deprecated WxChannelAddressService getAddressService(); /** * 优惠券服务 * * @return 优惠券服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getCouponService()}。 */ + @Deprecated WxChannelCouponService getCouponService(); /** * 分享员服务 * * @return 分享员服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getSharerService()}。 */ + @Deprecated WxChannelSharerService getSharerService(); /** * 资金服务 * * @return 资金服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getFundService()}。 */ + @Deprecated WxChannelFundService getFundService(); + /** + * 主页管理服务 + * + * @return 主页管理服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getHomePageService()}。 + */ + @Deprecated + WxStoreHomePageService getHomePageService(); + + /** + * 合作账号服务 + * + * @return 团长合作服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getCooperationService()}。 + */ + @Deprecated + WxStoreCooperationService getCooperationService(); + + /** + * 视频号/微信小店 罗盘商家版服务 + * + * @return 罗盘商家版服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getCompassShopService()}。 + */ + @Deprecated + WxChannelCompassShopService getCompassShopService(); + /** * 优选联盟-团长合作达人管理服务 * @@ -98,6 +224,16 @@ public interface WxChannelService extends BaseWxChannelService { */ WxLeagueWindowService getLeagueWindowService(); + /** + * 代发管理服务 + * + * @return 代发管理服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getSupplierService()}。 + */ + @Deprecated + WxChannelSupplierService getSupplierService(); + /** * 优选联盟-团长服务 * @@ -119,4 +255,88 @@ public interface WxChannelService extends BaseWxChannelService { */ WxLeagueProductService getLeagueProductService(); + /** + * 视频号助手 留资组件管理服务 + * + * @return 留资组件管理服务 + */ + WxLeadComponentService getLeadComponentService(); + + /** + * 视频号助手 留资服务的直播数据服务 + * + * @return 留资服务的直播数据服务 + */ + WxFinderLiveService getFinderLiveService(); + + /** + * 视频号助手 橱窗管理服务 + * + * @return 橱窗管理服务 + */ + WxAssistantService getAssistantService(); + + /** + * 会员功能 + * + * @return 会员服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getVipService()}。 + */ + @Deprecated + WxChannelVipService getVipService(); + + /** + * 视频号助手-罗盘达人版服务 + * + * @return 罗盘达人版服务 + */ + WxChannelCompassFinderService getCompassFinderService(); + + /** + * 视频号助手-直播大屏数据服务 + * + * @return 直播大屏数据服务 + */ + WxChannelLiveDashboardService getLiveDashboardService(); + + /** + * 质检管理服务. + * + * @return 质检管理服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getQicService()}。 + */ + @Deprecated + WxChannelQicService getQicService(); + + /** + * 微信小店-带货助手服务 + * + * @return 带货助手服务 + */ + WxTalentService getTalentService(); + + /** + * 收藏管理服务 + * + * @return 收藏管理服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getFavoriteService()}。 + */ + @Deprecated + WxChannelFavoriteService getFavoriteService(); + + /** + * 电子面单服务 + * + * @return 电子面单服务 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreService#getEwaybillService()}。 + */ + @Deprecated + default WxChannelEwaybillService getEwaybillService() { + throw new UnsupportedOperationException("当前 WxChannelService 实现不支持电子面单服务"); + } + } 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 index 300493158b..c802a23071 100644 --- 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 @@ -13,7 +13,9 @@ * 视频号小店 分享员服务接口 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreSharerService}。 */ +@Deprecated public interface WxChannelSharerService { /** diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelSupplierService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelSupplierService.java new file mode 100644 index 0000000000..48aa0a97c0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelSupplierService.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.supplier.DistributeTypeResponse; +import me.chanjar.weixin.channel.bean.supplier.DropshipAssignRequest; +import me.chanjar.weixin.channel.bean.supplier.DropshipDetailResponse; +import me.chanjar.weixin.channel.bean.supplier.DropshipListRequest; +import me.chanjar.weixin.channel.bean.supplier.DropshipListResponse; +import me.chanjar.weixin.channel.bean.supplier.DropshipResponse; +import me.chanjar.weixin.channel.bean.supplier.DropshipSearchRequest; +import me.chanjar.weixin.channel.bean.supplier.ProductDistributeRequest; +import me.chanjar.weixin.channel.bean.supplier.ProductListResponse; +import me.chanjar.weixin.channel.bean.supplier.SupplierInfoResponse; +import me.chanjar.weixin.channel.bean.supplier.SupplierListResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店代发管理服务。 + * + * @author GitHub Copilot + * @see 代发管理接口文档 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreSupplierService}。 + */ +@Deprecated +public interface WxChannelSupplierService { + + /** 获取供货商列表。 */ + SupplierListResponse getSupplierList() throws WxErrorException; + + /** + * 获取供货商列表。 + * + * @param pageSize 每页数量 + * @param nextKey 由上次请求返回,记录翻页的上下文。传入时会从上次返回的结果往后翻一页 + * @return 供货商列表响应 + * @throws WxErrorException 异常 + */ + SupplierListResponse getSupplierList(Integer pageSize, String nextKey) throws WxErrorException; + + /** 获取分配方式。 */ + DistributeTypeResponse getDistribute() throws WxErrorException; + + /** 设置全店订单手动分配。 */ + WxChannelBaseResponse setManuallyDistribute() throws WxErrorException; + + /** 设置全店订单自动分配。 */ + WxChannelBaseResponse setAllDistribute(String supplierId) throws WxErrorException; + + /** 设置按商品自动分配。 */ + WxChannelBaseResponse setProductDistribute(ProductDistributeRequest req) throws WxErrorException; + + /** 获取商品对应的自动分配供货商。 */ + SupplierInfoResponse getProductDefaultDistribute(String productId) throws WxErrorException; + + /** 获取按商品自动分配的商品列表。 */ + ProductListResponse getProductList(String supplierId) throws WxErrorException; + + /** 分配订单代发。 */ + DropshipResponse assignOrder(DropshipAssignRequest req) throws WxErrorException; + + /** 取消分配代发单。 */ + WxChannelBaseResponse cancelDropship(String orderId) throws WxErrorException; + + /** 查询代发单详情。 */ + DropshipDetailResponse getDropship(String orderId) throws WxErrorException; + + /** 拉取代发单列表。 */ + DropshipListResponse listDropship(DropshipListRequest req) throws WxErrorException; + + /** 搜索代发单。 */ + DropshipListResponse searchDropship(DropshipSearchRequest req) 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..1ca08ff69f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelVipService.java @@ -0,0 +1,99 @@ +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 会员功能接口文档 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreVipService}。 + */ +@Deprecated +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 index 1bb00885f5..4d9a60a7d0 100644 --- 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 @@ -18,7 +18,9 @@ * 视频号小店 区域仓库服务 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreWarehouseService}。 */ +@Deprecated public interface WxChannelWarehouseService { /** 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/WxLeaguePromoterService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxLeaguePromoterService.java index 8a8ffb9acf..60cf112271 100644 --- 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 @@ -15,43 +15,84 @@ public interface WxLeaguePromoterService { /** * 新增达人 * - * @param finderId 视频号finder_id + * @param finderId 视频号finder_id,待废除 * @return 结果 + * @deprecated 使用 {@link #addPromoterV2(String)} */ + @Deprecated WxChannelBaseResponse addPromoter(String finderId) throws WxErrorException; /** * 编辑达人 * - * @param finderId 视频号finder_id + * @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 + * @param finderId 视频号finder_id,待废除 * @return 结果 + * @deprecated 使用 {@link #deletePromoterV2(String)} */ + @Deprecated WxChannelBaseResponse deletePromoter(String finderId) throws WxErrorException; /** * 获取达人详情信息 * - * @param finderId 视频号finder_id + * @param finderId 视频号finder_id,待废除 * @return 结果 + * @deprecated 使用 {@link #getPromoterInfoV2(String)} */ + @Deprecated PromoterInfoResponse getPromoterInfo(String finderId) throws WxErrorException; - /** - * 获取达人列表 - * - * @param pageIndex 页面下标,下标从1开始,默认为1 - * @param pageSize 单页达人数(不超过200) - * @param status 拉取该状态下的达人列表 - * @return 结果 - */ - PromoterListResponse listPromoter(Integer pageIndex, Integer pageSize, Integer status) 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/WxStoreCooperationService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxStoreCooperationService.java new file mode 100644 index 0000000000..477dfec384 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxStoreCooperationService.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.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 合作账号状态机 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreCooperationService}。 + */ +@Deprecated +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..5d6d975105 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxStoreHomePageService.java @@ -0,0 +1,190 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxStoreHomePageService}。 + */ +@Deprecated +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/WxTalentService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxTalentService.java new file mode 100644 index 0000000000..c5737443ad --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxTalentService.java @@ -0,0 +1,58 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.talent.TalentOrderDetailParam; +import me.chanjar.weixin.channel.bean.talent.TalentOrderDetailResponse; +import me.chanjar.weixin.channel.bean.talent.TalentOrderListParam; +import me.chanjar.weixin.channel.bean.talent.TalentOrderListResponse; +import me.chanjar.weixin.channel.bean.talent.TalentWindowProductDetailParam; +import me.chanjar.weixin.channel.bean.talent.TalentWindowProductDetailResponse; +import me.chanjar.weixin.channel.bean.talent.TalentWindowProductListParam; +import me.chanjar.weixin.channel.bean.talent.TalentWindowProductListResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店-带货助手服务接口 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.api.WxTalentService}。 + */ +@Deprecated +public interface WxTalentService { + + /** + * 获取佣金单列表 + * + * @param param 查询参数 + * @return 佣金单列表 + * @throws WxErrorException 接口调用异常 + */ + TalentOrderListResponse getOrderList(TalentOrderListParam param) throws WxErrorException; + + /** + * 获取佣金单详情 + * + * @param param 查询参数 + * @return 佣金单详情 + * @throws WxErrorException 接口调用异常 + */ + TalentOrderDetailResponse getOrderDetail(TalentOrderDetailParam param) throws WxErrorException; + + /** + * 获取达人橱窗商品列表 + * + * @param param 查询参数 + * @return 橱窗商品列表 + * @throws WxErrorException 接口调用异常 + */ + TalentWindowProductListResponse getWindowProductList(TalentWindowProductListParam param) throws WxErrorException; + + /** + * 获取达人橱窗商品详情 + * + * @param param 查询参数 + * @return 橱窗商品详情 + * @throws WxErrorException 接口调用异常 + */ + TalentWindowProductDetailResponse getWindowProductDetail(TalentWindowProductDetailParam param) + 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 index 008da958a9..0aeabdd7c6 100644 --- 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 @@ -1,34 +1,5 @@ package me.chanjar.weixin.channel.api.impl; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.ACCOUNT_NOTIFY; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.AFTER_SALE_UPDATE; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.BRAND; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.COMPLAINT_NOTIFY; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.CREATE_COUPON; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.DELETE_COUPON; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.EXPIRE_COUPON; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.INVALID_COUPON; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.ORDER_CANCEL; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.ORDER_CONFIRM; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.ORDER_DELIVER; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.ORDER_EXT_INFO_UPDATE; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.ORDER_NEW; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.ORDER_PAY; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.ORDER_SETTLE; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.ORDER_STATUS_UPDATE; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.PRODUCT_CATEGORY_AUDIT; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.PRODUCT_SPU_AUDIT; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.PRODUCT_SPU_STATUS_UPDATE; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.PRODUCT_SPU_UPDATE; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.QRCODE_STATUS; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.RECEIVE_COUPON; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.SUPPLIER_ITEM_UPDATE; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.UPDATE_COUPON_INFO; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.USER_COUPON_EXPIRE; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.USER_COUPON_UNUSE; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.USER_COUPON_USE; -import static me.chanjar.weixin.channel.constant.MessageEventConstants.WITHDRAW_NOTIFY; - import java.util.Map; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.channel.api.BaseWxChannelMessageService; @@ -52,7 +23,14 @@ 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; @@ -60,11 +38,13 @@ 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 class BaseWxChannelMessageServiceImpl implements BaseWxChannelMessageService { +public abstract class BaseWxChannelMessageServiceImpl implements BaseWxChannelMessageService { /** 消息路由器 */ protected WxChannelMessageRouter router; @@ -86,6 +66,8 @@ protected void addDefaultRule() { 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); /* 订单下单 */ @@ -94,6 +76,8 @@ protected void addDefaultRule() { 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); /* 订单确认收货 */ @@ -126,6 +110,8 @@ protected void addDefaultRule() { 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); /* 提现回调 */ @@ -134,6 +120,25 @@ protected void addDefaultRule() { 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); } /** @@ -144,17 +149,23 @@ protected void addDefaultRule() { * @param consumer 处理器 * @param 消息类型 */ - protected void addRule(Class clazz, String event, - HandlerConsumer, WxSessionManager> consumer) { + protected void addRule(Class clazz, String event, Boolean async, + HandlerConsumer, WxSessionManager> consumer) { WxChannelMessageRouterRule rule = new WxChannelMessageRouterRule<>(); - rule.setMessageClass(clazz).setEvent(event).setAsync(true); + 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); @@ -184,6 +195,12 @@ public void orderPay(OrderPayMessage message, String content, String appId, 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) { @@ -232,6 +249,12 @@ public void spuUpdate(SpuAuditMessage message, String content, String appId, 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) { @@ -310,6 +333,12 @@ public void userCouponUnuse(UserCouponExpireMessage message, String content, Str 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) { @@ -340,4 +369,51 @@ public Object defaultMessageHandler(WxChannelMessage message, String content, St 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 index 6dd12a5b51..427a24d3b7 100644 --- 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 @@ -2,36 +2,19 @@ import com.google.gson.JsonObject; -import java.io.IOException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.channel.api.WxChannelAddressService; -import me.chanjar.weixin.channel.api.WxChannelAfterSaleService; -import me.chanjar.weixin.channel.api.WxChannelBasicService; -import me.chanjar.weixin.channel.api.WxChannelBrandService; -import me.chanjar.weixin.channel.api.WxChannelCategoryService; -import me.chanjar.weixin.channel.api.WxChannelCouponService; -import me.chanjar.weixin.channel.api.WxChannelFreightTemplateService; -import me.chanjar.weixin.channel.api.WxChannelFundService; -import me.chanjar.weixin.channel.api.WxChannelOrderService; -import me.chanjar.weixin.channel.api.WxChannelProductService; -import me.chanjar.weixin.channel.api.WxChannelService; -import me.chanjar.weixin.channel.api.WxChannelSharerService; -import me.chanjar.weixin.channel.api.WxChannelWarehouseService; -import me.chanjar.weixin.channel.api.WxLeagueProductService; -import me.chanjar.weixin.channel.api.WxLeaguePromoterService; -import me.chanjar.weixin.channel.api.WxLeagueSupplierService; -import me.chanjar.weixin.channel.api.WxLeagueWindowService; +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; @@ -40,6 +23,10 @@ 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 @@ -50,7 +37,14 @@ public abstract class BaseWxChannelServiceImpl implements WxChannelService 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 WxChannelGiftService giftService = new WxChannelGiftServiceImpl(this); + private final WxChannelLimitedDiscountService limitedDiscountService = + new WxChannelLimitedDiscountServiceImpl(this); + private final WxChannelProductStockService productStockService = new WxChannelProductStockServiceImpl(this); + private final WxChannelProductAssistantService productAssistantService = + new WxChannelProductAssistantServiceImpl(this); + private final WxChannelProductService productService = new WxChannelProductServiceImpl( + this, giftService, limitedDiscountService, productStockService); private final WxChannelWarehouseService warehouseService = new WxChannelWarehouseServiceImpl(this); private final WxChannelOrderService orderService = new WxChannelOrderServiceImpl(this); private final WxChannelAfterSaleService afterSaleService = new WxChannelAfterSaleServiceImpl(this); @@ -60,17 +54,32 @@ public abstract class BaseWxChannelServiceImpl implements WxChannelService 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 WxChannelSupplierService supplierService = 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; + private WxChannelQicService qicService = null; + private WxTalentService talentService = null; + private WxChannelFavoriteService favoriteService = null; + private WxChannelEwaybillService ewaybillService = null; + private WxChannelKfService kfService = null; protected WxChannelConfig config; private int retrySleepMillis = 1000; private int maxRetryTimes = 5; @Override - public RequestHttp getRequestHttp() { + public RequestHttp getRequestHttp() { return this; } @@ -79,7 +88,7 @@ public boolean checkSignature(String timestamp, String nonce, String signature) try { return SHA1.gen(this.getConfig().getToken(), timestamp, nonce).equals(signature); } catch (Exception e) { - log.error("Checking signature failed, and the reason is :" + e.getMessage()); + log.error("Checking signature failed, and the reason is :{}", e.getMessage()); return false; } } @@ -104,7 +113,12 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { return this.getConfig().getAccessToken(); } } while (!locked); - String response = doGetAccessTokenRequest(); + String response; + if (getConfig().isStableAccessToken()) { + response = doGetStableAccessTokenRequest(forceRefresh); + } else { + response = doGetAccessTokenRequest(); + } return extractAccessToken(response); } catch (IOException | InterruptedException e) { throw new WxRuntimeException(e); @@ -118,12 +132,19 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { /** * 通过网络请求获取AccessToken * - * @return . - * - * @throws IOException . + * @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); @@ -145,6 +166,12 @@ 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()); @@ -200,7 +227,7 @@ protected T execute0(RequestExecutor executor, String uri, E data, } protected T executeInternal(RequestExecutor executor, String uri, E data, boolean doNotAutoRefreshToken, - boolean printResult) throws WxErrorException { + boolean printResult) throws WxErrorException { E dataForLog = DataUtils.handleDataWithSecret(data); if (uri.contains("access_token=")) { @@ -217,7 +244,8 @@ protected T executeInternal(RequestExecutor executor, String uri, E try { T result = executor.execute(uriWithAccessToken, data, WxType.Channel); - log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, dataForLog, + log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, + printResult ? dataForLog : "...", printResult ? result : "..."); return result; } catch (WxErrorException e) { @@ -244,12 +272,14 @@ protected T executeInternal(RequestExecutor executor, String uri, E } if (error.getErrorCode() != 0) { - log.warn("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error); + log.warn("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, + printResult ? dataForLog : "...", error); throw new WxErrorException(error, e); } return null; } catch (IOException e) { - log.warn("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage()); + log.warn("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, + printResult ? dataForLog : "...", e.getMessage()); throw new WxRuntimeException(e); } } @@ -259,13 +289,12 @@ protected T executeInternal(RequestExecutor executor, String uri, E * * @param resultContent 响应内容 * @return access token - * * @throws WxErrorException 异常 */ protected String extractAccessToken(String resultContent) throws WxErrorException { - log.info("resultContent: " + resultContent); + log.debug("access-token response: {}", resultContent); WxChannelConfig config = this.getConfig(); - WxError error = WxError.fromJson(resultContent, WxType.MiniApp); + WxError error = WxError.fromJson(resultContent, WxType.Channel); if (error.getErrorCode() != 0) { throw new WxErrorException(error); } @@ -315,6 +344,26 @@ public WxChannelProductService getProductService() { return productService; } + @Override + public WxChannelGiftService getGiftService() { + return giftService; + } + + @Override + public WxChannelLimitedDiscountService getLimitedDiscountService() { + return limitedDiscountService; + } + + @Override + public WxChannelProductStockService getProductStockService() { + return productStockService; + } + + @Override + public WxChannelProductAssistantService getProductAssistantService() { + return productAssistantService; + } + @Override public WxChannelWarehouseService getWarehouseService() { return warehouseService; @@ -355,6 +404,38 @@ 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 WxChannelSupplierService getSupplierService() { + if (supplierService == null) { + supplierService = new WxChannelSupplierServiceImpl(this); + } + return supplierService; + } + @Override public synchronized WxLeagueWindowService getLeagueWindowService() { if (leagueWindowService == null) { @@ -372,7 +453,7 @@ public synchronized WxLeagueSupplierService getLeagueSupplierService() { } @Override - public synchronized WxLeaguePromoterService getLeaguePromoterService() { + public synchronized WxLeaguePromoterService getLeaguePromoterService() { if (leaguePromoterService == null) { leaguePromoterService = new WxLeaguePromoterServiceImpl(this); } @@ -386,4 +467,94 @@ public synchronized WxLeagueProductService getLeagueProductService() { } 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; + } + + @Override + public synchronized WxChannelQicService getQicService() { + if (qicService == null) { + qicService = new WxChannelQicServiceImpl(this); + } + return qicService; + } + + @Override + public synchronized WxTalentService getTalentService() { + if (talentService == null) { + talentService = new WxTalentServiceImpl(this); + } + return talentService; + } + + @Override + public synchronized WxChannelFavoriteService getFavoriteService() { + if (favoriteService == null) { + favoriteService = new WxChannelFavoriteServiceImpl(this); + } + return favoriteService; + } + + @Override + public synchronized WxChannelEwaybillService getEwaybillService() { + if (ewaybillService == null) { + ewaybillService = new WxChannelEwaybillServiceImpl(this); + } + return ewaybillService; + } + + @Override + public synchronized WxChannelKfService getKfService() { + if (kfService == null) { + kfService = new WxChannelKfServiceImpl(this); + } + return kfService; + } + } 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..58d6d45269 --- /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, req); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public GetWindowProductResponse getWindowProduct(WindowProductRequest req) throws WxErrorException { + String resJson = shopService.post(GET_WINDOW_PRODUCT_URL, req); + return ResponseUtils.decode(resJson, GetWindowProductResponse.class); + } + + @Override + public GetWindowProductListResponse getWindowProductList(GetWindowProductListRequest req) throws WxErrorException { + String resJson = shopService.post(LIST_WINDOW_PRODUCT_URL, req); + return ResponseUtils.decode(resJson, GetWindowProductListResponse.class); + } + + @Override + public WxChannelBaseResponse offWindowProduct(WindowProductRequest req) throws WxErrorException { + String resJson = shopService.post(OFF_WINDOW_PRODUCT_URL, req); + 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 index 53b9eb4d7a..20cf128559 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAddressServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAddressServiceImpl.java @@ -29,9 +29,9 @@ public class WxChannelAddressServiceImpl implements WxChannelAddressService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelAddressServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelAddressServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImpl.java index c29ea49b34..5f8c738741 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImpl.java @@ -1,30 +1,19 @@ package me.chanjar.weixin.channel.api.impl; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.AfterSale.AFTER_SALE_ACCEPT_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.AfterSale.AFTER_SALE_GET_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.AfterSale.AFTER_SALE_LIST_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.AfterSale.AFTER_SALE_REJECT_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.AfterSale.AFTER_SALE_UPLOAD_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Complaint.ADD_COMPLAINT_MATERIAL_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Complaint.ADD_COMPLAINT_PROOF_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Complaint.GET_COMPLAINT_ORDER_URL; - -import java.util.List; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.channel.api.WxChannelAfterSaleService; -import me.chanjar.weixin.channel.bean.after.AfterSaleAcceptParam; -import me.chanjar.weixin.channel.bean.after.AfterSaleIdParam; -import me.chanjar.weixin.channel.bean.after.AfterSaleInfoResponse; -import me.chanjar.weixin.channel.bean.after.AfterSaleListParam; -import me.chanjar.weixin.channel.bean.after.AfterSaleListResponse; -import me.chanjar.weixin.channel.bean.after.AfterSaleRejectParam; -import me.chanjar.weixin.channel.bean.after.RefundEvidenceParam; +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.*; + /** * 视频号小店 售后服务实现 * @@ -33,17 +22,25 @@ @Slf4j public class WxChannelAfterSaleServiceImpl implements WxChannelAfterSaleService { - /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + /** + * 微信商店服务 + */ + private final BaseWxChannelServiceImpl shopService; - public WxChannelAfterSaleServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelAfterSaleServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } @Override public AfterSaleListResponse listIds(Long beginCreateTime, Long endCreateTime, String nextKey) throws WxErrorException { - AfterSaleListParam param = new AfterSaleListParam(beginCreateTime, endCreateTime, nextKey); + AfterSaleListParam param = new AfterSaleListParam(beginCreateTime, endCreateTime, null, null, nextKey); + String resJson = shopService.post(AFTER_SALE_LIST_URL, param); + return ResponseUtils.decode(resJson, AfterSaleListResponse.class); + } + + @Override + public AfterSaleListResponse listIds(AfterSaleListParam param) throws WxErrorException { String resJson = shopService.post(AFTER_SALE_LIST_URL, param); return ResponseUtils.decode(resJson, AfterSaleListResponse.class); } @@ -56,15 +53,21 @@ public AfterSaleInfoResponse get(String afterSaleOrderId) throws WxErrorExceptio } @Override - public WxChannelBaseResponse accept(String afterSaleOrderId, String addressId) throws WxErrorException { - AfterSaleAcceptParam param = new AfterSaleAcceptParam(afterSaleOrderId, addressId); + 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) throws WxErrorException { - AfterSaleRejectParam param = new AfterSaleRejectParam(afterSaleOrderId, rejectReason); + public WxChannelBaseResponse reject(String afterSaleOrderId, String rejectReason, Integer rejectReasonType) throws WxErrorException { + return reject(afterSaleOrderId, rejectReason, rejectReasonType, null); + } + + @Override + public WxChannelBaseResponse reject(String afterSaleOrderId, String rejectReason, Integer rejectReasonType, + List rejectCertificates) throws WxErrorException { + AfterSaleRejectParam param = new AfterSaleRejectParam(afterSaleOrderId, rejectReason, rejectReasonType, rejectCertificates); String resJson = shopService.post(AFTER_SALE_REJECT_URL, param); return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); } @@ -100,4 +103,75 @@ public ComplaintOrderResponse getComplaint(String complaintId) throws WxErrorExc 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); + } + + @Override + public GuaranteeOrderListResponse listGuaranteeOrder(GuaranteeOrderListParam param) throws WxErrorException { + String resJson = shopService.post(GUARANTEE_ORDER_LIST_URL, param); + return ResponseUtils.decode(resJson, GuaranteeOrderListResponse.class); + } + + @Override + public GuaranteeOrderInfoResponse getGuaranteeOrder(String guaranteeOrderId) throws WxErrorException { + GuaranteeOrderIdParam param = new GuaranteeOrderIdParam(guaranteeOrderId); + String resJson = shopService.post(GUARANTEE_ORDER_GET_URL, param); + return ResponseUtils.decode(resJson, GuaranteeOrderInfoResponse.class); + } + + @Override + public WxChannelBaseResponse acceptGuarantee(String guaranteeOrderId) throws WxErrorException { + GuaranteeOrderIdParam param = new GuaranteeOrderIdParam(guaranteeOrderId); + String resJson = shopService.post(GUARANTEE_ORDER_ACCEPT_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse modifyGuarantee(GuaranteeModifyRequest request) throws WxErrorException { + String resJson = shopService.post(GUARANTEE_ORDER_MODIFY_URL, request); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse proofGuarantee(GuaranteeProofRequest request) throws WxErrorException { + String resJson = shopService.post(GUARANTEE_ORDER_PROOF_URL, request); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse refuseGuarantee(GuaranteeRefuseRequest request) throws WxErrorException { + String resJson = shopService.post(GUARANTEE_ORDER_REFUSE_URL, request); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImpl.java index cac5e9e513..45a869216e 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBasicServiceImpl.java @@ -2,7 +2,10 @@ 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_H5URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Basics.GET_SHOP_INFO; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Basics.GET_SHOP_QRCODE; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Basics.GET_SHOP_TAGLINK; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Basics.IMG_UPLOAD_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Basics.UPLOAD_QUALIFICATION_FILE; @@ -16,7 +19,10 @@ 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.ShopH5UrlResponse; import me.chanjar.weixin.channel.bean.shop.ShopInfoResponse; +import me.chanjar.weixin.channel.bean.shop.ShopQrCodeResponse; +import me.chanjar.weixin.channel.bean.shop.ShopTagLinkResponse; import me.chanjar.weixin.channel.executor.ChannelFileUploadRequestExecutor; import me.chanjar.weixin.channel.executor.ChannelMediaDownloadRequestExecutor; import me.chanjar.weixin.channel.util.ResponseUtils; @@ -31,9 +37,9 @@ public class WxChannelBasicServiceImpl implements WxChannelBasicService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelBasicServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelBasicServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } @@ -56,7 +62,7 @@ public ChannelImageInfo uploadImg(int respType, String imgUrl) throws WxErrorExc public ChannelImageInfo uploadImg(int respType, File file, int height, int width) throws WxErrorException { String url = IMG_UPLOAD_URL + "?upload_type=0&resp_type=" + respType + "&height=" + height + "&width=" + width; RequestExecutor executor = ChannelFileUploadRequestExecutor.create(shopService); - String resJson = (String) shopService.execute(executor, url, file); + String resJson = shopService.execute(executor, url, file); UploadImageResponse response = ResponseUtils.decode(resJson, UploadImageResponse.class); return response.getImgInfo(); } @@ -64,19 +70,19 @@ public ChannelImageInfo uploadImg(int respType, File file, int height, int width @Override public QualificationFileResponse uploadQualificationFile(File file) throws WxErrorException { RequestExecutor executor = ChannelFileUploadRequestExecutor.create(shopService); - String resJson = (String) shopService.execute(executor, UPLOAD_QUALIFICATION_FILE, file); + String resJson = shopService.execute(executor, UPLOAD_QUALIFICATION_FILE, file); return ResponseUtils.decode(resJson, QualificationFileResponse.class); } @Override public ChannelImageResponse getImg(String mediaId) throws WxErrorException { String appId = shopService.getConfig().getAppid(); - ChannelImageResponse rs = null; + ChannelImageResponse rs; try { String url = GET_IMG_URL + "?media_id=" + mediaId; RequestExecutor executor = ChannelMediaDownloadRequestExecutor.create(shopService, Files.createTempDirectory("wxjava-channel-" + appId).toFile()); - rs = (ChannelImageResponse) shopService.execute(executor, url, null); + rs = shopService.execute(executor, url, null); } catch (IOException e) { throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e); } @@ -93,4 +99,23 @@ public AddressCodeResponse getAddressCode(Integer code) throws WxErrorException return ResponseUtils.decode(resJson, AddressCodeResponse.class); } + @Override + public ShopH5UrlResponse getShopH5Url() throws WxErrorException { + String resJson = shopService.post(GET_SHOP_H5URL, "{}"); + return ResponseUtils.decode(resJson, ShopH5UrlResponse.class); + } + + @Override + public ShopQrCodeResponse getShopQrCode(int qrcodeType) throws WxErrorException { + String reqJson = "{\"qrcode_type\":" + qrcodeType + "}"; + String resJson = shopService.post(GET_SHOP_QRCODE, reqJson); + return ResponseUtils.decode(resJson, ShopQrCodeResponse.class); + } + + @Override + public ShopTagLinkResponse getShopTagLink() throws WxErrorException { + String resJson = shopService.post(GET_SHOP_TAGLINK, "{}"); + return ResponseUtils.decode(resJson, ShopTagLinkResponse.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 index 19aadcc06e..c6c476b116 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBrandServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelBrandServiceImpl.java @@ -33,9 +33,9 @@ public class WxChannelBrandServiceImpl implements WxChannelBrandService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelBrandServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelBrandServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImpl.java index 52fdf3cdf8..7070ab9298 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImpl.java @@ -1,13 +1,5 @@ package me.chanjar.weixin.channel.api.impl; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.ADD_CATEGORY_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.AVAILABLE_CATEGORY_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.CANCEL_CATEGORY_AUDIT_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.GET_CATEGORY_AUDIT_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.GET_CATEGORY_DETAIL_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.LIST_ALL_CATEGORY_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.LIST_PASS_CATEGORY_URL; - import java.util.Collections; import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -17,17 +9,15 @@ import me.chanjar.weixin.channel.bean.audit.CategoryAuditInfo; import me.chanjar.weixin.channel.bean.audit.CategoryAuditRequest; import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; -import me.chanjar.weixin.channel.bean.category.CategoryDetailResult; -import me.chanjar.weixin.channel.bean.category.CategoryQualificationResponse; -import me.chanjar.weixin.channel.bean.category.PassCategoryResponse; -import me.chanjar.weixin.channel.bean.category.ShopCategory; -import me.chanjar.weixin.channel.bean.category.ShopCategoryResponse; +import me.chanjar.weixin.channel.bean.category.*; import me.chanjar.weixin.channel.util.JsonUtils; import me.chanjar.weixin.channel.util.ResponseUtils; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Category.*; + /** * 视频号小店 商品类目相关接口 * @@ -37,9 +27,9 @@ public class WxChannelCategoryServiceImpl implements WxChannelCategoryService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelCategoryServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelCategoryServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } @@ -56,7 +46,7 @@ public List listAvailableCategory(String parentId) throws WxErrorE try { pid = Long.parseLong(parentId); } catch (Throwable e) { - log.error("parentId必须为数字, " + parentId, e); + log.error("parentId必须为数字, {}", parentId, e); return Collections.emptyList(); } String reqJson = "{\"f_cat_id\": " + pid + "}"; @@ -66,13 +56,21 @@ public List listAvailableCategory(String parentId) throws WxErrorE 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); + log.error("id必须为数字, {}", id, e); return ResponseUtils.internalError(CategoryDetailResult.class); } String reqJson = "{\"cat_id\": " + catId + "}"; @@ -89,7 +87,11 @@ public AuditApplyResponse addCategory(String level1, String level2, String level Long l1 = Long.parseLong(level1); Long l2 = Long.parseLong(level2); Long l3 = Long.parseLong(level3); - CategoryAuditInfo categoryInfo = new CategoryAuditInfo(l1, l2, l3, certificate); + 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); @@ -98,6 +100,13 @@ public AuditApplyResponse addCategory(String level1, String level2, String level 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 + "\"}"); @@ -116,4 +125,15 @@ public PassCategoryResponse listPassCategory() throws WxErrorException { return ResponseUtils.decode(resJson, PassCategoryResponse.class); } + @Override + public RelationCategoryResponse listRelationCategory(Boolean isFilterStatus, Integer status) throws WxErrorException { + RelationCategoryRequest request = new RelationCategoryRequest( + isFilterStatus != null ? isFilterStatus : false, + status != null ? status : 0 + ); + String reqJson = JsonUtils.encode(request); + String resJson = shopService.post(LIST_RELATION_CATEGORY_URL, reqJson); + return ResponseUtils.decode(resJson, RelationCategoryResponse.class); + } + } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/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 index 174626f4a9..22abf25fb0 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCouponServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelCouponServiceImpl.java @@ -35,9 +35,9 @@ public class WxChannelCouponServiceImpl implements WxChannelCouponService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelCouponServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelCouponServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelEwaybillServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelEwaybillServiceImpl.java new file mode 100644 index 0000000000..293230585f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelEwaybillServiceImpl.java @@ -0,0 +1,165 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.ADD_SUB_ORDER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.BATCH_PRINT_ORDER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.CANCEL_ORDER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.CREATE_ORDER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.CREATE_TEMPLATE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.DELETE_TEMPLATE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.GET_ACCOUNT_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.GET_DELIVERY_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.GET_ORDER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.GET_PRINT_CONTENT_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.GET_TEMPLATE_BY_ID_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.GET_TEMPLATE_CONFIG_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.GET_TEMPLATE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.PRE_CREATE_ORDER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.PRINT_ORDER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Ewaybill.UPDATE_TEMPLATE_URL; + +import java.util.List; +import me.chanjar.weixin.channel.api.WxChannelEwaybillService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.ewaybill.AccountInfoResponse; +import me.chanjar.weixin.channel.bean.ewaybill.AddSubOrderRequest; +import me.chanjar.weixin.channel.bean.ewaybill.CreateOrderRequest; +import me.chanjar.weixin.channel.bean.ewaybill.CreateOrderResponse; +import me.chanjar.weixin.channel.bean.ewaybill.DeliveryListResponse; +import me.chanjar.weixin.channel.bean.ewaybill.EwaybillOrderIdParam; +import me.chanjar.weixin.channel.bean.ewaybill.PrintOrderRequest; +import me.chanjar.weixin.channel.bean.ewaybill.BatchPrintOrderRequest; +import me.chanjar.weixin.channel.bean.ewaybill.OrderDetailResponse; +import me.chanjar.weixin.channel.bean.ewaybill.PreCreateRequest; +import me.chanjar.weixin.channel.bean.ewaybill.PreCreateResponse; +import me.chanjar.weixin.channel.bean.ewaybill.PrintContentResponse; +import me.chanjar.weixin.channel.bean.ewaybill.PrintContentParam; +import me.chanjar.weixin.channel.bean.ewaybill.TemplateCodeParam; +import me.chanjar.weixin.channel.bean.ewaybill.TemplateConfigResponse; +import me.chanjar.weixin.channel.bean.ewaybill.TemplateCreateRequest; +import me.chanjar.weixin.channel.bean.ewaybill.TemplateIdParam; +import me.chanjar.weixin.channel.bean.ewaybill.TemplateIdResponse; +import me.chanjar.weixin.channel.bean.ewaybill.TemplateInfoResponse; +import me.chanjar.weixin.channel.bean.ewaybill.TemplateUpdateRequest; +import me.chanjar.weixin.channel.bean.ewaybill.WaybillIdParam; +import me.chanjar.weixin.channel.bean.ewaybill.WaybillIdsParam; +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.SimplePostRequestExecutor; + +/** + * 视频号小店电子面单服务实现。 + * + * @author GitHub Copilot + */ +public class WxChannelEwaybillServiceImpl implements WxChannelEwaybillService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelEwaybillServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public TemplateConfigResponse getTemplateConfig() throws WxErrorException { + String resJson = post(GET_TEMPLATE_CONFIG_URL, "{}"); + return ResponseUtils.decode(resJson, TemplateConfigResponse.class); + } + + @Override + public TemplateIdResponse createTemplate(TemplateCreateRequest req) throws WxErrorException { + String resJson = post(CREATE_TEMPLATE_URL, req); + return ResponseUtils.decode(resJson, TemplateIdResponse.class); + } + + @Override + public WxChannelBaseResponse deleteTemplate(String templateId) throws WxErrorException { + String resJson = post(DELETE_TEMPLATE_URL, new TemplateIdParam(templateId)); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse updateTemplate(TemplateUpdateRequest req) throws WxErrorException { + String resJson = post(UPDATE_TEMPLATE_URL, req); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public TemplateInfoResponse getTemplate(String templateCode) throws WxErrorException { + String resJson = post(GET_TEMPLATE_URL, new TemplateCodeParam(templateCode)); + return ResponseUtils.decode(resJson, TemplateInfoResponse.class); + } + + @Override + public TemplateInfoResponse getTemplateById(String templateId) throws WxErrorException { + String resJson = post(GET_TEMPLATE_BY_ID_URL, new TemplateIdParam(templateId)); + return ResponseUtils.decode(resJson, TemplateInfoResponse.class); + } + + @Override + public AccountInfoResponse getAccount() throws WxErrorException { + String resJson = post(GET_ACCOUNT_URL, "{}"); + return ResponseUtils.decode(resJson, AccountInfoResponse.class); + } + + @Override + public DeliveryListResponse getDeliveryList() throws WxErrorException { + String resJson = post(GET_DELIVERY_LIST_URL, "{}"); + return ResponseUtils.decode(resJson, DeliveryListResponse.class); + } + + @Override + public PreCreateResponse preCreateOrder(PreCreateRequest req) throws WxErrorException { + String resJson = post(PRE_CREATE_ORDER_URL, req); + return ResponseUtils.decode(resJson, PreCreateResponse.class); + } + + @Override + public CreateOrderResponse createOrder(CreateOrderRequest req) throws WxErrorException { + String resJson = post(CREATE_ORDER_URL, req); + return ResponseUtils.decode(resJson, CreateOrderResponse.class); + } + + @Override + public WxChannelBaseResponse addSubOrder(AddSubOrderRequest req) throws WxErrorException { + String resJson = post(ADD_SUB_ORDER_URL, req); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse cancelOrder(PrintOrderRequest req) throws WxErrorException { + String resJson = post(CANCEL_ORDER_URL, req); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public OrderDetailResponse getOrder(String ewaybillOrderId) throws WxErrorException { + String resJson = post(GET_ORDER_URL, new EwaybillOrderIdParam(ewaybillOrderId)); + return ResponseUtils.decode(resJson, OrderDetailResponse.class); + } + + @Override + public PrintContentResponse getPrintContent(String ewaybillOrderId, String templateId) + throws WxErrorException { + String resJson = post(GET_PRINT_CONTENT_URL, new PrintContentParam(ewaybillOrderId, templateId)); + return ResponseUtils.decode(resJson, PrintContentResponse.class); + } + + @Override + public WxChannelBaseResponse printOrder(PrintOrderRequest req) throws WxErrorException { + String resJson = post(PRINT_ORDER_URL, req); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse batchPrintOrder(BatchPrintOrderRequest req) throws WxErrorException { + String resJson = post(BATCH_PRINT_ORDER_URL, req); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + private String post(String url, Object request) throws WxErrorException { + return shopService.executeWithoutLog( + SimplePostRequestExecutor.create(shopService), url, JsonUtils.encode(request)); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFavoriteServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFavoriteServiceImpl.java new file mode 100644 index 0000000000..8165f47987 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFavoriteServiceImpl.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Favorite.GET_FAVORITE_COUNT; + +import me.chanjar.weixin.channel.api.WxChannelFavoriteService; +import me.chanjar.weixin.channel.bean.favorite.FavoriteCountResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 视频号小店 收藏管理接口实现 + * + * @author GitHub Copilot + */ +public class WxChannelFavoriteServiceImpl implements WxChannelFavoriteService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelFavoriteServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public FavoriteCountResponse getFavoriteCount() throws WxErrorException { + String resJson = shopService.post(GET_FAVORITE_COUNT, "{}"); + return ResponseUtils.decode(resJson, FavoriteCountResponse.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 index 8fbfbd09c3..b8f00a4f84 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFreightTemplateServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFreightTemplateServiceImpl.java @@ -24,9 +24,9 @@ @Slf4j public class WxChannelFreightTemplateServiceImpl implements WxChannelFreightTemplateService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelFreightTemplateServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelFreightTemplateServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFundServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFundServiceImpl.java index 050a19f44d..7cf30905ec 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFundServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelFundServiceImpl.java @@ -54,9 +54,9 @@ public class WxChannelFundServiceImpl implements WxChannelFundService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelFundServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelFundServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelGiftServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelGiftServiceImpl.java new file mode 100644 index 0000000000..5d6a16bdc1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelGiftServiceImpl.java @@ -0,0 +1,104 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_ADD_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_DELETE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_STOP_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_ADD_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_GET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_ON_SALE_SET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_STOCK_UPDATE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_UPDATE_URL; + +import me.chanjar.weixin.channel.api.WxChannelGiftService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.GiftActivityAddParam; +import me.chanjar.weixin.channel.bean.product.GiftActivityAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftActivityInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductGetResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductListParam; +import me.chanjar.weixin.channel.bean.product.GiftProductListResponse; +import me.chanjar.weixin.channel.bean.product.SkuStockParam; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店赠品与买赠活动服务实现。 + */ +public class WxChannelGiftServiceImpl implements WxChannelGiftService { + + private final BaseWxChannelServiceImpl shopService; + + public WxChannelGiftServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public GiftProductAddResponse addGiftProduct(GiftProductInfo info) throws WxErrorException { + String reqJson = JsonUtils.encode(info); + String resJson = shopService.post(GIFT_PRODUCT_ADD_URL, reqJson); + return ResponseUtils.decode(resJson, GiftProductAddResponse.class); + } + + @Override + public WxChannelBaseResponse updateGiftProduct(GiftProductInfo info) throws WxErrorException { + String reqJson = JsonUtils.encode(info); + String resJson = shopService.post(GIFT_PRODUCT_UPDATE_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse setProductAsGift(String productId) throws WxErrorException { + String reqJson = "{\"product_id\":\"" + productId + "\"}"; + String resJson = shopService.post(GIFT_PRODUCT_ON_SALE_SET_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public GiftProductGetResponse getGiftProduct(String productId) throws WxErrorException { + String reqJson = "{\"product_id\":\"" + productId + "\"}"; + String resJson = shopService.post(GIFT_PRODUCT_GET_URL, reqJson); + return ResponseUtils.decode(resJson, GiftProductGetResponse.class); + } + + @Override + public GiftProductListResponse listGiftProduct(GiftProductListParam param) throws WxErrorException { + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(GIFT_PRODUCT_LIST_URL, reqJson); + return ResponseUtils.decode(resJson, GiftProductListResponse.class); + } + + @Override + public WxChannelBaseResponse updateGiftStock(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(GIFT_PRODUCT_STOCK_UPDATE_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public GiftActivityAddResponse addGiftActivity(GiftActivityInfo info) throws WxErrorException { + GiftActivityAddParam param = new GiftActivityAddParam(info); + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(GIFT_ACTIVITY_ADD_URL, reqJson); + return ResponseUtils.decode(resJson, GiftActivityAddResponse.class); + } + + @Override + public WxChannelBaseResponse deleteGiftActivity(String activityId) throws WxErrorException { + String reqJson = "{\"activity_id\":\"" + activityId + "\"}"; + String resJson = shopService.post(GIFT_ACTIVITY_DELETE_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse stopGiftActivity(String activityId) throws WxErrorException { + String reqJson = "{\"activity_id\":\"" + activityId + "\"}"; + String resJson = shopService.post(GIFT_ACTIVITY_STOP_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelKfServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelKfServiceImpl.java new file mode 100644 index 0000000000..21bef92e1e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelKfServiceImpl.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Kf.COS_UPLOAD_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Kf.SEND_MSG_URL; + +import me.chanjar.weixin.channel.api.WxChannelKfService; +import me.chanjar.weixin.channel.bean.kf.WxChannelKfCosUploadResponse; +import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgParam; +import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgResponse; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.bean.CommonUploadParam; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; + +/** 视频号小店商家客服服务实现。 */ +public class WxChannelKfServiceImpl implements WxChannelKfService { + + private final BaseWxChannelServiceImpl channelService; + + public WxChannelKfServiceImpl(BaseWxChannelServiceImpl channelService) { + this.channelService = channelService; + } + + @Override + public String uploadMedia(String openId, String msgType, byte[] file) throws WxErrorException { + return uploadMedia(openId, msgType, null, file); + } + + @Override + public String uploadMedia(String openId, String msgType, String fileName, byte[] file) throws WxErrorException { + CommonUploadParam uploadParam = CommonUploadParam.fromBytes("file", fileName, file) + .addFormField("open_id", openId) + .addFormField("msg_type", msgType); + String responseJson = channelService.upload(COS_UPLOAD_URL, uploadParam); + return ResponseUtils.decode(responseJson, WxChannelKfCosUploadResponse.class).getCosUrl(); + } + + @Override + public WxChannelKfSendMsgResponse sendMessage(WxChannelKfSendMsgParam param) throws WxErrorException { + String responseJson = channelService.executeWithoutLog(SimplePostRequestExecutor.create(channelService), SEND_MSG_URL, + JsonUtils.encode(param)); + return ResponseUtils.decode(responseJson, WxChannelKfSendMsgResponse.class); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java new file mode 100644 index 0000000000..26cca4d400 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java @@ -0,0 +1,68 @@ +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.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.STOP_LIMIT_TASK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.UPDATE_LIMIT_TASK_URL; + +import me.chanjar.weixin.channel.api.WxChannelLimitedDiscountService; +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.limit.LimitTaskUpdateParam; +import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateResponse; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店限时抢购服务实现。 + */ +public class WxChannelLimitedDiscountServiceImpl implements WxChannelLimitedDiscountService { + + private final BaseWxChannelServiceImpl shopService; + + public WxChannelLimitedDiscountServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @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); + } + + @Override + public LimitTaskUpdateResponse updateLimitTask(LimitTaskUpdateParam param) throws WxErrorException { + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(UPDATE_LIMIT_TASK_URL, reqJson); + return ResponseUtils.decode(resJson, LimitTaskUpdateResponse.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 index acd14a2876..eff93082d7 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImpl.java @@ -1,14 +1,33 @@ 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.DELIVERY_COMPENSATION_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.PRE_SHIPMENT_CHANGE_SKU_APPROVE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.PRE_SHIPMENT_CHANGE_SKU_GET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.PRE_SHIPMENT_CHANGE_SKU_REJECT_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.PRESENT_NOTE_ADD_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.PRESENT_SUB_ORDER_GET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.REAL_NUMBER_APPLY_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.REAL_NUMBER_VIEW_AUDIT_GET_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_NUMBER_APPLY_AGAIN_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.VIRTUAL_NUMBER_DELAY_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Order.VIRTUAL_TEL_NUMBER_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.PrivateNumber.ADD_PHONE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.PrivateNumber.GET_PHONE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.PrivateNumber.SEND_VERIFY_CODE_URL; import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -18,16 +37,30 @@ 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.OrderCompensationDeliveryParam; 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.PreShipmentChangeSkuRejectParam; +import me.chanjar.weixin.channel.bean.order.PreShipmentChangeSkuResponse; +import me.chanjar.weixin.channel.bean.order.PresentNoteAddParam; +import me.chanjar.weixin.channel.bean.order.PresentSubOrderResponse; +import me.chanjar.weixin.channel.bean.order.PrivateNumberAddPhoneParam; +import me.chanjar.weixin.channel.bean.order.PrivateNumberGetPhoneResponse; +import me.chanjar.weixin.channel.bean.order.PrivateNumberSendVerifyCodeParam; +import me.chanjar.weixin.channel.bean.order.RealNumberViewAuditResponse; +import me.chanjar.weixin.channel.bean.order.VirtualTelNumberResponse; import me.chanjar.weixin.channel.util.ResponseUtils; import me.chanjar.weixin.common.error.WxErrorException; @@ -41,15 +74,22 @@ public class WxChannelOrderServiceImpl implements WxChannelOrderService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelOrderServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelOrderServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } @Override public OrderInfoResponse getOrder(String orderId) throws WxErrorException { - OrderIdParam param = new OrderIdParam(orderId); + OrderInfoParam param = new OrderInfoParam(orderId, null); + String resJson = shopService.post(ORDER_GET_URL, param); + return ResponseUtils.decode(resJson, OrderInfoResponse.class); + } + + @Override + public OrderInfoResponse getOrder(String orderId, Boolean encodeSensitiveInfo) throws WxErrorException { + OrderInfoParam param = new OrderInfoParam(orderId, encodeSensitiveInfo); String resJson = shopService.post(ORDER_GET_URL, param); return ResponseUtils.decode(resJson, OrderInfoResponse.class); } @@ -95,6 +135,20 @@ public WxChannelBaseResponse updateDelivery(DeliveryUpdateParam param) throws Wx 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) { // 暂不支持 @@ -107,6 +161,16 @@ public DeliveryCompanyResponse listDeliveryCompany() throws WxErrorException { return ResponseUtils.decode(resJson, DeliveryCompanyResponse.class); } + @Override + public DeliveryCompanyResponse listDeliveryCompany(Boolean ewaybillOnly) throws WxErrorException { + String reqJson = "{}"; + if (ewaybillOnly != null) { + reqJson = "{\"ewaybill_only\":" + ewaybillOnly + "}"; + } + String resJson = shopService.post(GET_DELIVERY_COMPANY_NEW_URL, reqJson); + return ResponseUtils.decode(resJson, DeliveryCompanyResponse.class); + } + @Override public WxChannelBaseResponse deliveryOrder(String orderId, List deliveryList) throws WxErrorException { @@ -114,4 +178,117 @@ public WxChannelBaseResponse deliveryOrder(String orderId, List de 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); + } + + @Override + public WxChannelBaseResponse addPresentNote(String orderId, String note) throws WxErrorException { + PresentNoteAddParam param = new PresentNoteAddParam(orderId, note); + String resJson = shopService.post(PRESENT_NOTE_ADD_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public PresentSubOrderResponse getPresentSubOrders(String orderId) throws WxErrorException { + OrderIdParam param = new OrderIdParam(orderId); + String resJson = shopService.post(PRESENT_SUB_ORDER_GET_URL, param); + return ResponseUtils.decode(resJson, PresentSubOrderResponse.class); + } + + @Override + public PreShipmentChangeSkuResponse getPreShipmentChangeSku(String orderId) throws WxErrorException { + OrderIdParam param = new OrderIdParam(orderId); + String resJson = shopService.post(PRE_SHIPMENT_CHANGE_SKU_GET_URL, param); + return ResponseUtils.decode(resJson, PreShipmentChangeSkuResponse.class); + } + + @Override + public WxChannelBaseResponse approvePreShipmentChangeSku(String orderId) throws WxErrorException { + OrderIdParam param = new OrderIdParam(orderId); + String resJson = shopService.post(PRE_SHIPMENT_CHANGE_SKU_APPROVE_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse rejectPreShipmentChangeSku(String orderId, String rejectReason) + throws WxErrorException { + PreShipmentChangeSkuRejectParam param = new PreShipmentChangeSkuRejectParam(orderId, rejectReason); + String resJson = shopService.post(PRE_SHIPMENT_CHANGE_SKU_REJECT_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse applyRealNumber(String orderId) throws WxErrorException { + OrderIdParam param = new OrderIdParam(orderId); + String resJson = shopService.post(REAL_NUMBER_APPLY_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public RealNumberViewAuditResponse getRealNumberViewAudit(String orderId) throws WxErrorException { + OrderIdParam param = new OrderIdParam(orderId); + String resJson = shopService.post(REAL_NUMBER_VIEW_AUDIT_GET_URL, param); + return ResponseUtils.decode(resJson, RealNumberViewAuditResponse.class); + } + + @Override + public WxChannelBaseResponse applyVirtualNumberAgain(String orderId) throws WxErrorException { + OrderIdParam param = new OrderIdParam(orderId); + String resJson = shopService.post(VIRTUAL_NUMBER_APPLY_AGAIN_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse delayVirtualNumber(String orderId) throws WxErrorException { + OrderIdParam param = new OrderIdParam(orderId); + String resJson = shopService.post(VIRTUAL_NUMBER_DELAY_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse addPrivatePhone(String phone) throws WxErrorException { + PrivateNumberAddPhoneParam param = new PrivateNumberAddPhoneParam(phone); + String resJson = shopService.post(ADD_PHONE_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse sendPrivatePhoneVerifyCode(String phone) throws WxErrorException { + PrivateNumberSendVerifyCodeParam param = new PrivateNumberSendVerifyCodeParam(phone); + String resJson = shopService.post(SEND_VERIFY_CODE_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public PrivateNumberGetPhoneResponse getPrivatePhone() throws WxErrorException { + String resJson = shopService.post(GET_PHONE_URL, "{}"); + return ResponseUtils.decode(resJson, PrivateNumberGetPhoneResponse.class); + } + + @Override + public WxChannelBaseResponse compensationDelivery(OrderCompensationDeliveryParam param) + throws WxErrorException { + String resJson = shopService.post(DELIVERY_COMPENSATION_URL, param); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java new file mode 100644 index 0000000000..7f1de84d02 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java @@ -0,0 +1,77 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.BEGIN_TIMING_SALE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.CANCEL_TIMING_SALE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.CATEGORY_PRE_CHECK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.EXTERNAL_PRODUCT_MAPPING_NEW_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.EXTERNAL_PRODUCT_MAPPING_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.PRODUCT_BRAND_RECOMMEND_URL; + +import me.chanjar.weixin.channel.api.WxChannelProductAssistantService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.assistant.BeginTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.assistant.CancelTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckParam; +import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewParam; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingParam; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendParam; +import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendResponse; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店商品辅助功能服务实现。 + */ +public class WxChannelProductAssistantServiceImpl implements WxChannelProductAssistantService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelProductAssistantServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public CategoryPreCheckResponse categoryPreCheck(CategoryPreCheckParam param) throws WxErrorException { + return post(CATEGORY_PRE_CHECK_URL, param, CategoryPreCheckResponse.class); + } + + @Override + public ProductBrandRecommendResponse getProductBrandRecommend(ProductBrandRecommendParam param) + throws WxErrorException { + return post(PRODUCT_BRAND_RECOMMEND_URL, param, ProductBrandRecommendResponse.class); + } + + @Override + public ExternalProductMappingResponse externalProductMapping(ExternalProductMappingParam param) + throws WxErrorException { + return post(EXTERNAL_PRODUCT_MAPPING_URL, param, ExternalProductMappingResponse.class); + } + + @Override + public ExternalProductMappingNewResponse externalProductMappingNew(ExternalProductMappingNewParam param) + throws WxErrorException { + return post(EXTERNAL_PRODUCT_MAPPING_NEW_URL, param, ExternalProductMappingNewResponse.class); + } + + @Override + public WxChannelBaseResponse beginTimingSale(BeginTimingSaleParam param) throws WxErrorException { + return post(BEGIN_TIMING_SALE_URL, param, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse cancelTimingSale(CancelTimingSaleParam param) throws WxErrorException { + return post(CANCEL_TIMING_SALE_URL, param, WxChannelBaseResponse.class); + } + + private T post(String url, Object param, Class responseType) + throws WxErrorException { + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(url, reqJson); + return ResponseUtils.decode(resJson, responseType); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java index eb168a09e3..fb00f1874d 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java @@ -1,35 +1,97 @@ 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.GIFT_ACTIVITY_ADD_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_DELETE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_STOP_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_ADD_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_GET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_ON_SALE_SET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_STOCK_UPDATE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_UPDATE_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_PRODUCT_THIRD_PARTY_SOURCE_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_AUDIT_STRATEGY_GET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_AUDIT_STRATEGY_SET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_BEGIN_TIMING_SALE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_CANCEL_TIMING_SALE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_CATEGORY_CLASSIFY_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_CATEGORY_PRE_CHECK_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_EXTERNAL_PRODUCT_MAPPING_NEW_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_EXTERNAL_PRODUCT_MAPPING_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_AUDIT_QUOTA_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_FLOW_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_UPDATE_STOCK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_PRODUCT_BRAND_RECOMMEND_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_QRCODE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_SCHEME_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_TAGLINK_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.Collections; +import java.util.List; import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelGiftService; +import me.chanjar.weixin.channel.api.WxChannelLimitedDiscountService; import me.chanjar.weixin.channel.api.WxChannelProductService; +import me.chanjar.weixin.channel.api.WxChannelProductStockService; 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.SkuStockParam; +import me.chanjar.weixin.channel.bean.product.AddProductThirdPartySourceParam; +import me.chanjar.weixin.channel.bean.product.AddProductThirdPartySourceResponse; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingNewParam; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingNewResponse; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingParam; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingResponse; +import me.chanjar.weixin.channel.bean.product.GiftActivityAddParam; +import me.chanjar.weixin.channel.bean.product.GiftActivityAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftActivityInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductGetResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductListParam; +import me.chanjar.weixin.channel.bean.product.GiftProductListResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditQuotaResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategyResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategySetParam; +import me.chanjar.weixin.channel.bean.product.ProductBrandRecommendParam; +import me.chanjar.weixin.channel.bean.product.ProductBrandRecommendResponse; +import me.chanjar.weixin.channel.bean.product.ProductCategoryClassifyParam; +import me.chanjar.weixin.channel.bean.product.ProductCategoryClassifyResponse; +import me.chanjar.weixin.channel.bean.product.ProductCategoryPreCheckParam; +import me.chanjar.weixin.channel.bean.product.ProductCategoryPreCheckResponse; +import me.chanjar.weixin.channel.bean.product.ProductSchemeParam; +import me.chanjar.weixin.channel.bean.product.ProductSchemeResponse; +import me.chanjar.weixin.channel.bean.product.ProductStockFlowParam; +import me.chanjar.weixin.channel.bean.product.ProductStockFlowResponse; +import me.chanjar.weixin.channel.bean.product.ProductTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.SkuStockBatchParam; +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.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; @@ -43,10 +105,37 @@ public class WxChannelProductServiceImpl implements WxChannelProductService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; + private final WxChannelGiftService giftService; + private final WxChannelLimitedDiscountService limitedDiscountService; + private final WxChannelProductStockService productStockService; - public WxChannelProductServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelProductServiceImpl(BaseWxChannelServiceImpl shopService) { + this(shopService, new WxChannelGiftServiceImpl(shopService), + new WxChannelLimitedDiscountServiceImpl(shopService), new WxChannelProductStockServiceImpl(shopService)); + } + + WxChannelProductServiceImpl(BaseWxChannelServiceImpl shopService, WxChannelGiftService giftService, + WxChannelLimitedDiscountService limitedDiscountService, + WxChannelProductStockService productStockService) { this.shopService = shopService; + this.giftService = giftService; + this.limitedDiscountService = limitedDiscountService; + this.productStockService = productStockService; + } + + @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 @@ -63,13 +152,17 @@ public SpuUpdateResponse updateProduct(SpuInfo info) throws WxErrorException { 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); + return productStockService.updateStock(productId, skuId, diffType, num); } /** @@ -143,38 +236,178 @@ public WxChannelBaseResponse downProduct(String productId) throws WxErrorExcepti @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); + return productStockService.getSkuStock(productId, skuId); + } + + @Override + public SkuStockBatchResponse getSkuStockBatch(List productIds) throws WxErrorException { + return productStockService.getSkuStockBatch(productIds); + } + + @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 ProductSchemeResponse getProductScheme(ProductSchemeParam param) throws WxErrorException { + return postAndDecode(SPU_SCHEME_URL, param, ProductSchemeResponse.class); + } + + @Override + public ProductCategoryClassifyResponse classifyProductCategory(ProductCategoryClassifyParam param) + throws WxErrorException { + return postAndDecode(SPU_CATEGORY_CLASSIFY_URL, param, ProductCategoryClassifyResponse.class); + } + + @Override + public WxChannelBaseResponse beginTimingSale(ProductTimingSaleParam param) throws WxErrorException { + return postAndDecode(SPU_BEGIN_TIMING_SALE_URL, param, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse cancelTimingSale(String productId) throws WxErrorException { + return postAndDecode(SPU_CANCEL_TIMING_SALE_URL, Collections.singletonMap("product_id", productId), + WxChannelBaseResponse.class); + } + + @Override + public ExternalProductMappingResponse externalProductMapping(ExternalProductMappingParam param) + throws WxErrorException { + return postAndDecode(SPU_EXTERNAL_PRODUCT_MAPPING_URL, param, ExternalProductMappingResponse.class); + } + + @Override + public ProductCategoryPreCheckResponse categoryPreCheck(ProductCategoryPreCheckParam param) + throws WxErrorException { + return postAndDecode(SPU_CATEGORY_PRE_CHECK_URL, param, ProductCategoryPreCheckResponse.class); + } + + @Override + public ProductAuditStrategyResponse getProductAuditStrategy() throws WxErrorException { + return postAndDecode(SPU_AUDIT_STRATEGY_GET_URL, "{}", ProductAuditStrategyResponse.class); + } + + @Override + public WxChannelBaseResponse setProductAuditStrategy(ProductAuditStrategySetParam param) throws WxErrorException { + return postAndDecode(SPU_AUDIT_STRATEGY_SET_URL, param, WxChannelBaseResponse.class); + } + + @Override + public ProductAuditQuotaResponse getProductAuditQuota() throws WxErrorException { + return postAndDecode(SPU_GET_AUDIT_QUOTA_URL, "{}", ProductAuditQuotaResponse.class); + } + + @Override + public ExternalProductMappingNewResponse externalProductMappingNew(ExternalProductMappingNewParam param) + throws WxErrorException { + return postAndDecode(SPU_EXTERNAL_PRODUCT_MAPPING_NEW_URL, param, ExternalProductMappingNewResponse.class); + } + + @Override + public ProductBrandRecommendResponse productBrandRecommend(ProductBrandRecommendParam param) + throws WxErrorException { + return postAndDecode(SPU_PRODUCT_BRAND_RECOMMEND_URL, param, ProductBrandRecommendResponse.class); + } + + @Override + public AddProductThirdPartySourceResponse addProductThirdPartySource(AddProductThirdPartySourceParam param) + throws WxErrorException { + return postAndDecode(SPU_ADD_PRODUCT_THIRD_PARTY_SOURCE_URL, param, AddProductThirdPartySourceResponse.class); + } + + @Override + public ProductStockFlowResponse getStockFlow(ProductStockFlowParam param) throws WxErrorException { + return postAndDecode(SPU_GET_STOCK_FLOW_URL, param, ProductStockFlowResponse.class); + } + + private T postAndDecode(String url, Object param, Class responseType) + throws WxErrorException { + String reqJson = param instanceof String ? (String) param : JsonUtils.encode(param); + String resJson = shopService.post(url, reqJson); + return ResponseUtils.decode(resJson, responseType); + } + + @Override + public GiftProductAddResponse addGiftProduct(GiftProductInfo info) throws WxErrorException { + return giftService.addGiftProduct(info); + } + + @Override + public WxChannelBaseResponse updateGiftProduct(GiftProductInfo info) throws WxErrorException { + return giftService.updateGiftProduct(info); + } + + @Override + public WxChannelBaseResponse setProductAsGift(String productId) throws WxErrorException { + return giftService.setProductAsGift(productId); + } + + @Override + public GiftProductGetResponse getGiftProduct(String productId) throws WxErrorException { + return giftService.getGiftProduct(productId); + } + + @Override + public GiftProductListResponse listGiftProduct(GiftProductListParam param) throws WxErrorException { + return giftService.listGiftProduct(param); + } + + @Override + public WxChannelBaseResponse updateGiftStock(String productId, String skuId, Integer diffType, Integer num) + throws WxErrorException { + return giftService.updateGiftStock(productId, skuId, diffType, num); + } + + @Override + public GiftActivityAddResponse addGiftActivity(GiftActivityInfo info) throws WxErrorException { + return giftService.addGiftActivity(info); + } + + @Override + public WxChannelBaseResponse deleteGiftActivity(String activityId) throws WxErrorException { + return giftService.deleteGiftActivity(activityId); + } + + @Override + public WxChannelBaseResponse stopGiftActivity(String activityId) throws WxErrorException { + return giftService.stopGiftActivity(activityId); } @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); + return limitedDiscountService.addLimitTask(param); } @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); + return limitedDiscountService.listLimitTask(pageSize, nextKey, status); } @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); + return limitedDiscountService.stopLimitTask(taskId); } @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); + return limitedDiscountService.deleteLimitTask(taskId); } } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java new file mode 100644 index 0000000000..1b3b371420 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java @@ -0,0 +1,62 @@ +package me.chanjar.weixin.channel.api.impl; + +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_FLOW_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_UPDATE_STOCK_URL; + +import java.util.List; +import me.chanjar.weixin.channel.api.WxChannelProductStockService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +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.stock.StockFlowParam; +import me.chanjar.weixin.channel.bean.product.stock.StockFlowResponse; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店商品库存服务实现。 + */ +public class WxChannelProductStockServiceImpl implements WxChannelProductStockService { + + private final BaseWxChannelServiceImpl shopService; + + public WxChannelProductStockServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @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); + } + + @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 StockFlowResponse getStockFlow(StockFlowParam param) throws WxErrorException { + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(SPU_GET_STOCK_FLOW_URL, reqJson); + return ResponseUtils.decode(resJson, StockFlowResponse.class); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelQicServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelQicServiceImpl.java new file mode 100644 index 0000000000..a23532b6fd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelQicServiceImpl.java @@ -0,0 +1,75 @@ +package me.chanjar.weixin.channel.api.impl; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import me.chanjar.weixin.channel.api.WxChannelQicService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.qic.InspectCodeResponse; +import me.chanjar.weixin.channel.bean.qic.InspectConfigResponse; +import me.chanjar.weixin.channel.bean.qic.RegisterLogisticsRequest; +import me.chanjar.weixin.channel.bean.qic.SubmitConfigResponse; +import me.chanjar.weixin.channel.bean.qic.SubmitInspectRequest; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import org.apache.commons.lang3.StringUtils; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Qic.GET_INSPECT_CONFIG_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Qic.GET_SUBMIT_CONFIG_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Qic.PRINT_INSPECT_CODE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Qic.REGISTER_LOGISTICS_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Qic.SUBMIT_INSPECT_INFO_URL; + +/** + * 视频号小店 质检管理服务实现. + */ +public class WxChannelQicServiceImpl implements WxChannelQicService { + private final BaseWxChannelServiceImpl shopService; + + public WxChannelQicServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public InspectConfigResponse getInspectConfig() throws WxErrorException { + String respJson = shopService.get(GET_INSPECT_CONFIG_URL, null); + return ResponseUtils.decode(respJson, InspectConfigResponse.class); + } + + @Override + public SubmitConfigResponse getSubmitConfig(String orderId) throws WxErrorException { + String queryParam = StringUtils.isBlank(orderId) ? null : "order_id=" + orderId; + String respJson = shopService.get(GET_SUBMIT_CONFIG_URL, queryParam); + return ResponseUtils.decode(respJson, SubmitConfigResponse.class); + } + + @Override + public SubmitConfigResponse getSubmitConfig() throws WxErrorException { + return getSubmitConfig(null); + } + + @Override + public InspectCodeResponse printInspectCode(String orderId) throws WxErrorException { + String respJson = shopService.post(PRINT_INSPECT_CODE_URL, new PrintInspectCodeRequest(orderId)); + return ResponseUtils.decode(respJson, InspectCodeResponse.class); + } + + @Override + public WxChannelBaseResponse submitInspectInfo(SubmitInspectRequest request) throws WxErrorException { + String respJson = shopService.post(SUBMIT_INSPECT_INFO_URL, request); + return ResponseUtils.decode(respJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse registerLogistics(RegisterLogisticsRequest request) throws WxErrorException { + String respJson = shopService.post(REGISTER_LOGISTICS_URL, request); + return ResponseUtils.decode(respJson, WxChannelBaseResponse.class); + } + + @Data + @AllArgsConstructor + private static class PrintInspectCodeRequest { + @JsonProperty("order_id") + private String orderId; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpClientImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpClientImpl.java index bbe8865269..6f380f80fb 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpClientImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceHttpClientImpl.java @@ -1,22 +1,28 @@ package me.chanjar.weixin.channel.api.impl; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.GET_ACCESS_TOKEN_URL; - -import java.io.IOException; import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.bean.token.StableTokenParam; import me.chanjar.weixin.channel.config.WxChannelConfig; -import me.chanjar.weixin.common.util.http.HttpType; +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.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.BasicResponseHandler; +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 */ @@ -26,10 +32,6 @@ public class WxChannelServiceHttpClientImpl 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 index e3e749dca5..6f2c349f3f 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImpl.java @@ -10,4 +10,17 @@ @Slf4j public class WxChannelServiceImpl extends WxChannelServiceHttpClientImpl { + 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 index 96438ae00f..3e27b124c7 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelSharerServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelSharerServiceImpl.java @@ -7,6 +7,7 @@ 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; @@ -21,6 +22,7 @@ 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; /** * 视频号小店 分享员服务实现 @@ -31,16 +33,17 @@ public class WxChannelSharerServiceImpl implements WxChannelSharerService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelSharerServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelSharerServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } @Override public SharerBindResponse bindSharer(String username) throws WxErrorException { - String reqJson = "{\"username\": " + username + "}"; - String resJson = shopService.post(BIND_SHARER_URL, reqJson); + JsonObject jsonObject = GsonHelper.buildJsonObject("username", username); + + String resJson = shopService.post(BIND_SHARER_URL, jsonObject); return ResponseUtils.decode(resJson, SharerBindResponse.class); } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelSupplierServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelSupplierServiceImpl.java new file mode 100644 index 0000000000..451aebc8a0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelSupplierServiceImpl.java @@ -0,0 +1,132 @@ +package me.chanjar.weixin.channel.api.impl; + +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelSupplierService; +import me.chanjar.weixin.channel.bean.base.StreamPageParam; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.supplier.DistributeTypeResponse; +import me.chanjar.weixin.channel.bean.supplier.DropshipAssignRequest; +import me.chanjar.weixin.channel.bean.supplier.DropshipDetailResponse; +import me.chanjar.weixin.channel.bean.supplier.DropshipListRequest; +import me.chanjar.weixin.channel.bean.supplier.DropshipListResponse; +import me.chanjar.weixin.channel.bean.supplier.DropshipResponse; +import me.chanjar.weixin.channel.bean.supplier.DropshipSearchRequest; +import me.chanjar.weixin.channel.bean.supplier.ProductDistributeRequest; +import me.chanjar.weixin.channel.bean.supplier.ProductListResponse; +import me.chanjar.weixin.channel.bean.supplier.SupplierInfoResponse; +import me.chanjar.weixin.channel.bean.supplier.SupplierListResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonHelper; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Supplier.ASSIGN_DROPSHIP_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Supplier.CANCEL_DROPSHIP_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Supplier.GET_DISTRIBUTE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Supplier.GET_DROPSHIP_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Supplier.GET_DROPSHIP_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Supplier.GET_PRODUCT_DEFAULT_DISTRIBUTE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Supplier.GET_PRODUCT_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Supplier.GET_SUPPLIER_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Supplier.SEARCH_DROPSHIP_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Supplier.SET_ALL_DISTRIBUTION_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Supplier.SET_MANUALLY_DISTRIBUTE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Supplier.SET_PRODUCT_DISTRIBUTE_URL; + +/** + * 视频号小店代发管理服务。 + * + * @author GitHub Copilot + */ +@Slf4j +public class WxChannelSupplierServiceImpl implements WxChannelSupplierService { + + private final BaseWxChannelServiceImpl shopService; + + public WxChannelSupplierServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public SupplierListResponse getSupplierList() throws WxErrorException { + return getSupplierList(null, null); + } + + @Override + public SupplierListResponse getSupplierList(Integer pageSize, String nextKey) throws WxErrorException { + StreamPageParam param = new StreamPageParam(pageSize, nextKey); + String respJson = shopService.post(GET_SUPPLIER_LIST_URL, param); + return ResponseUtils.decode(respJson, SupplierListResponse.class); + } + + @Override + public DistributeTypeResponse getDistribute() throws WxErrorException { + String respJson = shopService.post(GET_DISTRIBUTE_URL, "{}"); + return ResponseUtils.decode(respJson, DistributeTypeResponse.class); + } + + @Override + public WxChannelBaseResponse setManuallyDistribute() throws WxErrorException { + String respJson = shopService.post(SET_MANUALLY_DISTRIBUTE_URL, "{}"); + return ResponseUtils.decode(respJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse setAllDistribute(String supplierId) throws WxErrorException { + JsonObject req = GsonHelper.buildJsonObject("supplier_id", supplierId); + String respJson = shopService.post(SET_ALL_DISTRIBUTION_URL, req); + return ResponseUtils.decode(respJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse setProductDistribute(ProductDistributeRequest req) throws WxErrorException { + String respJson = shopService.post(SET_PRODUCT_DISTRIBUTE_URL, req); + return ResponseUtils.decode(respJson, WxChannelBaseResponse.class); + } + + @Override + public SupplierInfoResponse getProductDefaultDistribute(String productId) throws WxErrorException { + JsonObject req = GsonHelper.buildJsonObject("product_id", productId); + String respJson = shopService.post(GET_PRODUCT_DEFAULT_DISTRIBUTE_URL, req); + return ResponseUtils.decode(respJson, SupplierInfoResponse.class); + } + + @Override + public ProductListResponse getProductList(String supplierId) throws WxErrorException { + JsonObject req = GsonHelper.buildJsonObject("supplier_id", supplierId); + String respJson = shopService.post(GET_PRODUCT_LIST_URL, req); + return ResponseUtils.decode(respJson, ProductListResponse.class); + } + + @Override + public DropshipResponse assignOrder(DropshipAssignRequest req) throws WxErrorException { + String respJson = shopService.post(ASSIGN_DROPSHIP_URL, req); + return ResponseUtils.decode(respJson, DropshipResponse.class); + } + + @Override + public WxChannelBaseResponse cancelDropship(String orderId) throws WxErrorException { + JsonObject req = GsonHelper.buildJsonObject("order_id", orderId); + String respJson = shopService.post(CANCEL_DROPSHIP_URL, req); + return ResponseUtils.decode(respJson, WxChannelBaseResponse.class); + } + + @Override + public DropshipDetailResponse getDropship(String orderId) throws WxErrorException { + JsonObject req = GsonHelper.buildJsonObject("order_id", orderId); + String respJson = shopService.post(GET_DROPSHIP_URL, req); + return ResponseUtils.decode(respJson, DropshipDetailResponse.class); + } + + @Override + public DropshipListResponse listDropship(DropshipListRequest req) throws WxErrorException { + String respJson = shopService.post(GET_DROPSHIP_LIST_URL, req); + return ResponseUtils.decode(respJson, DropshipListResponse.class); + } + + @Override + public DropshipListResponse searchDropship(DropshipSearchRequest req) throws WxErrorException { + String respJson = shopService.post(SEARCH_DROPSHIP_URL, req); + return ResponseUtils.decode(respJson, DropshipListResponse.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 index b9609e5c6b..6805f26a4f 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelWarehouseServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelWarehouseServiceImpl.java @@ -40,9 +40,9 @@ public class WxChannelWarehouseServiceImpl implements WxChannelWarehouseService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxChannelWarehouseServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxChannelWarehouseServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxFinderLiveServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxFinderLiveServiceImpl.java 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 index 29620874e2..fc8d2fbadc 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueProductServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueProductServiceImpl.java @@ -32,9 +32,9 @@ public class WxLeagueProductServiceImpl implements WxLeagueProductService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxLeagueProductServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxLeagueProductServiceImpl(BaseWxChannelServiceImplshopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeaguePromoterServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeaguePromoterServiceImpl.java index a6bfddfbef..c81df29533 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeaguePromoterServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeaguePromoterServiceImpl.java @@ -24,9 +24,9 @@ public class WxLeaguePromoterServiceImpl implements WxLeaguePromoterService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxLeaguePromoterServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxLeaguePromoterServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } @@ -58,6 +58,34 @@ public PromoterInfoResponse getPromoterInfo(String finderId) throws WxErrorExcep return ResponseUtils.decode(resJson, PromoterInfoResponse.class); } + @Override + public WxChannelBaseResponse addPromoterV2(String promoterId) throws WxErrorException { + String reqJson = "{\"promoter_id\":\"" + promoterId + "\"}"; + String resJson = shopService.post(ADD_PROMOTER_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse updatePromoterV2(String promoterId, int type) throws WxErrorException { + String reqJson = "{\"promoter_id\":\"" + promoterId + "\",\"type\":" + type + "}"; + String resJson = shopService.post(EDIT_PROMOTER_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse deletePromoterV2(String promoterId) throws WxErrorException { + String reqJson = "{\"promoter_id\":\"" + promoterId + "\"}"; + String resJson = shopService.post(DELETE_PROMOTER_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public PromoterInfoResponse getPromoterInfoV2(String promoterId) throws WxErrorException { + String reqJson = "{\"promoter_id\":\"" + promoterId + "\"}"; + String resJson = shopService.post(GET_PROMOTER_URL, reqJson); + return ResponseUtils.decode(resJson, PromoterInfoResponse.class); + } + @Override public PromoterListResponse listPromoter(Integer pageIndex, Integer pageSize, Integer status) throws WxErrorException { diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueSupplierServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueSupplierServiceImpl.java index d69296bd0f..2b280a2f6d 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueSupplierServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueSupplierServiceImpl.java @@ -38,9 +38,9 @@ public class WxLeagueSupplierServiceImpl implements WxLeagueSupplierService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxLeagueSupplierServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxLeagueSupplierServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueWindowServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueWindowServiceImpl.java index a59fc6efa5..a0c21ab4ef 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueWindowServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxLeagueWindowServiceImpl.java @@ -29,9 +29,9 @@ public class WxLeagueWindowServiceImpl implements WxLeagueWindowService { /** 微信商店服务 */ - private final BaseWxChannelServiceImpl shopService; + private final BaseWxChannelServiceImpl shopService; - public WxLeagueWindowServiceImpl(BaseWxChannelServiceImpl shopService) { + public WxLeagueWindowServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxStoreCooperationServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxStoreCooperationServiceImpl.java 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/api/impl/WxTalentServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxTalentServiceImpl.java new file mode 100644 index 0000000000..6bb623e9a4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxTalentServiceImpl.java @@ -0,0 +1,59 @@ +package me.chanjar.weixin.channel.api.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxTalentService; +import me.chanjar.weixin.channel.bean.talent.TalentOrderDetailParam; +import me.chanjar.weixin.channel.bean.talent.TalentOrderDetailResponse; +import me.chanjar.weixin.channel.bean.talent.TalentOrderListParam; +import me.chanjar.weixin.channel.bean.talent.TalentOrderListResponse; +import me.chanjar.weixin.channel.bean.talent.TalentWindowProductDetailParam; +import me.chanjar.weixin.channel.bean.talent.TalentWindowProductDetailResponse; +import me.chanjar.weixin.channel.bean.talent.TalentWindowProductListParam; +import me.chanjar.weixin.channel.bean.talent.TalentWindowProductListResponse; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Talent.GET_ORDER_DETAIL_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Talent.GET_ORDER_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Talent.GET_WINDOW_PRODUCT_DETAIL_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Talent.GET_WINDOW_PRODUCT_LIST_URL; + +/** + * 微信小店-带货助手服务实现 + * + * @author GitHub Copilot + */ +@RequiredArgsConstructor +@Slf4j +public class WxTalentServiceImpl implements WxTalentService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + @Override + public TalentOrderListResponse getOrderList(TalentOrderListParam param) throws WxErrorException { + String resJson = shopService.post(GET_ORDER_LIST_URL, param); + return ResponseUtils.decode(resJson, TalentOrderListResponse.class); + } + + @Override + public TalentOrderDetailResponse getOrderDetail(TalentOrderDetailParam param) throws WxErrorException { + String resJson = shopService.post(GET_ORDER_DETAIL_URL, param); + return ResponseUtils.decode(resJson, TalentOrderDetailResponse.class); + } + + @Override + public TalentWindowProductListResponse getWindowProductList(TalentWindowProductListParam param) + throws WxErrorException { + String resJson = shopService.post(GET_WINDOW_PRODUCT_LIST_URL, param); + return ResponseUtils.decode(resJson, TalentWindowProductListResponse.class); + } + + @Override + public TalentWindowProductDetailResponse getWindowProductDetail(TalentWindowProductDetailParam param) + throws WxErrorException { + String resJson = shopService.post(GET_WINDOW_PRODUCT_DETAIL_URL, param); + return ResponseUtils.decode(resJson, TalentWindowProductDetailResponse.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 index a831de6655..71f7c5b966 100644 --- 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 @@ -12,11 +12,13 @@ * 地址 请求参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.address.AddressAddParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(Include.NON_NULL) +@Deprecated public class AddressAddParam implements Serializable { private static final long serialVersionUID = 6778585213498438738L; 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 index c7c885f0ab..9f212daa22 100644 --- 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 @@ -9,9 +9,11 @@ * 地址编码 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.address.AddressCode}。 */ @Data @NoArgsConstructor +@Deprecated public class AddressCode implements Serializable { private static final long serialVersionUID = -6782328785056142627L; 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 index 09ede50c38..df9559fee6 100644 --- 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 @@ -11,10 +11,12 @@ * 地址编码 响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.address.AddressCodeResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class AddressCodeResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -8994407971295563982L; 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 index 88f4945e20..f3dde94a28 100644 --- 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 @@ -11,10 +11,12 @@ * 用户地址 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.address.AddressDetail}。 */ @Data @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class AddressDetail implements Serializable { private static final long serialVersionUID = -7839578838482198641L; 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 index d1eb7e0b46..1306fbf8ed 100644 --- 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 @@ -12,11 +12,13 @@ * 地址id 请求参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.address.AddressIdParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(Include.NON_NULL) +@Deprecated public class AddressIdParam implements Serializable { private static final long serialVersionUID = -7001183932180608746L; 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 index f6505efa15..df580bc2e4 100644 --- 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 @@ -11,11 +11,13 @@ * 地址id 响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.address.AddressIdResponse}。 */ @Data @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class AddressIdResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -9218327846685744008L; 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 index 957d0162a8..f77f562e34 100644 --- 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 @@ -10,10 +10,12 @@ * 地址id 响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.address.AddressInfoResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class AddressInfoResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 8203853673226715673L; 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 index c62cf39fb8..559d249250 100644 --- 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 @@ -11,11 +11,13 @@ * 用户地址 列表 请求参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.address.AddressListParam}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JsonInclude(Include.NON_NULL) +@Deprecated public class AddressListParam extends OffsetParam { private static final long serialVersionUID = -4434287264623932176L; 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 index b8846f9aa3..b18d399710 100644 --- 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 @@ -11,10 +11,12 @@ * 地址列表 响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.address.AddressListResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class AddressListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -3997164605170764105L; 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 index 81dd169399..12345f2a52 100644 --- 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 @@ -10,10 +10,12 @@ * 线下配送地址类型 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.address.OfflineAddressType}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class OfflineAddressType implements Serializable { private static final long serialVersionUID = 636850757572901377L; 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..1f48cbe348 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleAcceptExchangeReshipParam.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 售后单换货发货信息 + * + * @author Chu + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleAcceptExchangeReshipParam}。 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +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 index ebc63a2190..13b0bc2f84 100644 --- 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 @@ -9,9 +9,11 @@ * 售后单同意信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleAcceptParam}。 */ @Data @JsonInclude(Include.NON_NULL) +@Deprecated public class AfterSaleAcceptParam extends AfterSaleIdParam { private static final long serialVersionUID = -4352801757159074950L; @@ -19,6 +21,10 @@ public class AfterSaleAcceptParam extends AfterSaleIdParam { @JsonProperty("address_id") private String addressId; + /** 针对退货退款同意售后的阶段: 1. 同意退货退款,并通知用户退货; 2. 确认收到货并退款给用户。 如果不填则将根据当前的售后单状态自动选择相应操作。对于仅退款的情况,由于只存在一种同意的场景,无需填写此字段。*/ + @JsonProperty("accept_type") + private Integer acceptType; + public AfterSaleAcceptParam() { } @@ -26,4 +32,10 @@ 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/AfterSaleCreateResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleCreateResponse.java new file mode 100644 index 0000000000..268bf9814f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleCreateResponse.java @@ -0,0 +1,19 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleCreateResponse}。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class AfterSaleCreateResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 2680676438284658410L; + + @JsonProperty("after_sale_order_id") + private String afterSaleOrderId; +} 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 index aa1e7b400f..9c5780b20c 100644 --- 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 @@ -10,9 +10,11 @@ * 售后详情 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleDetail}。 */ @Data @NoArgsConstructor +@Deprecated public class AfterSaleDetail implements Serializable { private static final long serialVersionUID = -8130659179770831047L; 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..2fbd7b482d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleExchangeDeliveryInfo.java @@ -0,0 +1,37 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleExchangeDeliveryInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..d279d78f56 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleExchangeProductInfo.java @@ -0,0 +1,44 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleExchangeProductInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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/AfterSaleGenAfterSaleOrderParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleGenAfterSaleOrderParam.java new file mode 100644 index 0000000000..ec2ec1c703 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleGenAfterSaleOrderParam.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleGenAfterSaleOrderParam}。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class AfterSaleGenAfterSaleOrderParam extends AfterSaleRefundPriceDiffParam { + private static final long serialVersionUID = -6873909673739068936L; + + @JsonProperty("count") + private Integer count; + + @JsonProperty("type") + private String type; + + @JsonProperty("address_id") + private String addressId; + + @JsonProperty("exchange_sku_info") + private ExchangeSkuInfo exchangeSkuInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleHandleFastExchangeReceiptParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleHandleFastExchangeReceiptParam.java new file mode 100644 index 0000000000..05af300f2f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleHandleFastExchangeReceiptParam.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleHandleFastExchangeReceiptParam}。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class AfterSaleHandleFastExchangeReceiptParam extends AfterSaleIdParam { + private static final long serialVersionUID = 5430106715116197677L; + + @JsonProperty("act") + private Integer act; + + @JsonProperty("reject_reason") + private String rejectReason; + + @JsonProperty("reject_reason_type") + private Integer rejectReasonType; + + @JsonProperty("merchant_text") + private String merchantText; + + @JsonProperty("reject_confirm_exchange") + private List rejectConfirmExchange; +} 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 index 1e16a72395..8eec8569ec 100644 --- 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 @@ -12,11 +12,13 @@ * 售后单id信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleIdParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(Include.NON_NULL) +@Deprecated public class AfterSaleIdParam implements Serializable { private static final long serialVersionUID = 4974332291476116540L; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleInfo.java index b0d668b30e..d7309b0be4 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleInfo.java @@ -9,9 +9,11 @@ * 售后单信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class AfterSaleInfo implements Serializable { private static final long serialVersionUID = 6595670817781635247L; @@ -82,4 +84,20 @@ public class AfterSaleInfo implements Serializable { /** 纠纷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 index adedf72f03..c9f31b77d9 100644 --- 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 @@ -10,10 +10,12 @@ * 售后单 响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleInfoResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class AfterSaleInfoResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -752661975153491902L; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleListParam.java index 78cc394085..86ae7825f3 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleListParam.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleListParam.java @@ -12,11 +12,13 @@ * 售后单列表 请求参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleListParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(Include.NON_NULL) +@Deprecated public class AfterSaleListParam implements Serializable { private static final long serialVersionUID = -103549981452112069L; @@ -28,6 +30,14 @@ public class AfterSaleListParam implements Serializable { @JsonProperty("end_create_time") private Long endCreateTime; + /** 售后单更新起始时间 */ + @JsonProperty("begin_update_time") + private Long beginUpdateTime; + + /** 售后单更新结束时间,end_update_time减去begin_update_time不得大于24小时 */ + @JsonProperty("end_update_time") + private Long endUpdateTime; + /** 翻页参数,从第二页开始传,来源于上一页的返回值 */ @JsonProperty("next_key") private String nextKey; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleListResponse.java index 3ad67cffcf..1fbc115f2c 100644 --- 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 @@ -11,10 +11,12 @@ * 售后单列表 响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleListResponse}。 */ @Data @NoArgsConstructor -@EqualsAndHashCode +@EqualsAndHashCode(callSuper = true) +@Deprecated public class AfterSaleListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 5033313416948732123L; @@ -23,6 +25,7 @@ public class AfterSaleListResponse extends WxChannelBaseResponse { private List ids; /** 翻页参数 */ + @JsonProperty("next_key") private String nextKey; /** 是否还有数据 */ diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleMerchantUpdateParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleMerchantUpdateParam.java new file mode 100644 index 0000000000..808091cbe5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleMerchantUpdateParam.java @@ -0,0 +1,59 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleMerchantUpdateParam}。 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +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 index ffcaf320ca..a74283350c 100644 --- 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 @@ -9,9 +9,11 @@ * 售后相关商品信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleProductInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class AfterSaleProductInfo implements Serializable { private static final long serialVersionUID = 4205179093262757775L; 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..9efb0fc02f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleReason.java @@ -0,0 +1,35 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleReason}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..79d9a6714f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleReasonResponse.java @@ -0,0 +1,30 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleReasonResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode +@Deprecated +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/AfterSaleRefundPriceDiffParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRefundPriceDiffParam.java new file mode 100644 index 0000000000..7949fe98f3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRefundPriceDiffParam.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleRefundPriceDiffParam}。 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class AfterSaleRefundPriceDiffParam implements Serializable { + private static final long serialVersionUID = 3875058376021518123L; + + @JsonProperty("request_id") + private String requestId; + + @JsonProperty("order_id") + private String orderId; + + @JsonProperty("product_id") + private String productId; + + @JsonProperty("sku_id") + private String skuId; + + @JsonProperty("amount") + private Integer amount; + + @JsonProperty("reason") + private String reason; + + @JsonProperty("desc") + private String desc; +} 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..6097ea6fb5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectExchangeReshipParam.java @@ -0,0 +1,43 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleRejectExchangeReshipParam}。 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +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 index 080665ac00..3aefc8b4e8 100644 --- 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 @@ -5,20 +5,38 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; +import java.util.List; + /** * 售后单拒绝信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleRejectParam}。 */ @Data @JsonInclude(Include.NON_NULL) +@Deprecated public class AfterSaleRejectParam extends AfterSaleIdParam { private static final long serialVersionUID = -7507483859864253314L; - /** 拒绝原因 */ + /** + * 拒绝原因 + */ @JsonProperty("reject_reason") private String rejectReason; + /** + * 拒绝原因枚举值 + */ + @JsonProperty("reject_reason_type") + private Integer rejectReasonType; + + /** + * 拒绝凭证图片列表,可使用图片上传接口获取media_id + */ + @JsonProperty("reject_certificates") + private List rejectCertificates; + public AfterSaleRejectParam() { } @@ -26,4 +44,18 @@ 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; + } + + public AfterSaleRejectParam(String afterSaleOrderId, String rejectReason, Integer rejectReasonType, + List rejectCertificates) { + super(afterSaleOrderId); + this.rejectReason = rejectReason; + this.rejectReasonType = rejectReasonType; + this.rejectCertificates = rejectCertificates; + } } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReason.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReason.java new file mode 100644 index 0000000000..4f4b70b4e7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReason.java @@ -0,0 +1,46 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleRejectReason}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..06c1256c5f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleRejectReasonResponse.java @@ -0,0 +1,31 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleRejectReasonResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode +@Deprecated +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 index 47e815c8dd..d2a0502c27 100644 --- 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 @@ -9,8 +9,10 @@ * 退货信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleReturnParam}。 */ @Data +@Deprecated public class AfterSaleReturnParam implements Serializable { private static final long serialVersionUID = -1101993925465293521L; 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..ab37c18ebc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleVirtualNumberInfo.java @@ -0,0 +1,28 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleVirtualNumberInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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/AfterSaleVirtualTelNumResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleVirtualTelNumResponse.java new file mode 100644 index 0000000000..8a40fdec43 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/AfterSaleVirtualTelNumResponse.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.AfterSaleVirtualTelNumResponse}。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class AfterSaleVirtualTelNumResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -2715343569103426942L; + + @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/ExchangeSkuInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/ExchangeSkuInfo.java new file mode 100644 index 0000000000..929e3560b6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/ExchangeSkuInfo.java @@ -0,0 +1,18 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.ExchangeSkuInfo}。 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class ExchangeSkuInfo implements Serializable { + private static final long serialVersionUID = 1L; + @JsonProperty("new_sku_id") + private String newSkuId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeMerchantModifyParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeMerchantModifyParam.java new file mode 100644 index 0000000000..41be302aed --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeMerchantModifyParam.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.GuaranteeMerchantModifyParam}。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class GuaranteeMerchantModifyParam extends GuaranteeOrderIdParam { + private static final long serialVersionUID = 9193536167701367687L; + + @JsonProperty("bad_level") + private Integer badLevel; + + @JsonProperty("merchant_remark") + private String merchantRemark; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeMerchantProofParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeMerchantProofParam.java new file mode 100644 index 0000000000..3501bff266 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeMerchantProofParam.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.GuaranteeMerchantProofParam}。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class GuaranteeMerchantProofParam extends GuaranteeOrderIdParam { + private static final long serialVersionUID = -2365495841866160967L; + + @JsonProperty("content") + private String content; + + @JsonProperty("pic_list") + private java.util.List picList; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeModifyRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeModifyRequest.java new file mode 100644 index 0000000000..c6d7786f8c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeModifyRequest.java @@ -0,0 +1,36 @@ +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; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 商家协商保障单请求参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.GuaranteeModifyRequest}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(Include.NON_NULL) +@Deprecated +public class GuaranteeModifyRequest extends GuaranteeOrderIdParam { + + private static final long serialVersionUID = 4268864541609439068L; + + /** 商品破损程度。 */ + @JsonProperty("bad_level") + private Integer badLevel; + + /** 商家协商备注。 */ + @JsonProperty("merchant_remark") + private String merchantRemark; + + public GuaranteeModifyRequest(String guaranteeOrderId, Integer badLevel, String merchantRemark) { + super(guaranteeOrderId); + this.badLevel = badLevel; + this.merchantRemark = merchantRemark; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderIdParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderIdParam.java new file mode 100644 index 0000000000..2fb99a097d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderIdParam.java @@ -0,0 +1,27 @@ +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; + +/** + * 保障单号参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.GuaranteeOrderIdParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +public class GuaranteeOrderIdParam implements Serializable { + + private static final long serialVersionUID = -6638498743123537413L; + + /** 保障单号。 */ + @JsonProperty("guarantee_order_id") + private String guaranteeOrderId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderInfoResponse.java new file mode 100644 index 0000000000..c7c2301aec --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderInfoResponse.java @@ -0,0 +1,61 @@ +package me.chanjar.weixin.channel.bean.after; + +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; + +/** + * 保障单详情响应。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.GuaranteeOrderInfoResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class GuaranteeOrderInfoResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 7354122991247317485L; + + /** 保障单详情。 */ + @JsonProperty("guarantee_order") + private GuaranteeOrder guaranteeOrder; + + /** + * 保障单详情。 + */ + @Data + @NoArgsConstructor + public static class GuaranteeOrder implements Serializable { + + private static final long serialVersionUID = -2398976447575813507L; + + /** 保障单号。 */ + @JsonProperty("guarantee_order_id") + private String guaranteeOrderId; + + /** 保障单状态。 */ + @JsonProperty("status") + private String status; + + /** 商品信息。 */ + @JsonProperty("product_info") + private ProductInfo productInfo; + } + + /** + * 详情商品信息。 + */ + @Data + @NoArgsConstructor + public static class ProductInfo implements Serializable { + + private static final long serialVersionUID = -2455740986246085934L; + + /** 商品 SPU ID。 */ + @JsonProperty("product_id") + private String productId; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderListParam.java new file mode 100644 index 0000000000..a5c64c629a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderListParam.java @@ -0,0 +1,46 @@ +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 java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 保障单列表请求参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.GuaranteeOrderListParam}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +public class GuaranteeOrderListParam implements Serializable { + + private static final long serialVersionUID = 1622570776364341988L; + + @JsonProperty("guarantee_order_id_list") + private List guaranteeOrderIdList; + + @JsonProperty("order_id_list") + private List orderIdList; + + @JsonProperty("type") + private Integer type; + + @JsonProperty("begin_time") + private Long beginTime; + + @JsonProperty("end_time") + private Long endTime; + + @JsonProperty("status_list") + private String statusList; + + @JsonProperty("offset") + private Integer offset; + + @JsonProperty("limit") + private Integer limit; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderListResponse.java new file mode 100644 index 0000000000..defb95b909 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderListResponse.java @@ -0,0 +1,66 @@ +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.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 保障单列表响应。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.GuaranteeOrderListResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class GuaranteeOrderListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 9105476087203713187L; + + /** 保障单总数。 */ + @JsonProperty("total_num") + private Integer totalNum; + + /** 保障单列表。 */ + @JsonProperty("guarantee_order_list") + private List guaranteeOrderList; + + /** + * 保障单列表项。 + */ + @Data + @NoArgsConstructor + public static class GuaranteeOrder implements Serializable { + + private static final long serialVersionUID = 7151952524213202281L; + + /** 保障单号。 */ + @JsonProperty("guarantee_order_id") + private String guaranteeOrderId; + + /** 保障单状态。 */ + @JsonProperty("status") + private String status; + + /** 商品信息列表。 */ + @JsonProperty("product_info") + private List productInfo; + } + + /** + * 列表商品信息。 + */ + @Data + @NoArgsConstructor + public static class ProductInfo implements Serializable { + + private static final long serialVersionUID = -2565763879505631638L; + + /** 商品 SPU ID。 */ + @JsonProperty("product_id") + private String productId; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderResponse.java new file mode 100644 index 0000000000..d3909cf10e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeOrderResponse.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.after; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.GuaranteeOrderResponse}。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class GuaranteeOrderResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 3977781489692530604L; + + @JsonProperty("guarantee_order") + private JsonNode guaranteeOrder; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeProofRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeProofRequest.java new file mode 100644 index 0000000000..8a6d29b885 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeProofRequest.java @@ -0,0 +1,37 @@ +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.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 商家举证保障单请求参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.GuaranteeProofRequest}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(Include.NON_NULL) +@Deprecated +public class GuaranteeProofRequest extends GuaranteeOrderIdParam { + + private static final long serialVersionUID = 6599721896742974275L; + + /** 举证内容。 */ + @JsonProperty("content") + private String content; + + /** 举证图片 media_id 列表。 */ + @JsonProperty("pic_list") + private List picList; + + public GuaranteeProofRequest(String guaranteeOrderId, String content, List picList) { + super(guaranteeOrderId); + this.content = content; + this.picList = picList; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeRefuseRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeRefuseRequest.java new file mode 100644 index 0000000000..0e67312cca --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/GuaranteeRefuseRequest.java @@ -0,0 +1,37 @@ +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.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 商家拒绝保障单请求参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.GuaranteeRefuseRequest}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(Include.NON_NULL) +@Deprecated +public class GuaranteeRefuseRequest extends GuaranteeOrderIdParam { + + private static final long serialVersionUID = -6905594717805091393L; + + /** 拒绝原因。 */ + @JsonProperty("reason") + private String reason; + + /** 拒绝凭证图片 media_id 列表。 */ + @JsonProperty("pic_list") + private List picList; + + public GuaranteeRefuseRequest(String guaranteeOrderId, String reason, List picList) { + super(guaranteeOrderId); + this.reason = reason; + this.picList = picList; + } +} 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 index 805c3a3f6e..1ce46042fe 100644 --- 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 @@ -10,9 +10,11 @@ * 商家上传的信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.MerchantUploadInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class MerchantUploadInfo implements Serializable { private static final long serialVersionUID = 373513419356603563L; 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 index c81ae042d4..e3cd1dccc9 100644 --- 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 @@ -12,11 +12,13 @@ * 退款凭证信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.RefundEvidenceParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class RefundEvidenceParam implements Serializable { private static final long serialVersionUID = 2117305897849528009L; 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 index 9837b72b28..d87875d7e9 100644 --- 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 @@ -9,13 +9,19 @@ * 退款信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.RefundInfo}。 */ @Data @NoArgsConstructor +@Deprecated 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 index 83b7039a77..96f639e607 100644 --- 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 @@ -9,9 +9,11 @@ * 退款结果 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.RefundResp}。 */ @Data @NoArgsConstructor +@Deprecated public class RefundResp implements Serializable { private static final long serialVersionUID = 6549707043779644156L; 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 index 08238d5484..3f3e0a1066 100644 --- 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 @@ -9,9 +9,11 @@ * 用户退货信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.ReturnInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class ReturnInfo implements Serializable { private static final long serialVersionUID = 1643844664701376892L; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/SyncWorkOrderParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/SyncWorkOrderParam.java new file mode 100644 index 0000000000..4408ec16c3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/after/SyncWorkOrderParam.java @@ -0,0 +1,83 @@ +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.Data; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.after.SyncWorkOrderParam}。 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class SyncWorkOrderParam implements Serializable { + private static final long serialVersionUID = -7336088606071452113L; + + @JsonProperty("complaint_id") + private String complaintId; + + @JsonProperty("work_order_info") + private WorkOrderInfo workOrderInfo; + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class WorkOrderInfo implements Serializable { + private static final long serialVersionUID = 8573016851280130766L; + + @JsonProperty("version") + private Integer version; + + @JsonProperty("items") + private List items; + + @JsonProperty("work_order_id") + private String workOrderId; + } + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class WorkOrderItem implements Serializable { + private static final long serialVersionUID = 6925580701152256736L; + + @JsonProperty("status") + private Integer status; + + @JsonProperty("desc") + private String desc; + + @JsonProperty("update_time") + private Long updateTime; + + @JsonProperty("result_type") + private Integer resultType; + + @JsonProperty("refund_amount") + private Integer refundAmount; + + @JsonProperty("media_list") + private List mediaList; + } + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class WorkOrderMedia implements Serializable { + private static final long serialVersionUID = 2258990333977395631L; + + @JsonProperty("type") + private Integer type; + + @JsonProperty("picture") + private WorkOrderPicture picture; + } + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class WorkOrderPicture implements Serializable { + private static final long serialVersionUID = -3339842364541603289L; + + @JsonProperty("tmp_media_id") + private String tmpMediaId; + } +} 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 index 547207c82b..7963c8ea7b 100644 --- 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 @@ -10,10 +10,12 @@ * 审核提交结果响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.audit.AuditApplyResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class AuditApplyResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -3950614749162384497L; 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 index 3ef07387d1..af2e31497b 100644 --- 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 @@ -10,10 +10,12 @@ * 审核结果响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.audit.AuditResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class AuditResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 9218713381520774914L; 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 index 89aaa8a267..2d0edf0a13 100644 --- 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 @@ -9,9 +9,11 @@ * 审核结果 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.audit.AuditResult}。 */ @Data @NoArgsConstructor +@Deprecated public class AuditResult implements Serializable { private static final long serialVersionUID = 1846416634865665240L; 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 index 72a84bc922..516debd10a 100644 --- 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 @@ -1,5 +1,6 @@ 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; @@ -11,10 +12,13 @@ * 类目审核信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.audit.CategoryAuditInfo}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class CategoryAuditInfo implements Serializable { private static final long serialVersionUID = -8792967130645424788L; @@ -31,7 +35,47 @@ public class CategoryAuditInfo implements Serializable { @JsonProperty("level3") private Long level3; - /** 资质材料,图片url,图片类型,最多不超过10张 */ + /** 新类目树类目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 index a311bf0d2f..a3bf73cc09 100644 --- 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 @@ -10,10 +10,12 @@ * 类目审核信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.audit.CategoryAuditRequest}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class CategoryAuditRequest implements Serializable { private static final long serialVersionUID = -1151634735247657643L; 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..058c224004 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CategoryBrand.java @@ -0,0 +1,25 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.audit.CategoryBrand}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..c77ad5fcea --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/audit/CatsV2.java @@ -0,0 +1,24 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.audit.CatsV2}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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 index 7693f23ed3..814bcf6755 100644 --- 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 @@ -9,9 +9,11 @@ * 商品审核信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.audit.ProductAuditInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class ProductAuditInfo implements Serializable { private static final long serialVersionUID = -5264206679057480206L; 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 index 3c713840a4..df752977b2 100644 --- 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 @@ -10,10 +10,12 @@ * 地址信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.base.AddressInfo}。 */ @Data @NoArgsConstructor @Accessors(chain = true) +@Deprecated public class AddressInfo implements Serializable { private static final long serialVersionUID = 6928300709804576100L; 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 index ca6ce7a750..c50f528a00 100644 --- 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 @@ -10,10 +10,12 @@ * 属性 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.base.AttrInfo}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class AttrInfo implements Serializable { private static final long serialVersionUID = -790859309885311785L; 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 index ebfad1bf21..10dc30d7b3 100644 --- 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 @@ -12,11 +12,13 @@ * 偏移参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.base.OffsetParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(Include.NON_NULL) +@Deprecated public class OffsetParam implements Serializable { private static final long serialVersionUID = -1268796871980541662L; 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 index d76e48d3b6..dde4e6bc0c 100644 --- 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 @@ -10,10 +10,12 @@ * 分页参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.base.PageParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class PageParam implements Serializable { private static final long serialVersionUID = -2606033044242617845L; 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 index 6f3fb76d71..0a1f02f83a 100644 --- 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 @@ -10,10 +10,12 @@ * 流式分页参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.base.StreamPageParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class StreamPageParam implements Serializable { private static final long serialVersionUID = -4098060161712929196L; 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 index f681794835..39ae602166 100644 --- 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 @@ -9,9 +9,11 @@ * 时间范围 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.base.TimeRange}。 */ @Data @NoArgsConstructor +@Deprecated public class TimeRange implements Serializable { private static final long serialVersionUID = -8149679871789511479L; 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 index b20d7f4b33..36732574ed 100644 --- 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 @@ -8,7 +8,9 @@ * 视频号小店 基础响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.base.WxStoreBaseResponse}。 */ +@Deprecated public class WxChannelBaseResponse implements Serializable { private static final long serialVersionUID = 3141420881984171781L; 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 index 714740f843..63e366943d 100644 --- 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 @@ -9,9 +9,11 @@ * 基础品牌信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.brand.BasicBrand}。 */ @Data @NoArgsConstructor +@Deprecated public class BasicBrand implements Serializable { private static final long serialVersionUID = -1991771439710177859L; 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 index 92f4f41acc..37ad02c4af 100644 --- 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 @@ -10,11 +10,13 @@ * 品牌信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.brand.Brand}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class Brand extends BasicBrand { private static final long serialVersionUID = 4648597514861057019L; 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 index 48575f27cd..ebdbb27e9e 100644 --- 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 @@ -10,9 +10,11 @@ * 商标申请信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.brand.BrandApplicationDetail}。 */ @Data @NoArgsConstructor +@Deprecated public class BrandApplicationDetail implements Serializable { private static final long serialVersionUID = 2145344855482129473L; 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 index 16e7f3ae82..7530bd0c78 100644 --- 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 @@ -11,10 +11,12 @@ * 品牌申请列表响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.brand.BrandApplyListResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class BrandApplyListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 243021267020609148L; 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 index 6b4826fcd4..4f90819308 100644 --- 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 @@ -10,9 +10,11 @@ * 商标授权信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.brand.BrandGrantDetail}。 */ @Data @NoArgsConstructor +@Deprecated public class BrandGrantDetail implements Serializable { private static final long serialVersionUID = 3537812707384823606L; 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 index 799002369d..f7dd7c485d 100644 --- 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 @@ -10,10 +10,12 @@ * 品牌信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.brand.BrandInfo}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class BrandInfo extends Brand { private static final long serialVersionUID = 5464505958132626159L; 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 index 20536b5a07..573923e209 100644 --- 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 @@ -10,10 +10,12 @@ * 品牌响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.brand.BrandInfoResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class BrandInfoResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 2105745692451683517L; 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 index c6cff6f317..4b48368e04 100644 --- 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 @@ -11,10 +11,12 @@ * 品牌列表响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.brand.BrandListResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class BrandListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -5335449078706304920L; 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 index 05f8d89b42..872dba2409 100644 --- 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 @@ -11,11 +11,13 @@ * 品牌参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.brand.BrandParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class BrandParam implements Serializable { private static final long serialVersionUID = -4894709391464428613L; 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 index 28b417f38c..744b521fd8 100644 --- 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 @@ -10,9 +10,11 @@ * 品牌注册信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.brand.BrandRegisterDetail}。 */ @Data @NoArgsConstructor +@Deprecated public class BrandRegisterDetail implements Serializable { private static final long serialVersionUID = 1169957179510362405L; 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 index e73ed4f54e..e472d03879 100644 --- 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 @@ -10,10 +10,12 @@ * 品牌搜索参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.brand.BrandSearchParam}。 */ @Data @EqualsAndHashCode(callSuper = true) @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class BrandSearchParam extends StreamPageParam { private static final long serialVersionUID = 5961201403338269712L; 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 index 3db7c74cec..ebcd0b5cf1 100644 --- 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 @@ -11,10 +11,12 @@ * 分类响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.category.AccountCategoryResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class AccountCategoryResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 3486089711447908477L; 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 index c9e973c8b8..ad163fad7a 100644 --- 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 @@ -10,9 +10,11 @@ * 分类资质响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.category.CategoryAndQualificationList}。 */ @Data @NoArgsConstructor +@Deprecated public class CategoryAndQualificationList implements Serializable { private static final long serialVersionUID = 4245906598437404655L; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryDetailResult.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryDetailResult.java index 8819e94312..44bc6bbc27 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryDetailResult.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/CategoryDetailResult.java @@ -9,9 +9,11 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.category.CategoryDetailResult}。 */ @Data @NoArgsConstructor +@Deprecated public class CategoryDetailResult extends WxChannelBaseResponse { private static final long serialVersionUID = 4657778764371047619L; @@ -22,6 +24,9 @@ public class CategoryDetailResult extends WxChannelBaseResponse { @JsonProperty("attr") private Attr attr; + @JsonProperty("product_qua_list") + private List productQuaList; + @Data @NoArgsConstructor @@ -74,6 +79,38 @@ public static class Attr implements Serializable { /** 佣金信息 */ @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 @@ -93,11 +130,27 @@ public static class ProductAttr implements Serializable { @JsonProperty("name") private String name; - /** 类目必填项类型,string为自定义,select_one为多选一 */ + /** 属性类型,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; @@ -105,7 +158,13 @@ public static class ProductAttr implements Serializable { @JsonProperty("is_required") private Boolean required; + /** 输入提示,请填写提示语 */ + @JsonProperty("hint") + private String hint; + /** 允许添加选项,当type为select_one/select_many时,标识是否允许添加新选项(value中不存在的选项) */ + @JsonProperty("append_allowed") + private Boolean appendAllowed; } @Data @@ -123,8 +182,78 @@ public static class FeeInfo implements Serializable { /** 佣金激励类型,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 index f384eaae45..75b39381e9 100644 --- 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 @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serializable; +import java.util.List; import lombok.Data; import lombok.NoArgsConstructor; @@ -9,9 +10,11 @@ * 分类资质信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.category.CategoryQualification}。 */ @Data @NoArgsConstructor +@Deprecated public class CategoryQualification implements Serializable { private static final long serialVersionUID = 6495550078851408381L; @@ -24,8 +27,26 @@ public class CategoryQualification implements Serializable { @JsonProperty("qua") private QualificationInfo info; - /** 商品资质信息 */ + /** 商品资质信息,将废弃,使用product_qua_list代替 */ @JsonProperty("product_qua") + @Deprecated private QualificationInfo productInfo; + /** + * 品牌资质信息。 + * + * @deprecated 微信接口仍返回该字段,暂未提供替代字段。 + */ + @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 index 984a3ad79b..1ac37c98a2 100644 --- 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 @@ -11,10 +11,12 @@ * 分类资质响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.category.CategoryQualificationResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class CategoryQualificationResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -7869091908852685830L; @@ -22,4 +24,7 @@ public class CategoryQualificationResponse extends WxChannelBaseResponse { @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 index 82b16c0188..e51e96d019 100644 --- 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 @@ -9,9 +9,11 @@ * 审核通过的分类和资质信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.category.PassCategoryInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class PassCategoryInfo implements Serializable { private static final long serialVersionUID = 1152077957498898216L; 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 index af6f484254..fe8effa7af 100644 --- 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 @@ -3,6 +3,7 @@ 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; @@ -10,9 +11,12 @@ * 审核通过的分类和资质信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.category.PassCategoryResponse}。 */ @Data @NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated public class PassCategoryResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -3674591447273025743L; 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 index 197ac46528..fee3fddfbe 100644 --- 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 @@ -9,9 +9,11 @@ * 资质信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.category.QualificationInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class QualificationInfo implements Serializable { /** 资质ID */ @@ -29,4 +31,8 @@ public class QualificationInfo implements Serializable { /** 该类目申请的时候是否一定要提交资质 */ @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..3579fc11c3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryItem.java @@ -0,0 +1,43 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.category.RelationCategoryItem}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..ef73e59515 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryRequest.java @@ -0,0 +1,30 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.category.RelationCategoryRequest}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..64e4f67186 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/category/RelationCategoryResponse.java @@ -0,0 +1,27 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.category.RelationCategoryResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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 index b36edfa9e2..5154efec3b 100644 --- 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 @@ -9,9 +9,11 @@ * 商品类目 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.category.ShopCategory}。 */ @Data @NoArgsConstructor +@Deprecated public class ShopCategory implements Serializable { /** 类目ID */ @@ -29,4 +31,8 @@ public class ShopCategory implements Serializable { /** 层级 */ @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 index 2af64ad1c3..ca31e49dfe 100644 --- 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 @@ -11,10 +11,12 @@ * 分类响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.category.ShopCategoryResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class ShopCategoryResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 3871098948660947422L; @@ -23,5 +25,7 @@ public class ShopCategoryResponse extends WxChannelBaseResponse { @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 index 4570fdc615..f34da72f92 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintHistory.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintHistory.java @@ -10,9 +10,11 @@ * 纠纷历史 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.complaint.ComplaintHistory}。 */ @Data @NoArgsConstructor +@Deprecated public class ComplaintHistory implements Serializable { private static final long serialVersionUID = -4706637116597650133L; @@ -26,7 +28,7 @@ public class ComplaintHistory implements Serializable { /** 用户联系电话 */ @JsonProperty("phone_number") - private Integer phoneNumber; + private String phoneNumber; /** 相关文本内容 */ @JsonProperty("content") diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintOrderResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/complaint/ComplaintOrderResponse.java index a0a8ec1e18..5fc472bb7e 100644 --- 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 @@ -10,9 +10,11 @@ * 纠纷单响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.complaint.ComplaintOrderResponse}。 */ @Data @NoArgsConstructor +@Deprecated public class ComplaintOrderResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 1968530826349555367L; 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 index 0090348efe..925bbf3db6 100644 --- 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 @@ -12,11 +12,13 @@ * 纠纷单留言 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.complaint.ComplaintParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class ComplaintParam implements Serializable { private static final long serialVersionUID = 6146118590005718327L; 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..8d825003e2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationData.java @@ -0,0 +1,49 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.cooperation.CooperationData}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..b2f093e779 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationListResponse.java @@ -0,0 +1,27 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.cooperation.CooperationListResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..6f4d7cc9df --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationQrCode.java @@ -0,0 +1,25 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.cooperation.CooperationQrCode}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..b1f92059f5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationQrCodeResponse.java @@ -0,0 +1,26 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.cooperation.CooperationQrCodeResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..d93b96f90b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationSharerParam.java @@ -0,0 +1,32 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.cooperation.CooperationSharerParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +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..84585cf45f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationStatus.java @@ -0,0 +1,25 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.cooperation.CooperationStatus}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..3dd6496eb8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/cooperation/CooperationStatusResponse.java @@ -0,0 +1,26 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.cooperation.CooperationStatusResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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 index 73c09def1e..10e6861add 100644 --- 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 @@ -9,9 +9,11 @@ * 自动生效信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.AutoValidInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class AutoValidInfo implements Serializable { private static final long serialVersionUID = 1702505613539861103L; 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 index 34f76716f9..7b1c0b9623 100644 --- 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 @@ -9,10 +9,12 @@ * 优惠券信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.CouponDetailInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class CouponDetailInfo implements Serializable { private static final long serialVersionUID = 5994815232349181577L; 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 index b787016a09..ef7b0322c4 100644 --- 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 @@ -10,10 +10,12 @@ * 优惠券id * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.CouponIdInfo}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class CouponIdInfo implements Serializable { private static final long serialVersionUID = 6284609705855608275L; 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 index 7556fa6f11..cb3e87b2d1 100644 --- 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 @@ -9,10 +9,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.CouponIdResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class CouponIdResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -3263189706802013651L; 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 index cd247f9d71..760ec78c41 100644 --- 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 @@ -6,9 +6,11 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.CouponInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class CouponInfo extends CouponIdInfo { private static final long serialVersionUID = -5862063828870424262L; 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 index 801843025e..3157df797a 100644 --- 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 @@ -8,10 +8,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.CouponInfoResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class CouponInfoResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 5261320058699488529L; 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 index 6c7fc03a6e..bc7047e472 100644 --- 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 @@ -12,10 +12,12 @@ * 获取优惠券ID列表接口的请求参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.CouponListParam}。 */ @Data @NoArgsConstructor @JsonInclude(Include.NON_NULL) +@Deprecated public class CouponListParam implements Serializable { private static final long serialVersionUID = 7123047113279657365L; 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 index 66d6f63eef..18c1f3ea59 100644 --- 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 @@ -9,10 +9,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.CouponListResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class CouponListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -5330296358041282751L; 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 index fa89b0a1e4..9c47e54530 100644 --- 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 @@ -9,10 +9,12 @@ * 优惠券参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.CouponParam}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class CouponParam extends CouponIdInfo { private static final long serialVersionUID = -3663331372622943337L; 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 index 405ad52400..64d58cbf08 100644 --- 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 @@ -7,10 +7,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.CouponStatusParam}。 */ @Data @EqualsAndHashCode(callSuper = true) @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class CouponStatusParam extends CouponIdInfo { private static final long serialVersionUID = -7108348049925634704L; 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 index e249455526..4cac13356e 100644 --- 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 @@ -10,9 +10,11 @@ * 折扣条件 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.DiscountCondition}。 */ @Data @NoArgsConstructor +@Deprecated public class DiscountCondition implements Serializable { private static final long serialVersionUID = 3250293381093835082L; 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 index 7988e47ce6..5c7efc1e1d 100644 --- 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 @@ -9,9 +9,11 @@ * 优惠信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.DiscountInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class DiscountInfo implements Serializable { private static final long serialVersionUID = 3660070880545144112L; 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 index 69cf3dc073..1c36bebec2 100644 --- 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 @@ -9,9 +9,11 @@ * 额外信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.ExtInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class ExtInfo implements Serializable { private static final long serialVersionUID = 9053035437087423233L; 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 index 75d48e6d3e..7cdb5c1d8a 100644 --- 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 @@ -9,9 +9,11 @@ * 推广信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.PromoteInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class PromoteInfo implements Serializable { private static final long serialVersionUID = -3030639750899957382L; 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 index 9a602ac390..2f4c861843 100644 --- 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 @@ -9,9 +9,11 @@ * 领取信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.ReceiveInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class ReceiveInfo implements Serializable { private static final long serialVersionUID = 755956808504040633L; 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 index 07aaf4a1ec..652440b82a 100644 --- 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 @@ -9,9 +9,11 @@ * 库存信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.StockInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class StockInfo implements Serializable { private static final long serialVersionUID = -6078383881065929862L; 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 index 06436a9e73..804ed8d93e 100644 --- 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 @@ -9,10 +9,12 @@ * 用户优惠券 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.UserCoupon}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class UserCoupon extends UserCouponIdInfo { private static final long serialVersionUID = -4777537717885622888L; 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 index d68d881c98..8d9ef4a1d5 100644 --- 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 @@ -8,9 +8,11 @@ * 用户优惠券id * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.UserCouponIdInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class UserCouponIdInfo extends CouponIdInfo { private static final long serialVersionUID = -8285585134793264542L; 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 index aa2eb15421..9340f0fc90 100644 --- 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 @@ -6,8 +6,10 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.UserCouponIdParam}。 */ @Data +@Deprecated public class UserCouponIdParam implements Serializable { private static final long serialVersionUID = 3967276158727848348L; 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 index f14f5d7f6e..67963a72b8 100644 --- 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 @@ -8,11 +8,13 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.UserCouponListParam}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class UserCouponListParam extends CouponListParam { private static final long serialVersionUID = -1056132009327357435L; 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 index 2c3582e678..5019c4f5d2 100644 --- 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 @@ -9,10 +9,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.UserCouponListResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class UserCouponListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 5201633937239352879L; 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 index aeb9d89afb..ab5fbc7b89 100644 --- 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 @@ -8,10 +8,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.UserCouponResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class UserCouponResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 1434098386857953234L; 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 index 18962361ec..8b0ddc09a1 100644 --- 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 @@ -9,9 +9,11 @@ * 用户优惠券附加信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.UserExtInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class UserExtInfo implements Serializable { private static final long serialVersionUID = 8304922825230343409L; 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 index 10df794324..b33a90667a 100644 --- 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 @@ -9,9 +9,11 @@ * 优惠券有效信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.coupon.ValidInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class ValidInfo implements Serializable { private static final long serialVersionUID = -4550516248380285635L; 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 index 349d70cbb1..c9c3ca1986 100644 --- 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 @@ -9,9 +9,11 @@ * 快递公司信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.delivery.DeliveryCompanyInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class DeliveryCompanyInfo implements Serializable { private static final long serialVersionUID = 4225666604513570564L; 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 index d74a9439ea..55745dd98c 100644 --- 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 @@ -10,9 +10,11 @@ * 快递公司列表响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.delivery.DeliveryCompanyResponse}。 */ @Data @NoArgsConstructor +@Deprecated public class DeliveryCompanyResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -7695903997951385166L; 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 index 23ab8dad2c..fe7442a07c 100644 --- 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 @@ -10,9 +10,11 @@ * 物流信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.delivery.DeliveryInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class DeliveryInfo implements Serializable { private static final long serialVersionUID = -6205626967305385248L; 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 index f486032bc4..aa0125d9d1 100644 --- 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 @@ -13,11 +13,13 @@ * 订单发货信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.delivery.DeliverySendParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(Include.NON_NULL) +@Deprecated public class DeliverySendParam implements Serializable { private static final long serialVersionUID = 4555821308266899135L; 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 index 2a7c7dd3c6..23e4aef3e3 100644 --- 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 @@ -10,9 +10,11 @@ * 包裹中商品信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.delivery.FreightProductInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class FreightProductInfo implements Serializable { private static final long serialVersionUID = -3751269707150372172L; 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..bc0c4a959b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/FreshInspectParam.java @@ -0,0 +1,33 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.delivery.FreshInspectParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +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..9acee665e5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/delivery/PackageAuditInfo.java @@ -0,0 +1,34 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.delivery.PackageAuditInfo}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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/ewaybill/AbstractEwaybillRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/AbstractEwaybillRequest.java new file mode 100644 index 0000000000..d863c7c895 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/AbstractEwaybillRequest.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 电子面单通用请求参数容器。 + * + *

字段按官方文档动态透传,避免非官方字段定义。

+ * + * @author GitHub Copilot + */ +@Data +@NoArgsConstructor +public abstract class AbstractEwaybillRequest implements Serializable { + + private static final long serialVersionUID = 4213577159985597237L; + + @JsonIgnore + private Map params = new LinkedHashMap<>(); + + @JsonAnySetter + public void addParam(String key, Object value) { + params.put(key, value); + } + + @JsonAnyGetter + public Map anyParams() { + return params; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/AbstractEwaybillResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/AbstractEwaybillResponse.java new file mode 100644 index 0000000000..f62aab4ada --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/AbstractEwaybillResponse.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.util.LinkedHashMap; +import java.util.Map; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 电子面单通用响应参数容器。 + * + *

未显式声明字段将保存到 extra 字段,便于兼容官方接口变更。

+ * + * @author GitHub Copilot + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public abstract class AbstractEwaybillResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -2460196179063989718L; + + @JsonIgnore + private Map extra = new LinkedHashMap<>(); + + @JsonAnySetter + public void addExtra(String key, Object value) { + extra.put(key, value); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/AccountInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/AccountInfoResponse.java new file mode 100644 index 0000000000..e7b590d596 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/AccountInfoResponse.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +/** + * 电子面单网点/账号信息响应。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.AccountInfoResponse}。 + */ +@Deprecated +public class AccountInfoResponse extends AbstractEwaybillResponse { + private static final long serialVersionUID = 5682783958522805959L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/AddSubOrderRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/AddSubOrderRequest.java new file mode 100644 index 0000000000..d8be0cb0b3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/AddSubOrderRequest.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +/** + * 电子面单子件追加请求。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.AddSubOrderRequest}。 + */ +@Deprecated +public class AddSubOrderRequest extends AbstractEwaybillRequest { + private static final long serialVersionUID = 4250200603210217269L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/BatchPrintOrderRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/BatchPrintOrderRequest.java new file mode 100644 index 0000000000..2cca5cf1d5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/BatchPrintOrderRequest.java @@ -0,0 +1,16 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.BatchPrintOrderRequest}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class BatchPrintOrderRequest { + @JsonProperty("req_list") private List reqList; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/CreateOrderRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/CreateOrderRequest.java new file mode 100644 index 0000000000..9fef758eae --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/CreateOrderRequest.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +/** + * 电子面单取号请求。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.CreateOrderRequest}。 + */ +@Deprecated +public class CreateOrderRequest extends AbstractEwaybillRequest { + private static final long serialVersionUID = 2521225918646916853L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/CreateOrderResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/CreateOrderResponse.java new file mode 100644 index 0000000000..4260e42696 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/CreateOrderResponse.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +/** + * 电子面单取号响应。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.CreateOrderResponse}。 + */ +@Deprecated +public class CreateOrderResponse extends AbstractEwaybillResponse { + private static final long serialVersionUID = 9115454170108519187L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/DeliveryListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/DeliveryListResponse.java new file mode 100644 index 0000000000..736ac03943 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/DeliveryListResponse.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +/** + * 开通快递公司列表响应。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.DeliveryListResponse}。 + */ +@Deprecated +public class DeliveryListResponse extends AbstractEwaybillResponse { + private static final long serialVersionUID = 494164885034906535L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/EwaybillOrderIdParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/EwaybillOrderIdParam.java new file mode 100644 index 0000000000..b252e9b2b5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/EwaybillOrderIdParam.java @@ -0,0 +1,18 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.EwaybillOrderIdParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +public class EwaybillOrderIdParam { + @JsonProperty("ewaybill_order_id") + private String ewaybillOrderId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/OrderDetailResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/OrderDetailResponse.java new file mode 100644 index 0000000000..111fe8bf98 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/OrderDetailResponse.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +/** + * 面单详情响应。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.OrderDetailResponse}。 + */ +@Deprecated +public class OrderDetailResponse extends AbstractEwaybillResponse { + private static final long serialVersionUID = -2406734055149395916L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PreCreateRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PreCreateRequest.java new file mode 100644 index 0000000000..b129d29be8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PreCreateRequest.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +/** + * 电子面单预取号请求。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.PreCreateRequest}。 + */ +@Deprecated +public class PreCreateRequest extends AbstractEwaybillRequest { + private static final long serialVersionUID = 3761501770378571724L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PreCreateResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PreCreateResponse.java new file mode 100644 index 0000000000..d51d3d9085 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PreCreateResponse.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +/** + * 电子面单预取号响应。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.PreCreateResponse}。 + */ +@Deprecated +public class PreCreateResponse extends AbstractEwaybillResponse { + private static final long serialVersionUID = -6302826807350860584L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PrintContentParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PrintContentParam.java new file mode 100644 index 0000000000..3b94bb1197 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PrintContentParam.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +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; + +/** 获取电子面单打印报文请求参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.PrintContentParam}。 +*/ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +public class PrintContentParam implements Serializable { + private static final long serialVersionUID = 6898522842175667816L; + + @JsonProperty("ewaybill_order_id") + private String ewaybillOrderId; + + @JsonProperty("template_id") + private String templateId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PrintContentResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PrintContentResponse.java new file mode 100644 index 0000000000..12314a1d84 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PrintContentResponse.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +/** + * 打印报文响应。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.PrintContentResponse}。 + */ +@Deprecated +public class PrintContentResponse extends AbstractEwaybillResponse { + private static final long serialVersionUID = 1097526332493027364L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PrintOrderRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PrintOrderRequest.java new file mode 100644 index 0000000000..e069867cc0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/PrintOrderRequest.java @@ -0,0 +1,17 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.PrintOrderRequest}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class PrintOrderRequest extends EwaybillOrderIdParam { + @JsonProperty("delivery_id") private String deliveryId; + @JsonProperty("waybill_id") private String waybillId; + @JsonProperty("re_print") private Boolean rePrint; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateCodeParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateCodeParam.java new file mode 100644 index 0000000000..242f483f13 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateCodeParam.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** 面单标准模板编码请求参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.TemplateCodeParam}。 +*/ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +public class TemplateCodeParam implements Serializable { + private static final long serialVersionUID = 4473438799300843172L; + + @JsonProperty("template_code") + private String templateCode; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateConfigResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateConfigResponse.java new file mode 100644 index 0000000000..363f80c790 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateConfigResponse.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +/** + * 面单标准模板响应。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.TemplateConfigResponse}。 + */ +@Deprecated +public class TemplateConfigResponse extends AbstractEwaybillResponse { + private static final long serialVersionUID = 6779567498624326386L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateCreateRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateCreateRequest.java new file mode 100644 index 0000000000..49faf9d7f0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateCreateRequest.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +/** + * 新增面单模板请求。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.TemplateCreateRequest}。 + */ +@Deprecated +public class TemplateCreateRequest extends AbstractEwaybillRequest { + private static final long serialVersionUID = 2974771986022948202L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateIdParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateIdParam.java new file mode 100644 index 0000000000..d45e1490cd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateIdParam.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 模板ID请求参数。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.TemplateIdParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +public class TemplateIdParam implements Serializable { + private static final long serialVersionUID = -2397006631686547550L; + + @JsonProperty("template_id") + private String templateId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateIdResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateIdResponse.java new file mode 100644 index 0000000000..a4c70f15d9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateIdResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 面单模板ID响应。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.TemplateIdResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class TemplateIdResponse extends AbstractEwaybillResponse { + private static final long serialVersionUID = -6756111662032438585L; + + @JsonProperty("template_id") + private String templateId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateInfoResponse.java new file mode 100644 index 0000000000..c7a344fab3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateInfoResponse.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +/** + * 面单模板信息响应。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.TemplateInfoResponse}。 + */ +@Deprecated +public class TemplateInfoResponse extends AbstractEwaybillResponse { + private static final long serialVersionUID = 5718279884380636289L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateUpdateRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateUpdateRequest.java new file mode 100644 index 0000000000..29836b5df1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/TemplateUpdateRequest.java @@ -0,0 +1,12 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +/** + * 更新面单模板请求。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.TemplateUpdateRequest}。 + */ +@Deprecated +public class TemplateUpdateRequest extends AbstractEwaybillRequest { + private static final long serialVersionUID = -6201137374059216895L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/WaybillIdParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/WaybillIdParam.java new file mode 100644 index 0000000000..47485945b7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/WaybillIdParam.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 运单ID请求参数。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.WaybillIdParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +public class WaybillIdParam implements Serializable { + private static final long serialVersionUID = -7601452772833268240L; + + @JsonProperty("waybill_id") + private String waybillId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/WaybillIdsParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/WaybillIdsParam.java new file mode 100644 index 0000000000..a17e3a17ed --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/ewaybill/WaybillIdsParam.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 批量运单ID请求参数。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.ewaybill.WaybillIdsParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +public class WaybillIdsParam implements Serializable { + private static final long serialVersionUID = -9030594599179993010L; + + @JsonProperty("waybill_ids") + private List waybillIds; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/favorite/FavoriteCountResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/favorite/FavoriteCountResponse.java new file mode 100644 index 0000000000..f7fb7920b7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/favorite/FavoriteCountResponse.java @@ -0,0 +1,40 @@ +package me.chanjar.weixin.channel.bean.favorite; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 店铺收藏人数 响应 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.favorite.FavoriteCountResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class FavoriteCountResponse extends WxChannelBaseResponse { + + /** 店铺首页收藏用户数 */ + @JsonProperty("favor_uv_acc_shop_homepage") + private Long favorUvAccShopHomepage; + + /** 订单详情页收藏用户数 */ + @JsonProperty("favor_uv_acc_order_detail") + private Long favorUvAccOrderDetail; + + /** 商品详情页收藏用户数 */ + @JsonProperty("favor_uv_acc_product_detail") + private Long favorUvAccProductDetail; + + /** 其他场景收藏用户数 */ + @JsonProperty("favor_uv_acc_other_scene") + private Long favorUvAccOtherScene; + + /** 所有收藏用户数 */ + @JsonProperty("favor_uv_acc_all") + private Long favorUvAccAll; +} 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 index 4d8c7ec4a5..9a59c4e6ba 100644 --- 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 @@ -11,9 +11,11 @@ * 地址列表 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.freight.AddressInfoList}。 */ @Data @NoArgsConstructor +@Deprecated public class AddressInfoList implements Serializable { private static final long serialVersionUID = 5923805297331862706L; 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 index fd9aee451d..ca0d768582 100644 --- 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 @@ -12,9 +12,11 @@ * 计费规则列表 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.freight.AllConditionFreeDetail}。 */ @Data @NoArgsConstructor +@Deprecated public class AllConditionFreeDetail implements Serializable { private static final long serialVersionUID = -1649520737632417036L; 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 index 2c5523ebe4..acaae8aa45 100644 --- 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 @@ -10,8 +10,10 @@ * 具体计费方法,默认运费,指定地区运费等 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.freight.AllFreightCalcMethod}。 */ @Data +@Deprecated public class AllFreightCalcMethod implements Serializable { private static final long serialVersionUID = 6330919525271991949L; 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 index 68cb3b146e..60f36f4a51 100644 --- 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 @@ -9,10 +9,12 @@ * 计费规则 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.freight.ConditionFreeDetail}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class ConditionFreeDetail extends AddressInfoList { private static final long serialVersionUID = 9204578767029379142L; @@ -20,11 +22,11 @@ public class ConditionFreeDetail extends AddressInfoList { @JsonProperty("min_piece") private Integer minPiece; - /** 最低重量 */ + /** 最低重量,单位千克,订单商品总质量小于一千克,算作一千克 */ @JsonProperty("min_weight") private Double minWeight; - /** 最低金额 */ + /** 最低金额,单位(分) */ @JsonProperty("min_amount") private Integer minAmount; 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 index aab949bc44..bae909f189 100644 --- 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 @@ -9,10 +9,12 @@ * 运费计算方法 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.freight.FreightCalcMethod}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class FreightCalcMethod extends AddressInfoList { private static final long serialVersionUID = -8857987538121721376L; 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 index e28f90ad41..4431b06da9 100644 --- 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 @@ -10,9 +10,11 @@ * 运费模板 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.freight.FreightTemplate}。 */ @Data @NoArgsConstructor +@Deprecated public class FreightTemplate implements Serializable { private static final long serialVersionUID = -7876281924385999053L; 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 index 1c480fc227..ef03dadce3 100644 --- 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 @@ -8,10 +8,12 @@ * 不发货区域 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.freight.NotSendArea}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated 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 index 9c400533bf..06729c0c58 100644 --- 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 @@ -12,11 +12,13 @@ * 运费模板 请求参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.freight.TemplateAddParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(Include.NON_NULL) +@Deprecated public class TemplateAddParam implements Serializable { private static final long serialVersionUID = 2602919369418149309L; 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 index e895d066cb..dc4afa6a24 100644 --- 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 @@ -10,10 +10,12 @@ * 运费模板 列表 响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.freight.TemplateIdResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class TemplateIdResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 5179651364165620640L; 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 index f37e3dc2d1..068fc0e1b1 100644 --- 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 @@ -10,10 +10,12 @@ * 运费模板 响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.freight.TemplateInfoResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class TemplateInfoResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -8381510839783330617L; 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 index 628d907eb1..eb9b38733b 100644 --- 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 @@ -10,10 +10,12 @@ * 运费模板 列表 请求参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.freight.TemplateListParam}。 */ @Data @JsonInclude(Include.NON_NULL) @EqualsAndHashCode(callSuper = true) +@Deprecated public class TemplateListParam extends OffsetParam { private static final long serialVersionUID = -6716154891499581562L; 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 index a6fcd7d3e3..6c80c9e29c 100644 --- 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 @@ -11,10 +11,12 @@ * 运费模板 列表 响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.freight.TemplateListResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class TemplateListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 5375602442595264719L; 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 index f6248f96ba..f18cac098e 100644 --- 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 @@ -10,10 +10,12 @@ * 账户信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.AccountInfo}。 */ @Data @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class AccountInfo implements Serializable { private static final long serialVersionUID = -2107134853480093451L; 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 index ec6010bd07..22588b58c4 100644 --- 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 @@ -11,11 +11,13 @@ * 账户信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.AccountInfoParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class AccountInfoParam implements Serializable { private static final long serialVersionUID = 1689204583402779134L; 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 index b54a34a2e7..7d109368d7 100644 --- 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 @@ -9,9 +9,11 @@ * 账户信息响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.AccountInfoResponse}。 */ @Data @NoArgsConstructor +@Deprecated public class AccountInfoResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -8316068503468969533L; 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 index def7e86675..a0036f42be 100644 --- 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 @@ -9,9 +9,11 @@ * 账户余额信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.BalanceInfoResponse}。 */ @Data @NoArgsConstructor +@Deprecated public class BalanceInfoResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 4480496860612566921L; 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 index 9306b4516a..754f8d6e83 100644 --- 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 @@ -10,9 +10,11 @@ * 流水列表响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.FlowListResponse}。 */ @Data @NoArgsConstructor +@Deprecated public class FlowListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 8017827444308973489L; 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 index 4edecbb3b1..f63c2f4501 100644 --- 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 @@ -9,9 +9,11 @@ * 流水关联信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.FlowRelatedInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class FlowRelatedInfo implements Serializable { private static final long serialVersionUID = 3757839018198212504L; 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 index 9b01e820fa..1a86cda290 100644 --- 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 @@ -10,9 +10,11 @@ * 资金流水 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.FundsFlow}。 */ @Data @NoArgsConstructor +@Deprecated public class FundsFlow implements Serializable { private static final long serialVersionUID = -2785498655066305510L; 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 index 7db351263f..fc97729287 100644 --- 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 @@ -10,10 +10,12 @@ * 资金流水响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.FundsFlowResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class FundsFlowResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -1130785908352355914L; 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 index b5312e3a2a..3c74b768c5 100644 --- 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 @@ -9,9 +9,11 @@ * 资金流水参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.FundsListParam}。 */ @Data @NoArgsConstructor +@Deprecated public class FundsListParam implements Serializable { private static final long serialVersionUID = 2998955690332382229L; 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 index a1e726fb51..98fb74ad8e 100644 --- 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 @@ -10,10 +10,12 @@ * 提现详情响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.WithdrawDetailResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class WithdrawDetailResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 1473346677401168323L; 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 index a44b68567d..3fed419347 100644 --- 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 @@ -10,10 +10,12 @@ * 提现列表参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.WithdrawListParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class WithdrawListParam implements Serializable { private static final long serialVersionUID = -672422656564313999L; 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 index b1dabc2a4b..2013bf7bc4 100644 --- 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 @@ -10,9 +10,11 @@ * 提现列表响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.WithdrawListResponse}。 */ @Data @NoArgsConstructor +@Deprecated public class WithdrawListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -7950467108750325235L; 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 index 65b8cdd12c..2245ca67b1 100644 --- 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 @@ -10,10 +10,12 @@ * 提现提交参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.WithdrawSubmitParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class WithdrawSubmitParam implements Serializable { private static final long serialVersionUID = 5801338663530567830L; 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 index 0002b158d2..c4dab15d71 100644 --- 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 @@ -10,10 +10,12 @@ * 提现提交响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.WithdrawSubmitResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class WithdrawSubmitResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -8269579250564427758L; 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 index 04a69a8e87..d4b443de98 100644 --- 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 @@ -9,9 +9,11 @@ * 银行城市信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.bank.BankCityInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class BankCityInfo implements Serializable { private static final long serialVersionUID = 374087891799491196L; 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 index 5cb148c79b..cc86f566f7 100644 --- 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 @@ -11,10 +11,12 @@ * 银行城市信息响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.bank.BankCityResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class BankCityResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -6212360101083304631L; 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 index 1bb58badb4..13aae59d4b 100644 --- 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 @@ -9,9 +9,11 @@ * 银行信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.bank.BankInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class BankInfo implements Serializable { private static final long serialVersionUID = -4837989875996346711L; 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 index 499d9fcbb5..8b35b1982f 100644 --- 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 @@ -11,10 +11,12 @@ * 银行信息响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.bank.BankInfoResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class BankInfoResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 8583893898929290526L; 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 index 9517859c42..6c9a1c36c4 100644 --- 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 @@ -11,10 +11,12 @@ * 银行信息响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.bank.BankListResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class BankListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 7912035853286944260L; 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 index 955a25e8ad..22969119a1 100644 --- 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 @@ -9,9 +9,11 @@ * 银行省份信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.bank.BankProvinceInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class BankProvinceInfo implements Serializable { private static final long serialVersionUID = -3409931656361300144L; 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 index f509d24304..68bbf0f3c6 100644 --- 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 @@ -10,9 +10,11 @@ * 银行省份信息响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.bank.BankProvinceResponse}。 */ @Data @NoArgsConstructor +@Deprecated public class BankProvinceResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -6187805847136359892L; 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 index abc9c1ec77..b368241f06 100644 --- 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 @@ -11,11 +11,13 @@ * 银行查询参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.bank.BankSearchParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class BankSearchParam implements Serializable { private static final long serialVersionUID = 6070269209439188188L; 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 index c4cec9bc76..c3564f8516 100644 --- 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 @@ -9,9 +9,11 @@ * 分店信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.bank.BranchInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class BranchInfo implements Serializable { private static final long serialVersionUID = -2744729367131146892L; 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 index c7cfda4646..6280efdc65 100644 --- 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 @@ -11,10 +11,12 @@ * 支行信息响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.bank.BranchInfoResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class BranchInfoResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -1419832502854175767L; 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 index 47527efe1e..2103740d3d 100644 --- 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 @@ -10,10 +10,12 @@ * 银行支行信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.bank.BranchSearchParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class BranchSearchParam implements Serializable { private static final long serialVersionUID = -8800316690160248833L; 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 index e1a52ab9a3..d57bc039c0 100644 --- 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 @@ -10,10 +10,12 @@ * 二维码校验响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.qrcode.QrCheckResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class QrCheckResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -3860756719827268969L; 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 index d6c015c0cd..5fb75e7a60 100644 --- 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 @@ -10,10 +10,12 @@ * 二维码响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.fund.qrcode.QrCodeResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class QrCodeResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 4521008628337929496L; 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..5ac7ec67db --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/background/BackgroundApplyResponse.java @@ -0,0 +1,27 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.background.BackgroundApplyResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..af964a5c5c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/background/BackgroundApplyResult.java @@ -0,0 +1,37 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.background.BackgroundApplyResult}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..24caf72ad0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/background/BackgroundGetResponse.java @@ -0,0 +1,30 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.background.BackgroundGetResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..0dee3613a3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyDetail.java @@ -0,0 +1,35 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.banner.BannerApplyDetail}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +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..f48a398c32 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyInfo.java @@ -0,0 +1,37 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.banner.BannerApplyInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..1914e8e4d5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyParam.java @@ -0,0 +1,30 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.banner.BannerApplyParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +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..f20d3d102d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerApplyResponse.java @@ -0,0 +1,27 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.banner.BannerApplyResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..b1bebb2c65 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerGetResponse.java @@ -0,0 +1,30 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.banner.BannerGetResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..b90ec8e2c6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerInfo.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 java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 精选展示位 + * + * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.banner.BannerInfo}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +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..0aea2cbfba --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItem.java @@ -0,0 +1,43 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.banner.BannerItem}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +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..27a6c49763 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemDetail.java @@ -0,0 +1,35 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.banner.BannerItemDetail}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +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..398f069596 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemFinder.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 lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 精选展示位明细中的视频号数据 + * + * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.banner.BannerItemFinder}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +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..6bc73483a8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemOfficialAccount.java @@ -0,0 +1,27 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.banner.BannerItemOfficialAccount}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +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..6c94eaa4e9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/banner/BannerItemProduct.java @@ -0,0 +1,27 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.banner.BannerItemProduct}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +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..ede5ae02ba --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/CatTreeNode.java @@ -0,0 +1,34 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.CatTreeNode}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..53e6d89388 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/LevelTreeInfo.java @@ -0,0 +1,26 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.LevelTreeInfo}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..cbd2d99724 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/OneLevelTreeNode.java @@ -0,0 +1,27 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.OneLevelTreeNode}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..f9fb9c6714 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeAuditResult.java @@ -0,0 +1,29 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.TreeAuditResult}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..ec757a38c0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeAuditResultDetail.java @@ -0,0 +1,29 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.TreeAuditResultDetail}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..4b81f0e465 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductEditInfo.java @@ -0,0 +1,35 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.TreeProductEditInfo}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..b9bd244e81 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductEditParam.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.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 添加/删除分类关联的商品 参数 + * + * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.TreeProductEditParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..2c71419314 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListInfo.java @@ -0,0 +1,38 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.TreeProductListInfo}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..f7a20389ae --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListParam.java @@ -0,0 +1,26 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.TreeProductListParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..40a24473e1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListResponse.java @@ -0,0 +1,26 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.TreeProductListResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..3e9e683d63 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeProductListResult.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.Data; +import lombok.NoArgsConstructor; + +/** + * 资金流水响应 结果 + * + * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.TreeProductListResult}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..25b90706d8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowGetResponse.java @@ -0,0 +1,22 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.TreeShowGetResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..e3fb0cedee --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowInfo.java @@ -0,0 +1,47 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.TreeShowInfo}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..f6b691370d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowParam.java @@ -0,0 +1,26 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.TreeShowParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..3545cbaa24 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/tree/TreeShowSetResponse.java @@ -0,0 +1,22 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.tree.TreeShowSetResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..24be27cd3c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductIndexParam.java @@ -0,0 +1,30 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.window.WindowProductIndexParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..5533aade3b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductListParam.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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.window.WindowProductListParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..5eaeef813e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductSetting.java @@ -0,0 +1,36 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.window.WindowProductSetting}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +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..fc17890a44 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/home/window/WindowProductSettingResponse.java @@ -0,0 +1,35 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.home.window.WindowProductSettingResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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 index 3e12c7e830..e02febe01c 100644 --- 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 @@ -9,9 +9,11 @@ * 微信图片信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.image.StoreImageInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class ChannelImageInfo implements Serializable { private static final long serialVersionUID = 8883519290965944530L; 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 index 903af375af..91409d509c 100644 --- 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 @@ -8,9 +8,11 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.image.StoreImageResponse}。 */ @Data @EqualsAndHashCode(callSuper = true) +@Deprecated public class ChannelImageResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -4163511427507976489L; 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 index 905720a8dc..5b834f455d 100644 --- 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 @@ -10,10 +10,12 @@ * 资质文件id * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.image.QualificationFileId}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class QualificationFileId implements Serializable { private static final long serialVersionUID = -546135264746778249L; 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 index 5a4332885c..296232d2e0 100644 --- 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 @@ -10,10 +10,12 @@ * 资质文件id响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.image.QualificationFileResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class QualificationFileResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 5172377567441096813L; 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 index f1625bd3c4..7a64b3323a 100644 --- 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 @@ -10,10 +10,12 @@ * 微信图片信息响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.image.UploadImageResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class UploadImageResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -609315696774437877L; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfCosUploadResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfCosUploadResponse.java new file mode 100644 index 0000000000..83e58d4423 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfCosUploadResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.kf; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 客服素材上传响应。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.kf.WxStoreKfCosUploadResponse}。 +*/ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class WxChannelKfCosUploadResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1L; + + /** 素材在 COS 上的地址。 */ + @JsonProperty("cos_url") + private String cosUrl; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfSendMsgParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfSendMsgParam.java new file mode 100644 index 0000000000..221f1b3113 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfSendMsgParam.java @@ -0,0 +1,93 @@ +package me.chanjar.weixin.channel.bean.kf; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** 发送客服消息请求参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.kf.WxStoreKfSendMsgParam}。 +*/ +@Data +@NoArgsConstructor +@Deprecated +public class WxChannelKfSendMsgParam implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 请求幂等标识。 */ + @JsonProperty("request_id") + private String requestId; + + /** 接收消息的用户 openid。 */ + @JsonProperty("open_id") + private String openId; + + /** 消息类型。 */ + @JsonProperty("msg_type") + private String msgType; + + /** 文本消息内容。 */ + @JsonProperty("text") + private Text text; + + /** 图片消息内容。 */ + @JsonProperty("image") + private CosUrlMessage image; + + /** 视频消息内容。 */ + @JsonProperty("video") + private CosUrlMessage video; + + /** 文件消息内容。 */ + @JsonProperty("file") + private CosUrlMessage file; + + /** 商品卡片消息内容。 */ + @JsonProperty("product_share") + private ProductShareMessage productShare; + + /** 订单卡片消息内容。 */ + @JsonProperty("order_share") + private OrderShareMessage orderShare; + + @Data + @NoArgsConstructor + public static class Text implements Serializable { + + private static final long serialVersionUID = 1L; + + @JsonProperty("content") + private String content; + } + + @Data + @NoArgsConstructor + public static class CosUrlMessage implements Serializable { + + private static final long serialVersionUID = 1L; + + @JsonProperty("cos_url") + private String cosUrl; + } + + @Data + @NoArgsConstructor + public static class ProductShareMessage implements Serializable { + + private static final long serialVersionUID = 1L; + + @JsonProperty("product_id") + private String productId; + } + + @Data + @NoArgsConstructor + public static class OrderShareMessage implements Serializable { + + private static final long serialVersionUID = 1L; + + @JsonProperty("order_id") + private String orderId; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfSendMsgResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfSendMsgResponse.java new file mode 100644 index 0000000000..bd65b3d65f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfSendMsgResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.kf; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 发送客服消息响应。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.kf.WxStoreKfSendMsgResponse}。 +*/ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class WxChannelKfSendMsgResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1L; + + /** 消息 id。 */ + @JsonProperty("msg_id") + private String msgId; +} 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/promoter/PromoterListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterListResponse.java index c193550369..f7c7298c03 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterListResponse.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/promoter/PromoterListResponse.java @@ -18,11 +18,19 @@ public class PromoterListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 1411870432999885996L; - /** 达人finder_id列表 */ + /** 达人finder_id列表,待废除后续以promoter_ids为准 */ @JsonProperty("finder_ids") private List finderIds; /** 达人总数 */ @JsonProperty("total_num") private Integer totalNum; + + /** 后面是否还有(true: 还有内容; false: 已结束)*/ + @JsonProperty("continue_flag") + private Boolean continueFlag; + + /** 达人带货id列表 */ + @JsonProperty("promoter_ids") + private List promoterIds; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SupplierFlowListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/league/supplier/SupplierFlowListResponse.java index 468985fe3e..d4a04af981 100644 --- 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 @@ -9,8 +9,7 @@ /** * 资金流水列表 响应 * - * @author LiXiZe - * @since 2023-04-16 + * @author Zeyes */ @Data @NoArgsConstructor 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 index 29ffbf921e..53b6a98c89 100644 --- 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 @@ -8,10 +8,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.limit.LimitSku}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class LimitSku implements Serializable { private static final long serialVersionUID = -1819737633227427482L; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitSkuUpdate.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitSkuUpdate.java new file mode 100644 index 0000000000..33d1a23ff6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitSkuUpdate.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.limit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 限时抢购任务的 SKU 更新信息。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.limit.LimitSkuUpdate}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class LimitSkuUpdate implements Serializable { + + private static final long serialVersionUID = 4209672674401016015L; + + /** SKU 所属商品 ID。 */ + @JsonProperty("product_id") + private String productId; + + /** SKU ID。 */ + @JsonProperty("sku_id") + private String skuId; + + /** SKU 抢购价格,单位为分。 */ + @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 index 35ea00d68d..deda13b9d1 100644 --- 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 @@ -8,10 +8,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.limit.LimitTaskAddResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class LimitTaskAddResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -4742165348862157618L; 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 index aefc4b8136..0e9cde57bc 100644 --- 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 @@ -8,9 +8,11 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.limit.LimitTaskInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class LimitTaskInfo implements Serializable { private static final long serialVersionUID = 3032226931637189351L; 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 index d608c8231e..71ea390796 100644 --- 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 @@ -6,8 +6,10 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.limit.LimitTaskListParam}。 */ @Data +@Deprecated public class LimitTaskListParam extends StreamPageParam { private static final long serialVersionUID = -7227161890365102302L; 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 index 688fd158dc..8cf0837e52 100644 --- 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 @@ -9,10 +9,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.limit.LimitTaskListResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class LimitTaskListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 3604657299385130217L; 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 index b89c072944..77ee8398c3 100644 --- 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 @@ -10,10 +10,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.limit.LimitTaskParam}。 */ @Data @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class LimitTaskParam implements Serializable { private static final long serialVersionUID = 3885409806249022528L; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateParam.java new file mode 100644 index 0000000000..8197ee3bcd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateParam.java @@ -0,0 +1,43 @@ +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; + +/** + * 更新限时抢购任务请求参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.limit.LimitTaskUpdateParam}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class LimitTaskUpdateParam implements Serializable { + + private static final long serialVersionUID = 7277247203887803045L; + + /** 限时抢购任务 ID。 */ + @JsonProperty("task_id") + private String taskId; + + /** 当前活动状态:0 待开始,1 进行中。 */ + @JsonProperty("status") + private Integer status; + + /** 活动开始时间,秒级时间戳。 */ + @JsonProperty("start_time") + private Long startTime; + + /** 活动结束时间,秒级时间戳。 */ + @JsonProperty("end_time") + private Long endTime; + + /** 活动名称。 */ + @JsonProperty("title") + private String title; + + /** SKU 抢购信息列表。 */ + @JsonProperty("limited_discount_skus") + private List skus; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateResponse.java new file mode 100644 index 0000000000..067c313bb5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateResponse.java @@ -0,0 +1,28 @@ +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; + +/** + * 更新限时抢购任务响应。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.limit.LimitTaskUpdateResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class LimitTaskUpdateResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 4429517792042527433L; + + /** 限时抢购任务 ID。 */ + @JsonProperty("task_id") + private String taskId; + + /** 活动名称。 */ + @JsonProperty("title") + private String title; +} 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/after/AfterSaleMessage.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/after/AfterSaleMessage.java index 52beec7932..90f4749977 100644 --- 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 @@ -12,11 +12,13 @@ * 售后消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.after.AfterSaleMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class AfterSaleMessage extends WxChannelMessage { private static final long serialVersionUID = -7263404451639198126L; 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 index 06fd349da8..780b069dad 100644 --- 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 @@ -10,9 +10,11 @@ * 售后信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.after.AfterSaleStatusInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class AfterSaleStatusInfo implements Serializable { private static final long serialVersionUID = -7309656340583314591L; 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 index adb0b7b392..2922af5b6c 100644 --- 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 @@ -10,9 +10,11 @@ * 纠纷信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.after.ComplaintInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class ComplaintInfo implements Serializable { private static final long serialVersionUID = 3988395560953978239L; 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 index e10a9b365a..49966a0829 100644 --- 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 @@ -12,11 +12,13 @@ * 纠纷消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.after.ComplaintMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class ComplaintMessage extends WxChannelMessage { private static final long serialVersionUID = 5358093415172409157L; 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 index f7a55ce0fb..de66480bee 100644 --- 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 @@ -10,9 +10,11 @@ * 优惠券操作消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.coupon.CouponActionInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class CouponActionInfo implements Serializable { private static final long serialVersionUID = -4456716511656569552L; 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 index 7433b7a6c2..3832d92ff7 100644 --- 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 @@ -13,11 +13,13 @@ * 卡券操作 消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.coupon.CouponActionMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class CouponActionMessage extends WxChannelMessage { private static final long serialVersionUID = 4910461800721504462L; 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 index 448d815a58..5c0d81f3f4 100644 --- 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 @@ -14,11 +14,13 @@ * 用户领券 消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.coupon.CouponReceiveMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class CouponReceiveMessage extends WxChannelMessage { private static final long serialVersionUID = 5121347165246528730L; 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 index 1356c47fca..04b1785d77 100644 --- 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 @@ -10,9 +10,11 @@ * 用户优惠券操作消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.coupon.UserCouponActionInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class UserCouponActionInfo implements Serializable { private static final long serialVersionUID = -5948836918972669529L; 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 index 26370e5142..1ec71967eb 100644 --- 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 @@ -13,11 +13,13 @@ * 用户卡券过期 消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.coupon.UserCouponExpireMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class UserCouponExpireMessage extends WxChannelMessage { private static final long serialVersionUID = -2557475297107588372L; 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 index 7b436743c3..7cff51fef9 100644 --- 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 @@ -13,11 +13,13 @@ * 用户卡券使用 消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.coupon.UserCouponUseMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class UserCouponUseMessage extends WxChannelMessage { private static final long serialVersionUID = -1051142666438578628L; 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 index b5a02ac834..4f03523979 100644 --- 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 @@ -12,11 +12,13 @@ * 账户变更通知 消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.fund.AccountNotifyMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class AccountNotifyMessage extends WxChannelMessage { private static final long serialVersionUID = 3846692537729725664L; 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 index 44ef398f8b..39f5afb217 100644 --- 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 @@ -10,9 +10,11 @@ * 账户信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.fund.BankNotifyInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class BankNotifyInfo implements Serializable { private static final long serialVersionUID = 4192569196686180014L; 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 index 83b466f07e..6a68488d0f 100644 --- 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 @@ -10,9 +10,11 @@ * 提现二维码回调 消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.fund.QrNotifyInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class QrNotifyInfo implements Serializable { private static final long serialVersionUID = 2470016408300157273L; 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 index 56e906a641..199223418f 100644 --- 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 @@ -12,11 +12,13 @@ * 提现二维码回调 消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.fund.QrNotifyMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class QrNotifyMessage extends WxChannelMessage { private static final long serialVersionUID = -4705790895359679423L; 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 index 810f40c95c..ff6ef9bdeb 100644 --- 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 @@ -10,9 +10,11 @@ * 提现通知信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.fund.WithdrawNotifyInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class WithdrawNotifyInfo implements Serializable { private static final long serialVersionUID = 2987401114254821956L; 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 index ff45e73ec6..86a72d91e6 100644 --- 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 @@ -12,11 +12,13 @@ * 账户变更通知 消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.fund.WithdrawNotifyMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class WithdrawNotifyMessage extends WxChannelMessage { private static final long serialVersionUID = -2504086242143523430L; 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 index 8ff3ead54e..a8ed7ec36c 100644 --- 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 @@ -10,10 +10,12 @@ * 订单取消信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderCancelInfo}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class OrderCancelInfo extends OrderIdInfo { private static final long serialVersionUID = -8022876997578127873L; 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 index 8e6b33c2ee..148d7406c4 100644 --- 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 @@ -12,11 +12,13 @@ * 订单取消消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderCancelMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class OrderCancelMessage extends WxChannelMessage { private static final long serialVersionUID = 5389546516473919310L; 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 index bd212092a5..de5d6b904b 100644 --- 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 @@ -10,10 +10,12 @@ * 订单确认收货信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderConfirmInfo}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class OrderConfirmInfo extends OrderIdInfo { private static final long serialVersionUID = -2569494642832261346L; 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 index dda35041b2..95c4246fd1 100644 --- 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 @@ -12,11 +12,13 @@ * 订单确认收货消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderConfirmMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class OrderConfirmMessage extends WxChannelMessage { private static final long serialVersionUID = 4219477394934480425L; 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 index ca3d26736a..5f09b43995 100644 --- 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 @@ -10,10 +10,12 @@ * 订单发货信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderDeliveryInfo}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class OrderDeliveryInfo extends OrderIdInfo { private static final long serialVersionUID = 117962754344887556L; 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 index 25d79e2c4d..2bc081207a 100644 --- 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 @@ -12,11 +12,13 @@ * 订单发货消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderDeliveryMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class OrderDeliveryMessage extends WxChannelMessage { private static final long serialVersionUID = -1440834047566984402L; 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 index b4986f35c4..1458e42014 100644 --- 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 @@ -10,10 +10,12 @@ * 订单其他信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderExtInfo}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class OrderExtInfo extends OrderIdInfo { private static final long serialVersionUID = 4723533858047219828L; 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 index c5ede6c6bd..0a3880ff5d 100644 --- 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 @@ -12,11 +12,13 @@ * 订单状态消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderExtMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class OrderExtMessage extends WxChannelMessage { private static final long serialVersionUID = -3183077256476798756L; 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 index b9ac33b376..d71346f19b 100644 --- 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 @@ -10,9 +10,11 @@ * 订单id信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderIdInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class OrderIdInfo implements Serializable { private static final long serialVersionUID = 5547544436235032051L; 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 index 398c29bde4..4f152d5c78 100644 --- 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 @@ -12,11 +12,13 @@ * 订单id消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderIdMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class OrderIdMessage extends WxChannelMessage { private static final long serialVersionUID = 3793987364799712798L; 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 index d916c14a21..3617a3fef5 100644 --- 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 @@ -10,10 +10,12 @@ * 订单支付信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderPayInfo}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class OrderPayInfo extends OrderIdInfo { private static final long serialVersionUID = -3502786073769735831L; 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 index ee1f458aba..689e6d1f2a 100644 --- 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 @@ -12,11 +12,13 @@ * 订单支付成功消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderPayMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class OrderPayMessage extends WxChannelMessage { private static final long serialVersionUID = 1083018549119427808L; 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 index b4f48b6fb8..8876f36167 100644 --- 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 @@ -10,10 +10,12 @@ * 订单结算信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderSettleInfo}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class OrderSettleInfo extends OrderIdInfo { private static final long serialVersionUID = -1817955568383872053L; 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 index 2d3d1d96d6..e0241d2944 100644 --- 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 @@ -12,11 +12,13 @@ * 订单结算消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderSettleMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class OrderSettleMessage extends WxChannelMessage { private static final long serialVersionUID = -4001189226630840548L; 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 index 4a06ccc99c..3d72a02ede 100644 --- 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 @@ -13,11 +13,13 @@ * 订单状态消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.order.OrderStatusMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class OrderStatusMessage extends WxChannelMessage { private static final long serialVersionUID = -356717038344749283L; 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 index 9a7c021c9d..0524d7c75b 100644 --- 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 @@ -13,11 +13,13 @@ * 品牌消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.product.BrandMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class BrandMessage extends WxChannelMessage { private static final long serialVersionUID = -3773902704930003105L; 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 index f6d696d5c1..bf51aedf7d 100644 --- 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 @@ -13,11 +13,13 @@ * 类目审核消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.product.CategoryAuditMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class CategoryAuditMessage extends WxChannelMessage { private static final long serialVersionUID = 3192582751919917223L; 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 index 569b53781e..1314aacbfc 100644 --- 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 @@ -13,11 +13,13 @@ * SPU审核消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.product.SpuAuditMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class SpuAuditMessage extends WxChannelMessage { private static final long serialVersionUID = 1763291928383078102L; 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 index 7fb9f272e8..a1d6194818 100644 --- 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 @@ -13,11 +13,13 @@ * SPU状态消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.product.SpuStatusMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class SpuStatusMessage extends WxChannelMessage { private static final long serialVersionUID = 6872830451279856492L; 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..fb6e0483c5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/product/SpuStockMessage.java @@ -0,0 +1,90 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.product.SpuStockMessage}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +@Deprecated +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..ea75585411 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/sharer/SharerChangeMessage.java @@ -0,0 +1,50 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.sharer.SharerChangeMessage}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +@Deprecated +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..ec694318cb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/store/CloseStoreMessage.java @@ -0,0 +1,40 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.store.CloseStoreMessage}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +@Deprecated +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..59a765ff86 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/store/NicknameUpdateMessage.java @@ -0,0 +1,45 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.store.NicknameUpdateMessage}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +@Deprecated +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 index 49bbb0548b..07ba22d8ba 100644 --- 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 @@ -11,9 +11,11 @@ * 团长商品变更信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.supplier.SupplierItemInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class SupplierItemInfo implements Serializable { private static final long serialVersionUID = -1971161027976024360L; 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 index 2403aa0c60..c083658b5a 100644 --- 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 @@ -12,11 +12,13 @@ * 团长商品变更 消息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.supplier.SupplierItemMessage}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JacksonXmlRootElement(localName = "xml") +@Deprecated public class SupplierItemMessage extends WxChannelMessage { private static final long serialVersionUID = -4520611382070764349L; 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..df83d2ae86 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/CouponInfo.java @@ -0,0 +1,29 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.vip.CouponInfo}。 + */ + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@Deprecated +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..b76ec09125 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/ExchangeInfo.java @@ -0,0 +1,44 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.vip.ExchangeInfo}。 + */ + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@Deprecated +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..ae4684b348 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/ExchangeInfoMessage.java @@ -0,0 +1,30 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.vip.ExchangeInfoMessage}。 + */ + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +@Deprecated +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..2d97d8eed2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/ProductInfo.java @@ -0,0 +1,29 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.vip.ProductInfo}。 + */ + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@Deprecated +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..af921de924 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/UserInfo.java @@ -0,0 +1,61 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.vip.UserInfo}。 + */ + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@Deprecated +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..5b4f5b725e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/vip/UserInfoMessage.java @@ -0,0 +1,30 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.vip.UserInfoMessage}。 + */ + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +@Deprecated +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..031073e8a9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/voucher/VoucherInfo.java @@ -0,0 +1,108 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.voucher.VoucherInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..732d0cbeb5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/message/voucher/VoucherMessage.java @@ -0,0 +1,31 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.message.voucher.VoucherMessage}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JacksonXmlRootElement(localName = "xml") +@Deprecated +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 index 5401a588bf..0cbf7598f2 100644 --- 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 @@ -10,9 +10,11 @@ * 售后信息详情 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.AfterSaleDetail}。 */ @Data @NoArgsConstructor +@Deprecated public class AfterSaleDetail implements Serializable { private static final long serialVersionUID = -3786573982841041144L; 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 index 118feba35b..7f29f0af07 100644 --- 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 @@ -9,9 +9,11 @@ * 售后信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.AfterSaleOrderInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class AfterSaleOrderInfo implements Serializable { private static final long serialVersionUID = 3938545222231426455L; 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 index f6485085bb..3b9cd5fafd 100644 --- 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 @@ -9,9 +9,11 @@ * 订单修改信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.ChangeOrderInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class ChangeOrderInfo implements Serializable { private static final long serialVersionUID = 4932726847720452340L; 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..ea7a0f110c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/ChangeSkuInfo.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信息 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.ChangeSkuInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..8240514c96 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DecodeAddressInfo.java @@ -0,0 +1,24 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.DecodeAddressInfo}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..f151864599 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DecodeSensitiveInfoResponse.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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.DecodeSensitiveInfoResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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 index 5427a49839..d2f44516a7 100644 --- 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 @@ -11,9 +11,11 @@ * 发货物流信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.DeliveryProductInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class DeliveryProductInfo implements Serializable { private static final long serialVersionUID = -8110532854439612471L; 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 index 6aca6feed4..34595bacf5 100644 --- 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 @@ -12,10 +12,12 @@ * 修改物流参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.DeliveryUpdateParam}。 */ @Data @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated public class DeliveryUpdateParam implements Serializable { /** 订单ID */ 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..54ee531fd0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/DropshipInfo.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 代发相关信息 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.DropshipInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..7a7642565b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/FreeGiftInfo.java @@ -0,0 +1,27 @@ +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; + +/** + * 赠品信息 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.FreeGiftInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..efd7c97110 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/MainProductInfo.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; + +/** + * 赠品对应的主品信息 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.MainProductInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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 String 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 index ff3e1ba332..148e2be5e1 100644 --- 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 @@ -10,14 +10,34 @@ * 地址信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderAddressInfo}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated 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 index 55eb6a8655..2fbb11195c 100644 --- 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 @@ -13,11 +13,13 @@ * 订单地址参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderAddressParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(Include.NON_NULL) +@Deprecated public class OrderAddressParam implements Serializable { private static final long serialVersionUID = 2277618297276466650L; 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..25f19cfeb7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderAgentInfo.java @@ -0,0 +1,32 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderAgentInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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 index 78e391e774..c2b983a853 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCommissionInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCommissionInfo.java @@ -9,9 +9,11 @@ * 分佣信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderCommissionInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class OrderCommissionInfo implements Serializable { private static final long serialVersionUID = -3046852309683467272L; @@ -42,4 +44,8 @@ public class OrderCommissionInfo implements Serializable { /** 达人openfinderid */ @JsonProperty("openfinderid") private String openFinderId; + + /** 新带货平台 id */ + @JsonProperty("talent_id") + private String talentId; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCompensationDeliveryParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCompensationDeliveryParam.java new file mode 100644 index 0000000000..4b6c36113a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCompensationDeliveryParam.java @@ -0,0 +1,36 @@ +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.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.delivery.DeliveryInfo; + +/** + * 订单补发货 请求参数 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderCompensationDeliveryParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +public class OrderCompensationDeliveryParam implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 订单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/order/OrderCouponInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCouponInfo.java index a8f020c0ef..6ed69983a5 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCouponInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCouponInfo.java @@ -9,13 +9,35 @@ * 卡券信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderCouponInfo}。 */ @Data @NoArgsConstructor +@Deprecated 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..e67cdb053a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderCustomInfo.java @@ -0,0 +1,35 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderCustomInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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 index dce5460141..ea5a5fb060 100644 --- 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 @@ -10,9 +10,11 @@ * 物流信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderDeliveryInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class OrderDeliveryInfo implements Serializable { private static final long serialVersionUID = -5348922760017557397L; @@ -32,4 +34,28 @@ public class OrderDeliveryInfo implements Serializable { @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 index 1f4d55924d..8abe26f4fd 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderDetailInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderDetailInfo.java @@ -10,9 +10,11 @@ * 订单详细数据 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderDetailInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class OrderDetailInfo implements Serializable { private static final long serialVersionUID = 3916307299998005676L; @@ -52,4 +54,27 @@ public class OrderDetailInfo implements Serializable { @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 index 3338d1a428..eb7f131213 100644 --- 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 @@ -1,26 +1,56 @@ package me.chanjar.weixin.channel.bean.order; import com.fasterxml.jackson.annotation.JsonProperty; -import java.io.Serializable; import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + /** * 订单备注信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderExtInfo}。 */ @Data @NoArgsConstructor +@Deprecated 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..10087a1af5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderGreetingCardInfo.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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderGreetingCardInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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 index f1e92e1339..c3dd768279 100644 --- 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 @@ -12,11 +12,13 @@ * 订单id参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderIdParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(Include.NON_NULL) +@Deprecated public class OrderIdParam implements Serializable { private static final long serialVersionUID = -8616582197963359789L; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfo.java index 894b36f7af..b3754a0acd 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfo.java @@ -9,9 +9,11 @@ * 视频号小店订单 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class OrderInfo implements Serializable { private static final long serialVersionUID = -4562618835611282016L; @@ -39,6 +41,26 @@ public class OrderInfo implements Serializable { @JsonProperty("aftersale_detail") protected AfterSaleDetail afterSaleDetail; + /** 是否为礼物订单 */ + @JsonProperty("is_present") + private Boolean present; + + /** 礼物订单ID */ + @JsonProperty("present_order_id_str") + private String presentOrderId; + + /** 礼物订单留言 */ + @JsonProperty("present_note") + private String presentNote; + + /** 礼物订单赠送者openid */ + @JsonProperty("present_giver_openid") + private String presentGiverOpenid; + + /** 礼物订单赠送者unionid */ + @JsonProperty("present_giver_unionid") + private String presentGiverUnionid; + /** 创建时间 秒级时间戳 */ @JsonProperty("create_time") protected Integer createTime; @@ -46,5 +68,4 @@ public class OrderInfo implements Serializable { /** 更新时间 秒级时间戳 */ @JsonProperty("update_time") protected Integer updateTime; - } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfoParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfoParam.java new file mode 100644 index 0000000000..c26c888ddf --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderInfoParam.java @@ -0,0 +1,33 @@ +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; + +/** + * 获取订单详情参数 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderInfoParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +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 index 0b6fd53c17..da9514c10a 100644 --- 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 @@ -10,10 +10,12 @@ * 订单信息响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderInfoResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class OrderInfoResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 935829924760021624L; 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 index a84da3d2e8..2a589b3324 100644 --- 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 @@ -13,11 +13,13 @@ * 获取订单列表参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderListParam}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JsonInclude(Include.NON_NULL) +@Deprecated public class OrderListParam extends StreamPageParam { private static final long serialVersionUID = 3780097459964746890L; 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 index 454abc59d9..cfa98a8190 100644 --- 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 @@ -11,10 +11,12 @@ * 订单列表 响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderListResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class OrderListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -6198624448684807852L; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPayInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPayInfo.java index 6c912f7c45..20b09a8f2d 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPayInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPayInfo.java @@ -9,19 +9,17 @@ * 支付信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderPayInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class OrderPayInfo implements Serializable { private static final long serialVersionUID = -5085386252699113948L; /** 预支付id */ - @JsonProperty("prepayId") - private String prepayId; - - /** 预支付时间,秒级时间戳 */ - @JsonProperty("prepay_time") - private Long prepayTime; + @JsonProperty("payment_method") + private Integer paymentMethod; /** 支付时间,秒级时间戳 */ @JsonProperty("pay_time") diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceInfo.java index 89bef2daed..eb36a7c9f5 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderPriceInfo.java @@ -1,58 +1,142 @@ package me.chanjar.weixin.channel.bean.order; import com.fasterxml.jackson.annotation.JsonProperty; -import java.io.Serializable; import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + /** * 商店订单价格信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderPriceInfo}。 */ @Data @NoArgsConstructor +@Deprecated 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 index 30f74501c4..bc832829b8 100644 --- 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 @@ -11,9 +11,11 @@ * 订单价格参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderPriceParam}。 */ @Data @JsonInclude(Include.NON_NULL) +@Deprecated public class OrderPriceParam implements Serializable { private static final long serialVersionUID = -7925819981481556218L; 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..b95732696a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductExtraService.java @@ -0,0 +1,30 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderProductExtraService}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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 index 3d5d7a92cb..99736b5358 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderProductInfo.java @@ -1,8 +1,10 @@ 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; @@ -11,85 +13,246 @@ * 订单商品信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderProductInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class OrderProductInfo implements Serializable { private static final long serialVersionUID = -2193536732955185928L; - /** 商品spu id */ + /** + * 商品spu id + */ @JsonProperty("product_id") private String productId; - /** sku_id */ + /** + * sku_id + */ @JsonProperty("sku_id") private String skuId; - /** sku小图 */ + /** + * sku小图 + */ @JsonProperty("thumb_img") private String thumbImg; - /** sku数量 */ + /** + * sku数量 + */ @JsonProperty("sku_cnt") private Integer skuCnt; - /** 售卖价格(单位:分) */ + /** + * 售卖价格(单位:分) + */ @JsonProperty("sale_price") private Integer salePrice; - /** 商品标题 */ + /** + * 商品标题 + */ @JsonProperty("title") private String title; - /** 正在售后/退款流程中的 sku 数量 */ + /** + * 正在售后/退款流程中的 sku 数量 + */ @JsonProperty("on_aftersale_sku_cnt") private Integer onAfterSaleSkuCnt; - /** 完成售后/退款的 sku 数量 */ + /** + * 完成售后/退款的 sku 数量 + */ @JsonProperty("finish_aftersale_sku_cnt") private Integer finishAfterSaleSkuCnt; - /** 商品编码 */ + /** + * 商品编码 + */ @JsonProperty("sku_code") private String skuCode; - /** 市场价格(单位:分) */ + /** + * 市场价格(单位:分) + */ @JsonProperty("market_price") private Integer marketPrice; - /** sku属性 */ + /** + * sku属性 + */ @JsonProperty("sku_attrs") private List skuAttrs; - /** sku实付价格 */ + /** + * sku实付价格 + */ @JsonProperty("real_price") private Integer realPrice; - /** 商品外部spu id */ + /** + * 商品外部spu id + */ @JsonProperty("out_product_id") private String outProductId; - /** 商品外部sku id */ + /** + * 商品外部sku id + */ @JsonProperty("out_sku_id") private String outSkuId; - /** 是否有优惠金额,非必填,默认为false */ + /** + * 是否有优惠金额,非必填,默认为false + */ @JsonProperty("is_discounted") private Boolean isDiscounted; - /** 优惠后 sku 价格,非必填,is_discounted为 true 时有值 */ + /** + * 优惠后 sku 价格,非必填,is_discounted为 true 时有值 + */ @JsonProperty("estimate_price") private Integer estimatePrice; - /** 是否修改过价格,非必填,默认为false */ + /** + * 是否修改过价格,非必填,默认为false + */ @JsonProperty("is_change_price") private Boolean changePriced; - /** 改价后 sku 价格,非必填,is_change_price为 true 时有值 */ + /** + * 改价后 sku 价格,非必填,is_change_price为 true 时有值 + */ @JsonProperty("change_price") private Integer changePrice; - /** 区域库存id */ + /** + * 区域库存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..40e55b280b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderRefundInfo.java @@ -0,0 +1,23 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderRefundInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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 index 707ec0d96b..ce06eea211 100644 --- 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 @@ -10,10 +10,12 @@ * 订单备注 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderRemarkParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class OrderRemarkParam implements Serializable { private static final long serialVersionUID = 2285714780419948468L; 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 index 012b0fca49..a325c72e53 100644 --- 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 @@ -11,10 +11,12 @@ * 订单 搜索条件 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderSearchCondition}。 */ @Data @NoArgsConstructor @JsonInclude(Include.NON_EMPTY) +@Deprecated public class OrderSearchCondition implements Serializable { private static final long serialVersionUID = 5492584333971883140L; @@ -30,12 +32,33 @@ public class OrderSearchCondition implements Serializable { @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 index 0a9483e0d5..7e3332b4b8 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSearchParam.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSearchParam.java @@ -9,10 +9,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderSearchParam}。 */ @Data @NoArgsConstructor @JsonInclude(Include.NON_EMPTY) +@Deprecated public class OrderSearchParam extends StreamPageParam { private static final long serialVersionUID = 5737520097455135218L; @@ -25,6 +27,6 @@ public class OrderSearchParam extends StreamPageParam { private Integer onAfterSaleOrderExist; /** 订单状态 {@link me.chanjar.weixin.channel.enums.WxOrderStatus} */ - @JsonProperty("on_aftersale_order_exist") + @JsonProperty("status") private Integer status; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSettleInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSettleInfo.java index c264a6289a..0a06d9e57a 100644 --- 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 @@ -1,7 +1,9 @@ package me.chanjar.weixin.channel.bean.order; import com.fasterxml.jackson.annotation.JsonProperty; + import java.io.Serializable; + import lombok.Data; import lombok.NoArgsConstructor; @@ -9,18 +11,41 @@ * 结算信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderSettleInfo}。 */ @Data @NoArgsConstructor +@Deprecated 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 index be66463445..75caf153b2 100644 --- 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 @@ -1,7 +1,9 @@ package me.chanjar.weixin.channel.bean.order; import com.fasterxml.jackson.annotation.JsonProperty; + import java.io.Serializable; + import lombok.Data; import lombok.NoArgsConstructor; @@ -9,25 +11,41 @@ * 分享信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderSharerInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class OrderSharerInfo implements Serializable { private static final long serialVersionUID = 7183259072254660971L; - /** 分享员openid */ + /** + * 分享员openid + */ @JsonProperty("sharer_openid") private String sharerOpenid; - /** 分享员unionid */ + /** + * 分享员unionid + */ @JsonProperty("sharer_unionid") private String sharerUnionid; - /** 分享员类型,0:普通分享员,1:店铺分享员 */ + /** + * 分享员类型,0:普通分享员,1:店铺分享员 + */ @JsonProperty("sharer_type") private Integer sharerType; - /** 分享场景 {@link me.chanjar.weixin.channel.enums.ShareScene} */ + /** + * 分享场景 {@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..d1cf4d2713 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSkuDeliverInfo.java @@ -0,0 +1,30 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderSkuDeliverInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..c4d5a59eef --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSkuShareInfo.java @@ -0,0 +1,46 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderSkuShareInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..d3646307b7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/OrderSourceInfo.java @@ -0,0 +1,68 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.OrderSourceInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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/PreShipmentChangeSkuRejectParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PreShipmentChangeSkuRejectParam.java new file mode 100644 index 0000000000..77bebf1339 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PreShipmentChangeSkuRejectParam.java @@ -0,0 +1,34 @@ +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; + +/** + * 拒绝待发货前更换SKU请求 请求参数 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.PreShipmentChangeSkuRejectParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +public class PreShipmentChangeSkuRejectParam implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 订单ID */ + @JsonProperty("order_id") + private String orderId; + + /** 拒绝原因 */ + @JsonProperty("reject_reason") + private String rejectReason; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PreShipmentChangeSkuResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PreShipmentChangeSkuResponse.java new file mode 100644 index 0000000000..5bb2a14f9f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PreShipmentChangeSkuResponse.java @@ -0,0 +1,27 @@ +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; + +/** + * 获取待发货前更换SKU待处理请求 响应 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.PreShipmentChangeSkuResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class PreShipmentChangeSkuResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1L; + + /** 更换SKU信息 */ + @JsonProperty("change_sku_info") + private ChangeSkuInfo changeSkuInfo; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PresentNoteAddParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PresentNoteAddParam.java new file mode 100644 index 0000000000..78c1541fe0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PresentNoteAddParam.java @@ -0,0 +1,34 @@ +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; + +/** + * 礼物订单新增备注信息 请求参数 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.PresentNoteAddParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +public class PresentNoteAddParam implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 礼物订单ID */ + @JsonProperty("order_id") + private String orderId; + + /** 备注内容 */ + @JsonProperty("note") + private String note; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PresentSubOrderResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PresentSubOrderResponse.java new file mode 100644 index 0000000000..1d77777e0a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PresentSubOrderResponse.java @@ -0,0 +1,28 @@ +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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.PresentSubOrderResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class PresentSubOrderResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1L; + + /** 子单列表 */ + @JsonProperty("sub_order_ids") + private List subOrderIds; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PrivateNumberAddPhoneParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PrivateNumberAddPhoneParam.java new file mode 100644 index 0000000000..971cc57a1f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PrivateNumberAddPhoneParam.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 java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 添加待认证手机号 请求参数 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.PrivateNumberAddPhoneParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +public class PrivateNumberAddPhoneParam implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 手机号 */ + @JsonProperty("phone") + private String phone; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PrivateNumberGetPhoneResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PrivateNumberGetPhoneResponse.java new file mode 100644 index 0000000000..c96bc59b52 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PrivateNumberGetPhoneResponse.java @@ -0,0 +1,28 @@ +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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.PrivateNumberGetPhoneResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class PrivateNumberGetPhoneResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1L; + + /** 手机号认证信息列表 */ + @JsonProperty("phone_list") + private List phoneList; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PrivateNumberPhoneInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PrivateNumberPhoneInfo.java new file mode 100644 index 0000000000..70bade5c15 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PrivateNumberPhoneInfo.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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.PrivateNumberPhoneInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class PrivateNumberPhoneInfo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 手机号 */ + @JsonProperty("phone") + private String phone; + + /** + * 认证状态:1-待认证,2-认证成功,3-认证失败 + */ + @JsonProperty("auth_status") + private Integer authStatus; + +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PrivateNumberSendVerifyCodeParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PrivateNumberSendVerifyCodeParam.java new file mode 100644 index 0000000000..148b609eec --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/PrivateNumberSendVerifyCodeParam.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 java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 获取短信验证码 请求参数 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.PrivateNumberSendVerifyCodeParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +@Deprecated +public class PrivateNumberSendVerifyCodeParam implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 手机号 */ + @JsonProperty("phone") + private String phone; + +} 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..496a6f163c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/QualityInsepctInfo.java @@ -0,0 +1,24 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.QualityInsepctInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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/RealNumberViewAuditResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/RealNumberViewAuditResponse.java new file mode 100644 index 0000000000..a741ba9d48 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/RealNumberViewAuditResponse.java @@ -0,0 +1,33 @@ +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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.RealNumberViewAuditResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class RealNumberViewAuditResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1L; + + /** + * 审核状态:1-审核中,2-审核通过,3-审核拒绝 + */ + @JsonProperty("audit_status") + private Integer auditStatus; + + /** 真实号码(审核通过后返回)*/ + @JsonProperty("real_number") + private String realNumber; + +} 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..e79a8c2616 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/RechargeInfo.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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.RechargeInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..da33224c99 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/TelNumberExtInfo.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.order; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 联系方式信息 + * + * @author imyzt + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.TelNumberExtInfo}。 + */ +@Data +@Deprecated +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..504dda40c2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/VirtualNumberInfo.java @@ -0,0 +1,32 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.VirtualNumberInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..c271315ed8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/order/VirtualTelNumberResponse.java @@ -0,0 +1,32 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.order.VirtualTelNumberResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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/AddProductThirdPartySourceParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AddProductThirdPartySourceParam.java new file mode 100644 index 0000000000..29cba6d5e1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AddProductThirdPartySourceParam.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import java.io.Serializable; +import lombok.Data; + +/** 新增第三方货源信息请求参数. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.AddProductThirdPartySourceParam}。 +*/ +@Data +@Deprecated +public class AddProductThirdPartySourceParam implements Serializable { + private static final long serialVersionUID = -5784320217481497742L; + + @JsonProperty("scene_value") + private Integer sceneValue; + @JsonProperty("publish_method") + private Integer publishMethod; + private JsonNode supplier; + @JsonProperty("supplier_shop_performance") + private JsonNode supplierShopPerformance; + @JsonProperty("product_source_info") + private JsonNode productSourceInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AddProductThirdPartySourceResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AddProductThirdPartySourceResponse.java new file mode 100644 index 0000000000..d9709486da --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AddProductThirdPartySourceResponse.java @@ -0,0 +1,19 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 新增第三方货源信息响应. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.AddProductThirdPartySourceResponse}。 +*/ +@Data +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class AddProductThirdPartySourceResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -7528226120383065861L; + + @JsonProperty("third_party_source_id") + private Long thirdPartySourceId; +} 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..064237b6f2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AfterSaleInfo.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.product; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品售后信息 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.AfterSaleInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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 index b97473e3d3..1e84d3587a 100644 --- 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 @@ -10,9 +10,11 @@ * 商品详情 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.DescriptionInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class DescriptionInfo implements Serializable { private static final long serialVersionUID = 3402153796734747882L; 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 index 0c21d9610e..4b85278cb5 100644 --- 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 @@ -10,10 +10,12 @@ * 运费信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ExpressInfo}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class ExpressInfo implements Serializable { private static final long serialVersionUID = 3274035362148612426L; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingNewParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingNewParam.java new file mode 100644 index 0000000000..fbfb2511c6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingNewParam.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** 商品属性映射及推荐请求参数. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ExternalProductMappingNewParam}。 +*/ +@Data +@Deprecated +public class ExternalProductMappingNewParam implements Serializable { + private static final long serialVersionUID = -7982070319116550518L; + + @JsonProperty("cat_id") + private Long catId; + @JsonProperty("external_category_name") + private String externalCategoryName; + @JsonProperty("head_imgs") + private List headImgs; + @JsonProperty("detail_imgs") + private List detailImgs; + private String title; + @JsonProperty("external_attributes") + private List externalAttributes; + + @Data + public static class ExternalAttribute implements Serializable { + private static final long serialVersionUID = 300805187240781417L; + private String key; + private String value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingNewResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingNewResponse.java new file mode 100644 index 0000000000..21f26e3406 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingNewResponse.java @@ -0,0 +1,32 @@ +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; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 商品属性映射及推荐响应. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ExternalProductMappingNewResponse}。 +*/ +@Data +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ExternalProductMappingNewResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 4536547956225312823L; + + @JsonProperty("attributes") + private List attributes; + + /** 推荐属性. */ + @Data + @NoArgsConstructor + public static class Attribute implements Serializable { + private static final long serialVersionUID = -4072024462101489333L; + + private String key; + private String value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingParam.java new file mode 100644 index 0000000000..a4ed9df310 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingParam.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** 站内外商品属性映射请求参数. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ExternalProductMappingParam}。 +*/ +@Data +@Deprecated +public class ExternalProductMappingParam implements Serializable { + private static final long serialVersionUID = 3288069294712374035L; + + @JsonProperty("cat_id") + private Long catId; + @JsonProperty("external_attribute_name") + private String externalAttributeName; + @JsonProperty("external_attribute_value") + private String externalAttributeValue; + @JsonProperty("external_category_name") + private String externalCategoryName; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingResponse.java new file mode 100644 index 0000000000..69426bf806 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingResponse.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 站内外商品属性映射响应. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ExternalProductMappingResponse}。 +*/ +@Data +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ExternalProductMappingResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -8356596972896906087L; + + @JsonProperty("external_attribute_name") + private String externalAttributeName; + @JsonProperty("external_attribute_value") + private String externalAttributeValue; + @JsonProperty("internal_attribute_name") + private String internalAttributeName; + @JsonProperty("internal_attribute_value") + private List internalAttributeValue; +} 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 index aeaf1a8cd6..14ca39b3d2 100644 --- 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 @@ -7,9 +7,11 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ExtraServiceInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class ExtraServiceInfo implements Serializable { private static final long serialVersionUID = -5517806977282063174L; @@ -20,4 +22,20 @@ public class ExtraServiceInfo implements Serializable { */ @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/GiftActivityAddParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftActivityAddParam.java new file mode 100644 index 0000000000..c3ffb6bb3c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftActivityAddParam.java @@ -0,0 +1,25 @@ +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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.GiftActivityAddParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +public class GiftActivityAddParam implements Serializable { + + private static final long serialVersionUID = -3332952823917162308L; + + @JsonProperty("gift_activity") + private GiftActivityInfo giftActivity; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftActivityAddResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftActivityAddResponse.java new file mode 100644 index 0000000000..0381abed34 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftActivityAddResponse.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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.GiftActivityAddResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class GiftActivityAddResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -4527079816331082871L; + + @JsonProperty("activity_id") + private String activityId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftActivityInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftActivityInfo.java new file mode 100644 index 0000000000..00da46652a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftActivityInfo.java @@ -0,0 +1,94 @@ +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; + +/** + * 买赠活动信息 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.GiftActivityInfo}。 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class GiftActivityInfo implements Serializable { + + private static final long serialVersionUID = 3970308144375119175L; + + @JsonProperty("activity_id") + private String activityId; + + @JsonProperty("title") + private String title; + + @JsonProperty("start_time") + private Long startTime; + + @JsonProperty("end_time") + private Long endTime; + + @JsonProperty("detail") + private Detail detail; + + @Data + public static class Detail implements Serializable { + private static final long serialVersionUID = 1019081831733485084L; + + @JsonProperty("show_scene") + private Integer showScene; + + @JsonProperty("receive_limit") + private ReceiveLimit receiveLimit; + + @JsonProperty("main_products") + private List mainProducts; + + @JsonProperty("gift_set") + private GiftSet giftSet; + } + + @Data + public static class ReceiveLimit implements Serializable { + private static final long serialVersionUID = 3332293571373311829L; + + @JsonProperty("is_limited") + private Boolean limited; + + @JsonProperty("limit_num") + private Integer limitNum; + } + + @Data + public static class MainProduct implements Serializable { + private static final long serialVersionUID = 6368866030784193437L; + + @JsonProperty("product_id") + private String productId; + } + + @Data + public static class GiftSet implements Serializable { + private static final long serialVersionUID = 8473755235926932739L; + + @JsonProperty("gift_items") + private List giftItems; + + @JsonProperty("gift_set_num") + private Integer giftSetNum; + } + + @Data + public static class GiftItem implements Serializable { + private static final long serialVersionUID = -4130391476834450014L; + + @JsonProperty("gift_id") + private String giftId; + + @JsonProperty("give_num") + private Integer giveNum; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductAddResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductAddResponse.java new file mode 100644 index 0000000000..2101f4630d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductAddResponse.java @@ -0,0 +1,26 @@ +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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.GiftProductAddResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class GiftProductAddResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -5971026809157610975L; + + /** 赠品商品ID */ + @JsonProperty("product_id") + private String productId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductGetResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductGetResponse.java new file mode 100644 index 0000000000..6398aa4e6f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductGetResponse.java @@ -0,0 +1,30 @@ +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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.GiftProductGetResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class GiftProductGetResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 5331169221157446692L; + + /** 赠品线上数据 */ + @JsonProperty("product") + private GiftProductInfo product; + + /** 赠品草稿数据 */ + @JsonProperty("edit_product") + private GiftProductInfo editProduct; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductInfo.java new file mode 100644 index 0000000000..f2fe5fb06f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductInfo.java @@ -0,0 +1,13 @@ +package me.chanjar.weixin.channel.bean.product; + +/** + * 赠品商品信息 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.GiftProductInfo}。 + */ +@Deprecated +public class GiftProductInfo extends SpuUpdateInfo { + + private static final long serialVersionUID = -4366133550331058445L; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductListParam.java new file mode 100644 index 0000000000..2df26e1307 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductListParam.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import me.chanjar.weixin.channel.bean.base.StreamPageParam; + +/** + * 赠品列表查询参数 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.GiftProductListParam}。 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class GiftProductListParam extends StreamPageParam { + + private static final long serialVersionUID = 7583500622060651067L; + + /** 赠品状态 */ + @JsonProperty("status") + private Integer status; + + public GiftProductListParam() { + } + + public GiftProductListParam(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/GiftProductListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductListResponse.java new file mode 100644 index 0000000000..21d7050589 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/GiftProductListResponse.java @@ -0,0 +1,35 @@ +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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.GiftProductListResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class GiftProductListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -2734111694780970778L; + + /** 总数 */ + @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/LimitInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/LimitInfo.java index 389773d5e7..063e3006ac 100644 --- 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 @@ -10,10 +10,12 @@ * 限时购信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.LimitInfo}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class LimitInfo implements Serializable { private static final long serialVersionUID = -4670198322237114719L; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditQuotaResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditQuotaResponse.java new file mode 100644 index 0000000000..3870ea97a9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditQuotaResponse.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 商品提审限额响应. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductAuditQuotaResponse}。 +*/ +@Data +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ProductAuditQuotaResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -6242837308752181147L; + + @JsonProperty("audit_quota") + private AuditQuota auditQuota; + + @Data + public static class AuditQuota implements Serializable { + private static final long serialVersionUID = 6066821247844334714L; + + @JsonProperty("block_status") + private Integer blockStatus; + @JsonProperty("avail_quota") + private Integer availQuota; + @JsonProperty("total_quota") + private Integer totalQuota; + @JsonProperty("unlimited_type") + private Integer unlimitedType; + @JsonProperty("audit_total_quota") + private Integer auditTotalQuota; + @JsonProperty("audit_total_remaining") + private Integer auditTotalRemaining; + @JsonProperty("new_product_total_quota") + private Integer newProductTotalQuota; + @JsonProperty("new_product_remaining") + private Integer newProductRemaining; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategyInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategyInfo.java new file mode 100644 index 0000000000..047f4e46f6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategyInfo.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** 商品上架策略信息. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductAuditStrategyInfo}。 +*/ +@Data +@Deprecated +public class ProductAuditStrategyInfo implements Serializable { + private static final long serialVersionUID = -2747596416115475981L; + + @JsonProperty("hide_err_field_flag") + private Integer hideErrFieldFlag; + @JsonProperty("hit_duplicated_flag") + private Integer hitDuplicatedFlag; + @JsonProperty("hit_low_risk_rule_flag") + private Integer hitLowRiskRuleFlag; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategyResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategyResponse.java new file mode 100644 index 0000000000..e89a6f9b7d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategyResponse.java @@ -0,0 +1,19 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 商品上架策略响应. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductAuditStrategyResponse}。 +*/ +@Data +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ProductAuditStrategyResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -1074784511408331849L; + + @JsonProperty("audit_strategy") + private ProductAuditStrategyInfo auditStrategy; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategySetParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategySetParam.java new file mode 100644 index 0000000000..708d55bc5d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategySetParam.java @@ -0,0 +1,17 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** 设置商品上架策略请求参数. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductAuditStrategySetParam}。 +*/ +@Data +@Deprecated +public class ProductAuditStrategySetParam implements Serializable { + private static final long serialVersionUID = 7542738744842032508L; + + @JsonProperty("audit_strategy") + private ProductAuditStrategyInfo auditStrategy; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductBrandRecommendParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductBrandRecommendParam.java new file mode 100644 index 0000000000..3b9c18e776 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductBrandRecommendParam.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; + +/** 商品品牌推荐请求参数. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductBrandRecommendParam}。 +*/ +@Data +@Deprecated +public class ProductBrandRecommendParam implements Serializable { + private static final long serialVersionUID = 6462717198206491138L; + + @JsonProperty("cat_id") + private Long catId; + @JsonProperty("head_imgs") + private List headImgs; + @JsonProperty("detail_imgs") + private List detailImgs; + private String title; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductBrandRecommendResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductBrandRecommendResponse.java new file mode 100644 index 0000000000..71fa9acfd5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductBrandRecommendResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 商品品牌推荐响应. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductBrandRecommendResponse}。 +*/ +@Data +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ProductBrandRecommendResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 4350605866373432810L; + + @JsonProperty("brand_id") + private Long brandId; + @JsonProperty("brand_name_chinese") + private String brandNameChinese; + @JsonProperty("brand_name_english") + private String brandNameEnglish; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryClassifyParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryClassifyParam.java new file mode 100644 index 0000000000..b6edd454e5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryClassifyParam.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; + +/** 商品类目推荐请求参数. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductCategoryClassifyParam}。 +*/ +@Data +@Deprecated +public class ProductCategoryClassifyParam implements Serializable { + private static final long serialVersionUID = 4665563979720739777L; + + @JsonProperty("req_type") + private Integer reqType; + private String title; + @JsonProperty("head_imgs") + private List headImgs; + @JsonProperty("cat_id") + private String catId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryClassifyResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryClassifyResponse.java new file mode 100644 index 0000000000..e8eaef31e8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryClassifyResponse.java @@ -0,0 +1,51 @@ +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.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 商品类目推荐响应. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductCategoryClassifyResponse}。 +*/ +@Data +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ProductCategoryClassifyResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 8258747142248203374L; + + private List categories; + @JsonProperty("wrong_cat") + private Boolean wrongCat; + + @Data + public static class CategoryInfo implements Serializable { + private static final long serialVersionUID = -4800760946330901306L; + + private List cats; + } + + @Data + public static class CategoryLevel implements Serializable { + private static final long serialVersionUID = 8010801623725584755L; + + @JsonProperty("cat_info") + private Category catInfo; + @JsonProperty("has_permission") + private Boolean hasPermission; + } + + @Data + public static class Category implements Serializable { + private static final long serialVersionUID = -9013991576741902059L; + + @JsonProperty("cat_id") + private String catId; + @JsonProperty("cat_name") + private String catName; + @JsonProperty("is_shop_no_audit") + private Boolean shopNoAudit; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryPreCheckParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryPreCheckParam.java new file mode 100644 index 0000000000..6e6c19ef1a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryPreCheckParam.java @@ -0,0 +1,17 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** 发品前校验请求参数. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductCategoryPreCheckParam}。 +*/ +@Data +@Deprecated +public class ProductCategoryPreCheckParam implements Serializable { + private static final long serialVersionUID = 5155253060483296766L; + + @JsonProperty("cat_id") + private Long catId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryPreCheckResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryPreCheckResponse.java new file mode 100644 index 0000000000..0f8963462f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryPreCheckResponse.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 发品前校验响应. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductCategoryPreCheckResponse}。 +*/ +@Data +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ProductCategoryPreCheckResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 7136603000806024499L; + + @JsonProperty("all_pass") + private Boolean allPass; + @JsonProperty("fail_reasons") + private List failReasons; +} 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..210104e5a5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductQuaInfo.java @@ -0,0 +1,31 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductQuaInfo}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..ca03c62c37 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSaleLimitInfo.java @@ -0,0 +1,32 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductSaleLimitInfo}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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/ProductSchemeParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSchemeParam.java new file mode 100644 index 0000000000..6f1916c2c1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSchemeParam.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; + +/** 获取商品移动应用跳转 scheme 码请求参数. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductSchemeParam}。 +*/ +@Data +@Deprecated +public class ProductSchemeParam implements Serializable { + private static final long serialVersionUID = 613832623081127830L; + + @JsonProperty("product_id") + private String productId; + @JsonProperty("from_appid") + private String fromAppid; + private Integer expire; + @JsonProperty("ext_info") + private String extInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSchemeResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSchemeResponse.java new file mode 100644 index 0000000000..873f743a68 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSchemeResponse.java @@ -0,0 +1,17 @@ +package me.chanjar.weixin.channel.bean.product; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 获取商品移动应用跳转 scheme 码响应. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductSchemeResponse}。 +*/ +@Data +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ProductSchemeResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 7310433919100539990L; + + private String openlink; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductStockFlowParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductStockFlowParam.java new file mode 100644 index 0000000000..5456a3084f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductStockFlowParam.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** 获取库存流水请求参数. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductStockFlowParam}。 +*/ +@Data +@Deprecated +public class ProductStockFlowParam implements Serializable { + private static final long serialVersionUID = -407227347279113050L; + + @JsonProperty("product_id") + private String productId; + @JsonProperty("sku_id") + private String skuId; + @JsonProperty("stock_type") + private Integer stockType; + @JsonProperty("finder_id") + private String finderId; + @JsonProperty("begin_time") + private Long beginTime; + @JsonProperty("end_time") + private Long endTime; + @JsonProperty("op_type_list") + private List opTypeList; + @JsonProperty("page_size") + private Integer pageSize; + @JsonProperty("next_key") + private String nextKey; + @JsonProperty("stock_type_id") + private String stockTypeId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductStockFlowResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductStockFlowResponse.java new file mode 100644 index 0000000000..36004a614e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductStockFlowResponse.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 获取库存流水响应. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductStockFlowResponse}。 +*/ +@Data +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ProductStockFlowResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 7600529379926896515L; + + private StockFlowData data; + + @Data + public static class StockFlowData implements Serializable { + private static final long serialVersionUID = -4963813730951045381L; + + @JsonProperty("stock_flow_info_list") + private List stockFlowInfoList; + @JsonProperty("next_key") + private String nextKey; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductTimingSaleParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductTimingSaleParam.java new file mode 100644 index 0000000000..c068ef2857 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductTimingSaleParam.java @@ -0,0 +1,19 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** 商品立即开售请求参数. + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.ProductTimingSaleParam}。 +*/ +@Data +@Deprecated +public class ProductTimingSaleParam implements Serializable { + private static final long serialVersionUID = -7185451543781817487L; + + @JsonProperty("product_id") + private String productId; + @JsonProperty("task_id") + private Long taskId; +} 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 index d1f10dc5f8..b61a15cf4e 100644 --- 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 @@ -9,9 +9,11 @@ * sku发货信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SkuDeliverInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class SkuDeliverInfo implements Serializable { private static final long serialVersionUID = 8046963723772755406L; 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..0cdd42f40c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuFastInfo.java @@ -0,0 +1,62 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SkuFastInfo}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +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 index 3b46708039..f5a6d5a4bd 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuInfo.java @@ -10,8 +10,10 @@ * SKU信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SkuInfo}。 */ @Data +@Deprecated public class SkuInfo implements Serializable { private static final long serialVersionUID = -8734396136299597845L; @@ -54,7 +56,11 @@ public class SkuInfo implements Serializable { /** skuID */ @JsonProperty("sku_id") - private Long skuId; + private String skuId; + + /** sku条形码 */ + @JsonProperty("bar_code") + private String barCode; public SkuInfo() { } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchList.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchList.java new file mode 100644 index 0000000000..d7b32dbb0b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchList.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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SkuStockBatchList}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..619fb684ef --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchParam.java @@ -0,0 +1,26 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SkuStockBatchParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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..17758700ad --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SkuStockBatchResponse.java @@ -0,0 +1,26 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SkuStockBatchResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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 index a0dccb1329..188345a669 100644 --- 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 @@ -10,9 +10,11 @@ * 商品库存 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SkuStockInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class SkuStockInfo implements Serializable { private static final long serialVersionUID = 4719729125885685958L; @@ -29,7 +31,14 @@ public class SkuStockInfo implements Serializable { @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 index cf7374e75e..1fec8b0466 100644 --- 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 @@ -8,10 +8,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SkuStockParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class SkuStockParam implements Serializable { private static final long serialVersionUID = -5542939078361208816L; 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 index 9cbd6f18c0..6a18d017e4 100644 --- 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 @@ -1,5 +1,6 @@ package me.chanjar.weixin.channel.bean.product; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -9,14 +10,17 @@ * 库存信息响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SkuStockResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated 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 index 8adc311f95..0b09b17b36 100644 --- 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 @@ -9,9 +9,11 @@ * 商品类目id * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SpuCategory}。 */ @Data @NoArgsConstructor +@Deprecated public class SpuCategory implements Serializable { private static final long serialVersionUID = -8500610555473351789L; 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..c477490fad --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuFastInfo.java @@ -0,0 +1,54 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SpuFastInfo}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +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 index b01682802f..7a417fc537 100644 --- 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 @@ -10,10 +10,12 @@ * 商品信息 响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SpuGetResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class SpuGetResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -8955745006296226140L; @@ -25,4 +27,8 @@ public class SpuGetResponse extends WxChannelBaseResponse { /** 商品草稿数据,入参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 index 7e3834f10e..e39cf91698 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuInfo.java @@ -13,10 +13,12 @@ * Spu信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SpuInfo}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class SpuInfo extends SpuSimpleInfo { private static final long serialVersionUID = -1183209029245287297L; @@ -33,10 +35,18 @@ public class SpuInfo extends SpuSimpleInfo { @JsonProperty("head_imgs") private List headImgs; - /** 发货方式,若为无需快递(仅对部分类目开放),则无需填写运费模版id。0:快递发货;1:无需快递;默认0 */ + /** 发货方式: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; @@ -45,6 +55,10 @@ public class SpuInfo extends SpuSimpleInfo { @JsonProperty("cats") private List cats; + /** 新类目树,商家需要先申请可使用类目 */ + @JsonProperty("cats_v2") + private List catsV2; + /** 商品参数 */ @JsonProperty("attrs") private List attrs; @@ -94,4 +108,53 @@ public class SpuInfo extends SpuSimpleInfo { @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 index 775bdf990d..52dbb363c9 100644 --- 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 @@ -10,9 +10,11 @@ * 商品列表查询参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SpuListParam}。 */ @Data @JsonInclude(Include.NON_NULL) +@Deprecated public class SpuListParam extends StreamPageParam { private static final long serialVersionUID = -242932365961748404L; 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 index 421725c04b..7ee387d62c 100644 --- 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 @@ -11,10 +11,12 @@ * 商品列表信息 响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SpuListResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class SpuListResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -7448819335418389308L; 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 index b1ab3febe7..388f68861f 100644 --- 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 @@ -8,14 +8,16 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SpuSimpleInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class SpuSimpleInfo implements Serializable { private static final long serialVersionUID = 5583726432139404883L; - /** 交易组件平台内部商品ID */ + /** 商品ID */ @JsonProperty("product_id") protected String productId; 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..1af8f68341 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuSizeChart.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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SpuSizeChart}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..230a672e3c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuSizeChartItem.java @@ -0,0 +1,57 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SpuSizeChartItem}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..7215e236da --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuStockInfo.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; + +/** + * SPU库存信息 + * + * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SpuStockInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..cd64813030 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/SpuUpdateInfo.java @@ -0,0 +1,26 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SpuUpdateInfo}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +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 index 815ee4412c..9b65abd91d 100644 --- 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 @@ -10,10 +10,12 @@ * 商品信息 响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.SpuUpdateResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class SpuUpdateResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -7072796795527767292L; 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..042cf028a5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/TimingOnSaleInfo.java @@ -0,0 +1,38 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.TimingOnSaleInfo}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Deprecated +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 index bb239c9492..5621084fde 100644 --- 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 @@ -6,10 +6,14 @@ import lombok.NoArgsConstructor; /** + * 区域库存 + * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.WarehouseStockInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class WarehouseStockInfo implements Serializable { private static final long serialVersionUID = 3184902895765107425L; @@ -21,4 +25,8 @@ public class WarehouseStockInfo implements Serializable { /** 区域库存数量 */ @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/assistant/BeginTimingSaleParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/BeginTimingSaleParam.java new file mode 100644 index 0000000000..782758c6ce --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/BeginTimingSaleParam.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品立即开售参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.assistant.BeginTimingSaleParam}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class BeginTimingSaleParam implements Serializable { + + private static final long serialVersionUID = -1525220756273987016L; + + /** 商品 ID。 */ + @JsonProperty("product_id") + private String productId; + + /** 定时开售任务 ID。 */ + @JsonProperty("task_id") + private String taskId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CancelTimingSaleParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CancelTimingSaleParam.java new file mode 100644 index 0000000000..3a0570f6d2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CancelTimingSaleParam.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 取消商品开售参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.assistant.CancelTimingSaleParam}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class CancelTimingSaleParam implements Serializable { + + private static final long serialVersionUID = -3750831026611057323L; + + /** 商品 ID。 */ + @JsonProperty("product_id") + private String productId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckParam.java new file mode 100644 index 0000000000..40316cf440 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckParam.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 发品前校验参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.assistant.CategoryPreCheckParam}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class CategoryPreCheckParam implements Serializable { + + private static final long serialVersionUID = 3616569394767815856L; + + /** 叶子类目 ID,不传时只校验店铺相关条件。 */ + @JsonProperty("cat_id") + private Long catId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckResponse.java new file mode 100644 index 0000000000..bc50e54add --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckResponse.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +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; + +/** + * 发品前校验响应。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.assistant.CategoryPreCheckResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class CategoryPreCheckResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 8912798390684239592L; + + /** 是否全部校验通过。 */ + @JsonProperty("all_pass") + private Boolean allPass; + + /** 校验不通过的原因。 */ + @JsonProperty("fail_reasons") + private List failReasons; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalAttribute.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalAttribute.java new file mode 100644 index 0000000000..b01cf6e1c6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalAttribute.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品属性键值对。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.assistant.ExternalAttribute}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class ExternalAttribute implements Serializable { + + private static final long serialVersionUID = -8639178782951125101L; + + /** 属性名。 */ + @JsonProperty("key") + private String key; + + /** 属性值。 */ + @JsonProperty("value") + private String value; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewParam.java new file mode 100644 index 0000000000..dc0720187d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewParam.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品属性映射及推荐参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.assistant.ExternalProductMappingNewParam}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class ExternalProductMappingNewParam implements Serializable { + + private static final long serialVersionUID = -4942505655791636645L; + + /** 叶子类目 ID。 */ + @JsonProperty("cat_id") + private Long catId; + + /** 外部商品类目名称。 */ + @JsonProperty("external_category_name") + private String externalCategoryName; + + /** 商品主图,至少一张。 */ + @JsonProperty("head_imgs") + private List headImgs; + + /** 商品详情图。 */ + @JsonProperty("detail_imgs") + private List detailImgs; + + /** 商品标题。 */ + @JsonProperty("title") + private String title; + + /** 外部商品属性列表。 */ + @JsonProperty("external_attributes") + private List externalAttributes; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewResponse.java new file mode 100644 index 0000000000..800babe05a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewResponse.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +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; + +/** + * 商品属性映射及推荐响应。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.assistant.ExternalProductMappingNewResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ExternalProductMappingNewResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -6192580254142696913L; + + /** 映射属性结果。 */ + @JsonProperty("attributes") + private List attributes; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingParam.java new file mode 100644 index 0000000000..50ad9e4649 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingParam.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 站内外商品属性映射参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.assistant.ExternalProductMappingParam}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class ExternalProductMappingParam implements Serializable { + + private static final long serialVersionUID = 1944528166283981889L; + + /** 叶子类目 ID。 */ + @JsonProperty("cat_id") + private Long catId; + + /** 外部商品属性名。 */ + @JsonProperty("external_attribute_name") + private String externalAttributeName; + + /** 外部商品属性值。 */ + @JsonProperty("external_attribute_value") + private String externalAttributeValue; + + /** 外部商品类目名称。 */ + @JsonProperty("external_category_name") + private String externalCategoryName; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingResponse.java new file mode 100644 index 0000000000..6f36b8ed1d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingResponse.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +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; + +/** + * 站内外商品属性映射响应。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.assistant.ExternalProductMappingResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ExternalProductMappingResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -2267639791023044849L; + + /** 外部商品属性名。 */ + @JsonProperty("external_attribute_name") + private String externalAttributeName; + + /** 外部商品属性值。 */ + @JsonProperty("external_attribute_value") + private String externalAttributeValue; + + /** 内部商品属性名。 */ + @JsonProperty("internal_attribute_name") + private String internalAttributeName; + + /** 内部商品属性值。 */ + @JsonProperty("internal_attribute_value") + private List internalAttributeValue; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendParam.java new file mode 100644 index 0000000000..78ed7f7c48 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendParam.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品品牌推荐参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.assistant.ProductBrandRecommendParam}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class ProductBrandRecommendParam implements Serializable { + + private static final long serialVersionUID = 4516219198778673928L; + + /** 叶子类目 ID。 */ + @JsonProperty("cat_id") + private Long catId; + + /** 商品主图,至少一张。 */ + @JsonProperty("head_imgs") + private List headImgs; + + /** 商品详情图。 */ + @JsonProperty("detail_imgs") + private List detailImgs; + + /** 商品标题。 */ + @JsonProperty("title") + private String title; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendResponse.java new file mode 100644 index 0000000000..9fe72c3596 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendResponse.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品品牌推荐响应。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.assistant.ProductBrandRecommendResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ProductBrandRecommendResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -7903894941180639923L; + + /** 品牌 ID。 */ + @JsonProperty("brand_id") + private Long brandId; + + /** 品牌中文名称。 */ + @JsonProperty("brand_name_chinese") + private String brandNameChinese; + + /** 品牌英文名称。 */ + @JsonProperty("brand_name_english") + private String brandNameEnglish; +} 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..b2c1a73993 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/link/ProductH5UrlResponse.java @@ -0,0 +1,24 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.link.ProductH5UrlResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..14964145fb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/link/ProductQrCodeResponse.java @@ -0,0 +1,24 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.link.ProductQrCodeResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..e315468fdc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/link/ProductTagLinkResponse.java @@ -0,0 +1,24 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.link.ProductTagLinkResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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/product/stock/StockFlowExtInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowExtInfo.java new file mode 100644 index 0000000000..7b556ab785 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowExtInfo.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.channel.bean.product.stock; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 库存流水额外信息。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.stock.StockFlowExtInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class StockFlowExtInfo implements Serializable { + + private static final long serialVersionUID = 1170328051641116647L; + + /** 归还的源库存子类型。 */ + @JsonProperty("unmove_from_stock_sub_type") + private Integer unmoveFromStockSubType; + + /** 分配的目标库存子类型。 */ + @JsonProperty("move_to_stock_sub_type") + private Integer moveToStockSubType; + + /** 操作来源。 */ + @JsonProperty("upload_source") + private Integer uploadSource; + + /** 订单 ID。 */ + @JsonProperty("order_id") + private String orderId; + + /** 区域仓库 ID。 */ + @JsonProperty("out_warehouse_id") + private String outWarehouseId; + + /** 限时抢购任务 ID。 */ + @JsonProperty("limited_discount_id") + private String limitedDiscountId; + + /** 达人的视频号 finder_id。 */ + @JsonProperty("finder_id") + private String finderId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowInfo.java new file mode 100644 index 0000000000..bf4ca89a14 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowInfo.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.channel.bean.product.stock; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 库存流水信息。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.stock.StockFlowInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class StockFlowInfo implements Serializable { + + private static final long serialVersionUID = 4094168882102603379L; + + /** 操作数量。 */ + @JsonProperty("amount") + private Integer amount; + + /** 操作前数量。 */ + @JsonProperty("beginning_amount") + private Integer beginningAmount; + + /** 操作后数量。 */ + @JsonProperty("ending_amount") + private Integer endingAmount; + + /** 库存子类型。 */ + @JsonProperty("stock_sub_type") + private Integer stockSubType; + + /** 库存事件类型。 */ + @JsonProperty("op_type") + private Integer opType; + + /** 流水发生时间,秒级时间戳。 */ + @JsonProperty("update_time") + private Long updateTime; + + /** 额外信息。 */ + @JsonProperty("ext_info") + private StockFlowExtInfo extInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowParam.java new file mode 100644 index 0000000000..ec61666cdc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowParam.java @@ -0,0 +1,59 @@ +package me.chanjar.weixin.channel.bean.product.stock; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 获取库存流水请求参数。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.stock.StockFlowParam}。 + */ +@Data +@NoArgsConstructor +@Deprecated +public class StockFlowParam implements Serializable { + + private static final long serialVersionUID = -7882480822919984178L; + + /** 内部商品 ID。 */ + @JsonProperty("product_id") + private String productId; + + /** 内部 SKU ID。 */ + @JsonProperty("sku_id") + private String skuId; + + /** 库存类型。 */ + @JsonProperty("stock_type") + private Integer stockType; + + /** 达人的视频号 finder_id。 */ + @JsonProperty("finder_id") + private String finderId; + + /** 查询开始时间,秒级时间戳。 */ + @JsonProperty("begin_time") + private Long beginTime; + + /** 查询结束时间,秒级时间戳。 */ + @JsonProperty("end_time") + private Long endTime; + + /** 库存事件类型列表。 */ + @JsonProperty("op_type_list") + private List opTypeList; + + /** 每页数量。 */ + @JsonProperty("page_size") + private Integer pageSize; + + /** 上次请求返回的翻页上下文。 */ + @JsonProperty("next_key") + private String nextKey; + + /** 库存类型 ID。 */ + @JsonProperty("stock_type_id") + private String stockTypeId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowResponse.java new file mode 100644 index 0000000000..ba1598eb63 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowResponse.java @@ -0,0 +1,50 @@ +package me.chanjar.weixin.channel.bean.product.stock; + +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; + +/** + * 获取库存流水响应。 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.product.stock.StockFlowResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class StockFlowResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -7420844779570799705L; + + /** 本次翻页的上下文。 */ + private String nextKey; + + /** 库存流水。 */ + private List stockFlowInfoList; + + @JsonProperty("data") + private void unpackData(StockFlowData data) { + if (data == null) { + return; + } + this.nextKey = data.getNextKey(); + this.stockFlowInfoList = data.getStockFlowInfoList(); + } + + @Data + @NoArgsConstructor + private static class StockFlowData implements Serializable { + + private static final long serialVersionUID = -5455751387420196045L; + + @JsonProperty("next_key") + private String nextKey; + + @JsonProperty("stock_flow_info_list") + private List stockFlowInfoList; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/InspectCodeResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/InspectCodeResponse.java new file mode 100644 index 0000000000..f98510d7e4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/InspectCodeResponse.java @@ -0,0 +1,118 @@ +package me.chanjar.weixin.channel.bean.qic; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.io.Serializable; +import java.util.List; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.qic.InspectCodeResponse}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class InspectCodeResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -6242555695898612990L; + + private DataPayload data; + + @Data + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class DataPayload implements Serializable { + private static final long serialVersionUID = 684071509005627272L; + + @JsonProperty("backupDeliveryId") + private String backupDeliveryId; + + @JsonProperty("backupDeliveryName") + private String backupDeliveryName; + + @JsonProperty("boxDTOList") + private List boxInfoList; + + @JsonProperty("channelAppId") + private String channelAppId; + + @JsonProperty("deliveryId") + private String deliveryId; + + @JsonProperty("deliveryName") + private String deliveryName; + + @JsonProperty("embedGoodsMaterial") + private String embedGoodsMaterial; + + @JsonProperty("goodsDesc") + private String goodsDesc; + + @JsonProperty("expressMerge") + private Boolean expressMerge; + + @JsonProperty("goodsMainMaterial") + private String goodsMainMaterial; + + @JsonProperty("goodsName") + private String goodsName; + + @JsonProperty("goodsNum") + private Integer goodsNum; + + @JsonProperty("goodsPartsMaterial") + private String goodsPartsMaterial; + + @JsonProperty("inspectBaseId") + private String inspectBaseId; + + @JsonProperty("inspectBaseName") + private String inspectBaseName; + + @JsonProperty("inspectCode") + private String inspectCode; + + @JsonProperty("inspectOrgId") + private String inspectOrgId; + + @JsonProperty("inspectOrgName") + private String inspectOrgName; + + @JsonProperty("inspectOrgShortName") + private String inspectOrgShortName; + + @JsonProperty("merchantName") + private String merchantName; + + @JsonProperty("orderId") + private String orderId; + + @JsonProperty("urgentOrder") + private Boolean urgentOrder; + + @JsonProperty("printInfo") + private String printInfo; + + @JsonProperty("needLabel") + private Boolean needLabel; + } + + @Data + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class BoxInfo implements Serializable { + private static final long serialVersionUID = 4074623844069371776L; + + @JsonProperty("boxId") + private Long boxId; + + @JsonProperty("boxName") + private String boxName; + + @JsonProperty("boxNum") + private Integer boxNum; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/InspectConfigResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/InspectConfigResponse.java new file mode 100644 index 0000000000..7b5ccb917d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/InspectConfigResponse.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.channel.bean.qic; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.io.Serializable; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.qic.InspectConfigResponse}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class InspectConfigResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 6463651966377955876L; + + @JsonProperty("inspect_config") + private InspectConfig inspectConfig; + + @Data + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class InspectConfig implements Serializable { + private static final long serialVersionUID = 5829846300579243328L; + + @JsonProperty("warehouse_id") + private String warehouseId; + + @JsonProperty("delivery_address") + private Address deliveryAddress; + + @JsonProperty("return_address") + private Address returnAddress; + + @JsonProperty("warehouse_name") + private String warehouseName; + + @JsonProperty("warehouse_addr") + private String warehouseAddr; + } + + @Data + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Address implements Serializable { + private static final long serialVersionUID = -664266740472865991L; + + @JsonProperty("contact_name") + private String contactName; + + @JsonProperty("contact_phone") + private String contactPhone; + + private String province; + + private String city; + + private String county; + + private String detail; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/RegisterLogisticsRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/RegisterLogisticsRequest.java new file mode 100644 index 0000000000..ade0b05b67 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/RegisterLogisticsRequest.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.channel.bean.qic; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.qic.RegisterLogisticsRequest}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class RegisterLogisticsRequest implements Serializable { + private static final long serialVersionUID = 4346443649534209624L; + + @JsonProperty("order_id_list") + private List orderIdList; + + @JsonProperty("logistics_info") + private LogisticsInfo logisticsInfo; + + @Data + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class LogisticsInfo implements Serializable { + private static final long serialVersionUID = 8677143207727485993L; + + @JsonProperty("waybill_id") + private String waybillId; + + @JsonProperty("delivery_id") + private String deliveryId; + + @JsonProperty("delivery_name") + private String deliveryName; + + @JsonProperty("delivery_type") + private Integer deliveryType; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/SubmitConfigResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/SubmitConfigResponse.java new file mode 100644 index 0000000000..5d3706f5d1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/SubmitConfigResponse.java @@ -0,0 +1,102 @@ +package me.chanjar.weixin.channel.bean.qic; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.io.Serializable; +import java.util.List; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.qic.SubmitConfigResponse}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class SubmitConfigResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 2456553692263326158L; + + @JsonProperty("submit_config") + private SubmitConfig submitConfig; + + @Data + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class SubmitConfig implements Serializable { + private static final long serialVersionUID = 3286213539172123945L; + + @JsonProperty("delivery_list") + private List deliveryList; + + @JsonProperty("inspect_org_list") + private List inspectOrgList; + + @JsonProperty("charge_url") + private String chargeUrl; + } + + @Data + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Delivery implements Serializable { + private static final long serialVersionUID = -9209694824619490683L; + + private String id; + + private String name; + + @JsonProperty("delivery_products") + private List deliveryProducts; + } + + @Data + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class DeliveryProduct implements Serializable { + private static final long serialVersionUID = 6527277159948670769L; + + private Long id; + + private String name; + + @JsonProperty("enable_insure") + private Integer enableInsure; + + @JsonProperty("insure_type_list") + private List insureTypeList; + } + + @Data + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class InsureType implements Serializable { + private static final long serialVersionUID = -7788541278375899098L; + + private String id; + + private String name; + + @JsonProperty("upper_limit_type") + private Integer upperLimitType; + + @JsonProperty("upper_limit_amount") + private Long upperLimitAmount; + } + + @Data + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class InspectOrg implements Serializable { + private static final long serialVersionUID = 1723422231048685194L; + + private String id; + + private String name; + + @JsonProperty("org_category") + private Integer orgCategory; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/SubmitInspectRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/SubmitInspectRequest.java new file mode 100644 index 0000000000..90daf16e9d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/qic/SubmitInspectRequest.java @@ -0,0 +1,89 @@ +package me.chanjar.weixin.channel.bean.qic; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.qic.SubmitInspectRequest}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class SubmitInspectRequest implements Serializable { + private static final long serialVersionUID = 6396115469552098613L; + + @JsonProperty("order_id") + private String orderId; + + @JsonProperty("inspect_info") + private InspectInfo inspectInfo; + + @Data + @NoArgsConstructor + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class InspectInfo implements Serializable { + private static final long serialVersionUID = -3502982646296821525L; + + @JsonProperty("delivery_id") + private String deliveryId; + + @JsonProperty("backup_delivery_id") + private String backupDeliveryId; + + @JsonProperty("express_insure") + private Boolean expressInsure; + + @JsonProperty("express_insure_amount") + private Long expressInsureAmount; + + @JsonProperty("express_merge") + private Boolean expressMerge; + + @JsonProperty("inspect_org_id") + private String inspectOrgId; + + @JsonProperty("refund_intercept") + private Integer refundIntercept; + + @JsonProperty("inspect_org_name") + private String inspectOrgName; + + @JsonProperty("warehouse_name") + private String warehouseName; + + @JsonProperty("warehouse_addr") + private String warehouseAddr; + + @JsonProperty("delivery_product_id") + private Long deliveryProductId; + + @JsonProperty("delivery_insure_id") + private String deliveryInsureId; + + @JsonProperty("backup_delivery_product_id") + private Long backupDeliveryProductId; + + @JsonProperty("backup_delivery_insure_id") + private String backupDeliveryInsureId; + + @JsonProperty("backup_express_insure") + private Boolean backupExpressInsure; + + @JsonProperty("backup_express_insure_amount") + private Long backupExpressInsureAmount; + + @JsonProperty("remark") + private String remark; + + @JsonProperty("agarwood_inspect_org_id") + private String agarwoodInspectOrgId; + + @JsonProperty("agarwood_inspect_org_name") + private String agarwoodInspectOrgName; + } +} 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 index 76d54d90c9..f9ad50d455 100644 --- 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 @@ -9,9 +9,11 @@ * 视频号场景信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.sharer.FinderSceneInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class FinderSceneInfo implements Serializable { private static final long serialVersionUID = 5298261857489231549L; 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 index 4a0f8f2bb4..13b3d99117 100644 --- 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 @@ -10,10 +10,12 @@ * 分享员绑定响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.sharer.SharerBindResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class SharerBindResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 7078787380791500161L; 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 index 73aaeddbd4..8754282aba 100644 --- 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 @@ -10,9 +10,11 @@ * 分享员信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.sharer.SharerInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class SharerInfo implements Serializable { private static final long serialVersionUID = -4373597470611742887L; 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 index 554109c1a9..576fc68b23 100644 --- 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 @@ -11,10 +11,12 @@ * 分享员信息响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.sharer.SharerInfoResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class SharerInfoResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 1090517907546557929L; 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 index 97ab2797b8..2175ea377e 100644 --- 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 @@ -9,10 +9,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.sharer.SharerListParam}。 */ @Data @EqualsAndHashCode(callSuper = true) @JsonInclude(Include.NON_NULL) +@Deprecated public class SharerListParam extends PageParam { private static final long serialVersionUID = -2454284952706596246L; 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 index b184187b35..b0f0dbda15 100644 --- 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 @@ -9,30 +9,64 @@ * 分享员订单 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.sharer.SharerOrder}。 */ @Data @NoArgsConstructor +@Deprecated 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("sharer_scene") + /** + * 分享场景 {@link me.chanjar.weixin.channel.enums.ShareScene} + */ + @JsonProperty("share_scene") private Integer sharerScene; - /** 分享员openid */ + /** + * 分享员openid + */ @JsonProperty("sharer_openid") private String sharerOpenid; - /** 分享员类型 {@link me.chanjar.weixin.channel.enums.SharerType} */ + /** + * 分享员类型 {@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 index 191dfc6ec3..0bf4b9a59e 100644 --- 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 @@ -10,17 +10,19 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.sharer.SharerOrderParam}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JsonInclude(Include.NON_NULL) +@Deprecated public class SharerOrderParam extends PageParam { private static final long serialVersionUID = 5240085870008898601L; /** 分享员openid */ @JsonProperty("openid") - private Integer openid; + private String openid; /** 分享场景 */ @JsonProperty("share_scene") 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 index c84da4114b..4443f9c3b6 100644 --- 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 @@ -11,10 +11,12 @@ * 分享员订单响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.sharer.SharerOrderResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class SharerOrderResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 2807417719466178508L; 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 index a2669775cb..776cfa8416 100644 --- 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 @@ -8,9 +8,11 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.sharer.SharerSearchParam}。 */ @Data @JsonInclude(Include.NON_NULL) +@Deprecated public class SharerSearchParam implements Serializable { private static final long serialVersionUID = -6763899740755735718L; 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 index 52631521df..e6ff8f1280 100644 --- 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 @@ -10,10 +10,12 @@ * 分享员绑定响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.sharer.SharerSearchResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class SharerSearchResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -5346019069466917659L; 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 index cd8f21d409..3461a09e8a 100644 --- 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 @@ -11,11 +11,13 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.sharer.SharerUnbindParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(Include.NON_NULL) +@Deprecated public class SharerUnbindParam implements Serializable { private static final long serialVersionUID = -4515654492511136037L; 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 index 9166bc0b58..61b11b2451 100644 --- 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 @@ -11,10 +11,12 @@ * 分享员解绑响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.sharer.SharerUnbindResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class SharerUnbindResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -2395560383862569445L; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopH5UrlResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopH5UrlResponse.java new file mode 100644 index 0000000000..e134b4d35d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopH5UrlResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.shop; + +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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.shop.ShopH5UrlResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ShopH5UrlResponse extends WxChannelBaseResponse { + + /** 店铺H5链接 */ + @JsonProperty("shop_h5url") + private String shopH5url; +} 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 index b2209a4309..c615834402 100644 --- 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 @@ -9,9 +9,11 @@ * 店铺信息 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.shop.ShopInfo}。 */ @Data @NoArgsConstructor +@Deprecated public class ShopInfo implements Serializable { /** 店铺名称 */ @@ -25,4 +27,12 @@ public class ShopInfo implements Serializable { /** 店铺类型,目前为"企业"或"个体工商户" */ @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 index b4317ad3c0..f80c670031 100644 --- 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 @@ -9,9 +9,11 @@ * 店铺基本信息响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.shop.ShopInfoResponse}。 */ @Data @NoArgsConstructor +@Deprecated public class ShopInfoResponse extends WxChannelBaseResponse { @JsonProperty("info") diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopQrCodeResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopQrCodeResponse.java new file mode 100644 index 0000000000..891ff294f7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopQrCodeResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.shop.ShopQrCodeResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ShopQrCodeResponse extends WxChannelBaseResponse { + + /** 店铺二维码链接 */ + @JsonProperty("shop_qrcode") + private String shopQrcode; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopTagLinkResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopTagLinkResponse.java new file mode 100644 index 0000000000..3e97172bb9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/shop/ShopTagLinkResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.shop.ShopTagLinkResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ShopTagLinkResponse extends WxChannelBaseResponse { + + /** 店铺微信口令 */ + @JsonProperty("shop_taglink") + private String shopTaglink; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DistributeTypeResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DistributeTypeResponse.java new file mode 100644 index 0000000000..addfffa6ce --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DistributeTypeResponse.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.supplier.DistributeTypeResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class DistributeTypeResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -750860556286328053L; + + @JsonProperty("distribute_type") + private Integer distributeType; + + @JsonProperty("supplier_info") + private SupplierInfo supplierInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipAssignRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipAssignRequest.java new file mode 100644 index 0000000000..a3446de13b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipAssignRequest.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.supplier; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 代发单分配请求。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.supplier.DropshipAssignRequest}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class DropshipAssignRequest implements Serializable { + private static final long serialVersionUID = 6945436332042017565L; + + @JsonProperty("order_id") + private String orderId; + + @JsonProperty("supplier_id") + private String supplierId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipDetailResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipDetailResponse.java new file mode 100644 index 0000000000..83f2ea9c7f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipDetailResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.supplier.DropshipDetailResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class DropshipDetailResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 5548774863400272707L; + + @JsonProperty("dropship_info") + private DropshipInfo dropshipInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipInfo.java new file mode 100644 index 0000000000..40cff87fa8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipInfo.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.supplier; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 代发单信息。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.supplier.DropshipInfo}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class DropshipInfo implements Serializable { + private static final long serialVersionUID = -7880364210849039278L; + + @JsonProperty("order_id") + private String orderId; + + @JsonProperty("supplier_id") + private String supplierId; + + @JsonProperty("ds_order_id") + private String dropshipId; + + @JsonProperty("status") + private Integer status; + + @JsonProperty("create_time") + private Long createTime; + + @JsonProperty("update_time") + private Long updateTime; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipListRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipListRequest.java new file mode 100644 index 0000000000..a56c31af63 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipListRequest.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.supplier; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 代发单列表请求。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.supplier.DropshipListRequest}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class DropshipListRequest implements Serializable { + private static final long serialVersionUID = 2638071229335192596L; + + @JsonProperty("supplier_id") + private String supplierId; + + @JsonProperty("status") + private Integer status; + + @JsonProperty("create_time_start") + private Long createTimeStart; + + @JsonProperty("create_time_end") + private Long createTimeEnd; + + @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/supplier/DropshipListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipListResponse.java new file mode 100644 index 0000000000..feb2f3b52f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipListResponse.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.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.base.WxChannelBaseResponse; + +/** + * 代发单列表响应。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.supplier.DropshipListResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class DropshipListResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -2850183412032417307L; + + @JsonProperty("dropship_list") + private List dropshipList; + + @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/supplier/DropshipResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipResponse.java new file mode 100644 index 0000000000..21076e6967 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipResponse.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.supplier.DropshipResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class DropshipResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 4376618566823584629L; + + @JsonProperty("order_id") + private String orderId; + + @JsonProperty("supplier_id") + private String supplierId; + + @JsonProperty("dropship_id") + private String dropshipId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipSearchRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipSearchRequest.java new file mode 100644 index 0000000000..e5c8e7b93f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/DropshipSearchRequest.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.supplier; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 代发单搜索请求。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.supplier.DropshipSearchRequest}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class DropshipSearchRequest extends DropshipListRequest { + private static final long serialVersionUID = 3915264648809784742L; + + @JsonProperty("order_id") + private String orderId; + + @JsonProperty("dropship_id") + private String dropshipId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/ProductDistributeRequest.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/ProductDistributeRequest.java new file mode 100644 index 0000000000..cf45096e22 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/ProductDistributeRequest.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.supplier; + +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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.supplier.ProductDistributeRequest}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class ProductDistributeRequest implements Serializable { + private static final long serialVersionUID = 4201609097231290078L; + + @JsonProperty("supplier_id") + private String supplierId; + + @JsonProperty("product_id_list") + private List productIdList; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/ProductListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/ProductListResponse.java new file mode 100644 index 0000000000..6fbd16d974 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/ProductListResponse.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.channel.bean.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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.supplier.ProductListResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class ProductListResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -7096250227033388295L; + + @JsonProperty("product_list") + private List productList; + + @JsonProperty("next_key") + private String nextKey; + + @JsonProperty("has_more") + private Boolean hasMore; + + @Data + @NoArgsConstructor + public static class ProductInfo implements Serializable { + private static final long serialVersionUID = -4482299212575966325L; + + @JsonProperty("product_id") + private String productId; + + @JsonProperty("supplier_id") + private String supplierId; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/SupplierInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/SupplierInfo.java new file mode 100644 index 0000000000..b05701eace --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/SupplierInfo.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.supplier; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 供货商信息。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.supplier.SupplierInfo}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class SupplierInfo implements Serializable { + private static final long serialVersionUID = -6480813119738259476L; + + @JsonProperty("supplier_id") + private String supplierId; + + @JsonProperty("supplier_name") + private String supplierName; + + @JsonProperty("status") + private Integer status; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/SupplierInfoResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/SupplierInfoResponse.java new file mode 100644 index 0000000000..e84cf9e495 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/SupplierInfoResponse.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.supplier.SupplierInfoResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class SupplierInfoResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -3071464065836573893L; + + @JsonProperty("supplier_info") + private SupplierInfo supplierInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/SupplierListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/SupplierListResponse.java new file mode 100644 index 0000000000..a09043ba6f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/supplier/SupplierListResponse.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.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.base.WxChannelBaseResponse; + +/** + * 供货商列表响应。 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.supplier.SupplierListResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class SupplierListResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -692609589633695295L; + + @JsonProperty("supplier_list") + private List supplierList; + + @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/talent/TalentOrderDetailParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentOrderDetailParam.java new file mode 100644 index 0000000000..d75a7eb9d9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentOrderDetailParam.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.talent; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 带货助手-获取佣金单详情 请求参数 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.talent.TalentOrderDetailParam}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class TalentOrderDetailParam implements Serializable { + + private static final long serialVersionUID = 8741285036412736219L; + + /** 订单号,可从获取佣金单列表接口获得 */ + @JsonProperty("order_id") + private String orderId; + + /** 商品skuid,可从获取佣金单列表接口获得 */ + @JsonProperty("sku_id") + private String skuId; + + /** 订单额外参数【在订单列表里面返回的参数回传】 */ + @JsonProperty("special_id") + private String specialId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentOrderDetailResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentOrderDetailResponse.java new file mode 100644 index 0000000000..2aac1293a5 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentOrderDetailResponse.java @@ -0,0 +1,205 @@ +package me.chanjar.weixin.channel.bean.talent; + +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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.talent.TalentOrderDetailResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class TalentOrderDetailResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 2174806923145876312L; + + /** 订单基础信息 */ + @JsonProperty("base_info") + private BaseInfo baseInfo; + + /** 订单佣金信息 */ + @JsonProperty("commission_info") + private CommissionInfo commissionInfo; + + /** 下单通道信息 */ + @JsonProperty("channel_info") + private ChannelInfo channelInfo; + + /** 内容推广推客机构信息 */ + @JsonProperty("promotion_head_supplier_info") + private PromotionHeadSupplierInfo promotionHeadSupplierInfo; + + /** 商品信息 */ + @JsonProperty("product_info") + private ProductInfo productInfo; + + /** 订单基础信息 */ + @Data + @NoArgsConstructor + public static class BaseInfo implements Serializable { + + private static final long serialVersionUID = 6382947162830498251L; + + /** 订单id */ + @JsonProperty("order_id") + private String orderId; + + /** 商品spuid */ + @JsonProperty("spu_id") + private String spuId; + + /** 商品skuid */ + @JsonProperty("sku_id") + private String skuId; + + /** 特殊id【针对本地生活】 */ + @JsonProperty("special_id") + private String specialId; + + /** + * 订单状态:1=待支付, 2=待发货, 3=已发货, 4=已收货, 5=售后中, 6=已完成, 7=已取消, 8=已退款, 9=部分退款, 10=待使用 + */ + @JsonProperty("order_status") + private Integer orderStatus; + + /** 实际支付金额【单位:分】 */ + @JsonProperty("actual_payment") + private String actualPayment; + + /** 订单创建时间 */ + @JsonProperty("order_create_time") + private Long orderCreateTime; + + /** 订单更新时间 */ + @JsonProperty("order_update_time") + private Long orderUpdateTime; + + /** 下单用户 */ + @JsonProperty("buyer_info") + private BuyerInfo buyerInfo; + + /** 订单支付时间 */ + @JsonProperty("order_pay_time") + private Long orderPayTime; + + /** 订单的分佣基数【单位:分】 */ + @JsonProperty("settle_payment") + private String settlePayment; + } + + /** 下单用户信息 */ + @Data + @NoArgsConstructor + public static class BuyerInfo implements Serializable { + + private static final long serialVersionUID = 4729638451027364819L; + + /** 下单用户的openid */ + @JsonProperty("open_id") + private String openId; + + /** 下单用户的unionid */ + @JsonProperty("union_id") + private String unionId; + } + + /** 订单佣金信息 */ + @Data + @NoArgsConstructor + public static class CommissionInfo implements Serializable { + + private static final long serialVersionUID = -3819264037182640581L; + + /** 佣金单状态:1=待结算, 2=已结算, 3=取消结算, 4=结算异常 */ + @JsonProperty("state") + private Integer state; + + /** 佣金比例 */ + @JsonProperty("ratio") + private String ratio; + + /** 预期结算时间 */ + @JsonProperty("expect_settle_time") + private Long expectSettleTime; + + /** 预期结算金额 */ + @JsonProperty("expect_settlement") + private String expectSettlement; + + /** 实际结算时间 */ + @JsonProperty("actual_settle_time") + private Long actualSettleTime; + + /** 实际结算金额 */ + @JsonProperty("actual_settlement") + private String actualSettlement; + } + + /** 下单通道信息 */ + @Data + @NoArgsConstructor + public static class ChannelInfo implements Serializable { + + private static final long serialVersionUID = 7364918204736159023L; + + /** 渠道类型:1=视频号, 2=公众号 */ + @JsonProperty("channel_type") + private Integer channelType; + + /** 渠道id */ + @JsonProperty("channel_id") + private String channelId; + + /** 渠道名称 */ + @JsonProperty("channel_name") + private String channelName; + } + + /** 内容推广推客机构信息 */ + @Data + @NoArgsConstructor + public static class PromotionHeadSupplierInfo implements Serializable { + + private static final long serialVersionUID = 1826374950183647291L; + + /** 机构id */ + @JsonProperty("id") + private String id; + + /** 机构名称 */ + @JsonProperty("name") + private String name; + + /** 佣金单比例 */ + @JsonProperty("ratio") + private String ratio; + + /** 佣金 */ + @JsonProperty("fee") + private String fee; + } + + /** 商品信息 */ + @Data + @NoArgsConstructor + public static class ProductInfo implements Serializable { + + private static final long serialVersionUID = -4920183645872916370L; + + /** 商品的标题 */ + @JsonProperty("title") + private String title; + + /** 商品的头图 */ + @JsonProperty("thumb_img") + private String thumbImg; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentOrderListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentOrderListParam.java new file mode 100644 index 0000000000..db14ea041b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentOrderListParam.java @@ -0,0 +1,54 @@ +package me.chanjar.weixin.channel.bean.talent; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 带货助手-获取佣金单列表 请求参数 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.talent.TalentOrderListParam}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class TalentOrderListParam implements Serializable { + + private static final long serialVersionUID = -6218342185316399261L; + + /** 佣金单创建时间范围之开始时间【和更新时间范围二选一】 */ + @JsonProperty("create_time_gt") + private Long createTimeGt; + + /** 佣金单创建时间范围之结束时间【和更新时间范围二选一】 */ + @JsonProperty("create_time_lt") + private Long createTimeLt; + + /** 订单 ID 过滤 */ + @JsonProperty("order_id") + private String orderId; + + /** 商品 id 过滤 */ + @JsonProperty("spu_id") + private String spuId; + + /** 佣金单更新时间范围之开始时间【和创建时间范围二选一】 */ + @JsonProperty("update_time_gt") + private Long updateTimeGt; + + /** 佣金单更新时间范围之结束时间【和创建时间范围二选一】 */ + @JsonProperty("update_time_lt") + private Long updateTimeLt; + + /** 单页佣金单数(不超过10) */ + @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/talent/TalentOrderListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentOrderListResponse.java new file mode 100644 index 0000000000..e0ba1880ab --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentOrderListResponse.java @@ -0,0 +1,56 @@ +package me.chanjar.weixin.channel.bean.talent; + +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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.talent.TalentOrderListResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class TalentOrderListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 3541802319654186172L; + + /** 佣金单信息列表 */ + @JsonProperty("order_list") + private List orderList; + + /** 是否还有剩余订单 */ + @JsonProperty("has_more") + private Boolean hasMore; + + /** 本次翻页的上下文,用于顺序翻页请求 */ + @JsonProperty("next_key") + private String nextKey; + + /** 佣金单基础信息 */ + @Data + @NoArgsConstructor + public static class OrderInfo implements Serializable { + + private static final long serialVersionUID = 5261736494628827543L; + + /** 订单id */ + @JsonProperty("order_id") + private String orderId; + + /** skuid */ + @JsonProperty("sku_id") + private String skuId; + + /** 佣金单特殊标识【目前主要用于本地生活】 */ + @JsonProperty("special_id") + private String specialId; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentWindowProductDetailParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentWindowProductDetailParam.java new file mode 100644 index 0000000000..3ff6575e8f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentWindowProductDetailParam.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.talent; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 带货助手-获取达人橱窗商品详情 请求参数 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.talent.TalentWindowProductDetailParam}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class TalentWindowProductDetailParam implements Serializable { + + private static final long serialVersionUID = 3849271605183749261L; + + /** 橱窗商品ID(可以从"获取达人橱窗商品列表"接口获取) */ + @JsonProperty("product_id") + private String productId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentWindowProductDetailResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentWindowProductDetailResponse.java new file mode 100644 index 0000000000..3f7802ff2c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentWindowProductDetailResponse.java @@ -0,0 +1,83 @@ +package me.chanjar.weixin.channel.bean.talent; + +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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.talent.TalentWindowProductDetailResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class TalentWindowProductDetailResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 1634829710537264918L; + + /** 橱窗商品详情 */ + @JsonProperty("product") + private ProductDetail product; + + /** 橱窗商品详情 */ + @Data + @NoArgsConstructor + public static class ProductDetail implements Serializable { + + private static final long serialVersionUID = 7283640192847516039L; + + /** 橱窗商品ID */ + @JsonProperty("product_id") + private String productId; + + /** 商品来源店铺的appid */ + @JsonProperty("appid") + private String appid; + + /** 商品在货源店铺的商品ID,对于带货商品会返回 */ + @JsonProperty("out_product_id") + private String outProductId; + + /** 商品标题 */ + @JsonProperty("title") + private String title; + + /** 商品头图url */ + @JsonProperty("img_url") + private String imgUrl; + + /** 商品所属叶子类目(品类)ID */ + @JsonProperty("leaf_category_id") + private Long leafCategoryId; + + /** 商品状态:1=生效中,2=被禁止售卖 */ + @JsonProperty("status") + private Integer status; + + /** 价格区间最小值(单位分,销售价) */ + @JsonProperty("selling_price") + private Long sellingPrice; + + /** 剩余库存 */ + @JsonProperty("stock") + private Long stock; + + /** 销量 */ + @JsonProperty("sales") + private Long sales; + + /** 是否在橱窗设置对外隐藏 */ + @JsonProperty("is_hide") + private Boolean isHide; + + /** 用于在小程序跳转小店场景添加商品时传递跟佣信息 */ + @JsonProperty("product_promotion_link") + private String productPromotionLink; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentWindowProductListParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentWindowProductListParam.java new file mode 100644 index 0000000000..cfd2c87b30 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentWindowProductListParam.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.channel.bean.talent; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 带货助手-获取达人橱窗商品列表 请求参数 + * + * @author GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.talent.TalentWindowProductListParam}。 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +public class TalentWindowProductListParam implements Serializable { + + private static final long serialVersionUID = 7419836250174638291L; + + /** 单页商品数(不超过500) */ + @JsonProperty("page_size") + private Integer pageSize; + + /** 页面下标,下标从1开始(不可以与 lastBuffer 一起填写) */ + @JsonProperty("page_index") + private Integer pageIndex; + + /** 由上次请求返回,顺序翻页时需要传入(不可以与 pageIndex 一起填写) */ + @JsonProperty("last_buffer") + private String lastBuffer; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentWindowProductListResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentWindowProductListResponse.java new file mode 100644 index 0000000000..5eb0c48f3b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/talent/TalentWindowProductListResponse.java @@ -0,0 +1,60 @@ +package me.chanjar.weixin.channel.bean.talent; + +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 GitHub Copilot + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.talent.TalentWindowProductListResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +public class TalentWindowProductListResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 8263047195826340712L; + + /** 橱窗商品列表 */ + @JsonProperty("products") + private List products; + + /** 本次翻页的上下文,用于顺序翻页请求 */ + @JsonProperty("last_buffer") + private String lastBuffer; + + /** 橱窗商品基础信息 */ + @Data + @NoArgsConstructor + public static class ProductInfo implements Serializable { + + private static final long serialVersionUID = 6142837490516284039L; + + /** 橱窗商品id */ + @JsonProperty("product_id") + private String productId; + + /** 对于自营商品会返回,代表商品来源店铺的appid */ + @JsonProperty("appid") + private String appid; + + /** + * 商品来源:1=来源店铺的自营商品,2=来源选品中心的带货商品 + */ + @JsonProperty("product_source") + private Integer productSource; + + /** + * 对于带货商品会返回,代表商品在货源小店中的商品id + */ + @JsonProperty("out_product_id") + private String outProductId; + } +} 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..ef29b9e0d9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/token/StableTokenParam.java @@ -0,0 +1,36 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.token.StableTokenParam}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +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..016eee01cd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/ScoreInfo.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 java.io.Serializable; + +/** + * 视频号小店-会员功能 - 订单详情 + * + * @author asushiye + * + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.vip.ScoreInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..7054795645 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/UserGradeInfo.java @@ -0,0 +1,29 @@ +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 + * + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.vip.UserGradeInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..39a7f56157 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/UserInfo.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 java.io.Serializable; + +/** + * 视频号小店-会员功能 - 订单详情 + * + * @author asushiye + * + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.vip.UserInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..178ad35c20 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipGradeParam.java @@ -0,0 +1,33 @@ +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 + + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.vip.VipGradeParam}。 + */ + +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +@Deprecated +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..05e7156f05 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipInfo.java @@ -0,0 +1,49 @@ +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" + * } + * } + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.vip.VipInfo}。 + */ +@Data +@NoArgsConstructor +@Deprecated +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..bc36c52522 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipInfoParam.java @@ -0,0 +1,29 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.vip.VipInfoParam}。 + */ + +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +@Deprecated +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..921cb0bf77 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipInfoResponse.java @@ -0,0 +1,22 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.vip.VipInfoResponse}。 + */ + +@Data +@NoArgsConstructor +@Deprecated +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..246451355b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipListParam.java @@ -0,0 +1,34 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.vip.VipListParam}。 + */ + +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +@Deprecated +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..a1ef9a0401 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipListResponse.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 me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +import java.util.List; + +/** + * @author : zhenyun.su + * @since : 2023/10/8 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.vip.VipListResponse}。 + */ + +@Data +@NoArgsConstructor +@Deprecated +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..b83b6424db --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipOpenIdParam.java @@ -0,0 +1,26 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.vip.VipOpenIdParam}。 + */ + +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +@Deprecated +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..31ce81d3a6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipScoreParam.java @@ -0,0 +1,41 @@ +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" + * } + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.vip.VipScoreParam}。 + */ + +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +@Deprecated +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..feb1a5e7eb --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/vip/VipScoreResponse.java @@ -0,0 +1,22 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.vip.VipScoreResponse}。 + */ + +@Data +@NoArgsConstructor +@Deprecated +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 index 5959cb746d..c880f95d6e 100644 --- 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 @@ -11,10 +11,12 @@ * 仓库优先级响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.warehouse.LocationPriorityResponse}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class LocationPriorityResponse extends WxChannelBaseResponse { private static final long serialVersionUID = -4037484169497319150L; 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 index 0b304487a7..8b10224743 100644 --- 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 @@ -10,10 +10,12 @@ * 带优先级的仓库区域 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.warehouse.PriorityLocationParam}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) +@Deprecated public class PriorityLocationParam extends WarehouseLocation { private static final long serialVersionUID = -3087702364669180903L; 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 index 99e00a4801..992f69b1d5 100644 --- 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 @@ -8,10 +8,12 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.warehouse.StockGetParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class StockGetParam implements Serializable { private static final long serialVersionUID = -4144913434092446664L; 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 index 5b71c0a4b4..acb803886f 100644 --- 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 @@ -11,10 +11,12 @@ * 仓库区域 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.warehouse.UpdateLocationParam}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class UpdateLocationParam implements Serializable { private static final long serialVersionUID = 6102771485047925091L; 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 index 7ca07e637f..966c34bbc4 100644 --- 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 @@ -10,9 +10,11 @@ * 仓库 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.warehouse.Warehouse}。 */ @Data @NoArgsConstructor +@Deprecated public class Warehouse implements Serializable { private static final long serialVersionUID = -2322154583471063637L; 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 index 57c989a56b..8f0b7c11bc 100644 --- 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 @@ -11,9 +11,11 @@ * 仓库id列表响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.warehouse.WarehouseIdsResponse}。 */ @Data @EqualsAndHashCode(callSuper = true) +@Deprecated public class WarehouseIdsResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 3974529583232187473L; 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 index 33309522bb..e7a3fe4a73 100644 --- 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 @@ -10,10 +10,12 @@ * 仓库区域 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.warehouse.WarehouseLocation}。 */ @Data @NoArgsConstructor @AllArgsConstructor +@Deprecated public class WarehouseLocation implements Serializable { private static final long serialVersionUID = 1626579682640060352L; 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 index 2b64e55dea..7041c7117b 100644 --- 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 @@ -6,9 +6,11 @@ /** * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.warehouse.WarehouseLocationParam}。 */ @Data @JsonInclude(Include.NON_NULL) +@Deprecated public class WarehouseLocationParam extends WarehouseLocation { private static final long serialVersionUID = 3347484433136057123L; 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 index 77ac1a8134..945d0ae22f 100644 --- 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 @@ -9,11 +9,13 @@ * 仓库 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.warehouse.WarehouseParam}。 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated 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 index fa96771d67..d1fefff56a 100644 --- 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 @@ -9,9 +9,11 @@ * 仓库响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.warehouse.WarehouseResponse}。 */ @Data @NoArgsConstructor +@Deprecated public class WarehouseResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 3206095869486573824L; 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 index 5a4354504d..27ecec523c 100644 --- 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 @@ -9,9 +9,11 @@ * 库存参数 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.warehouse.WarehouseStockParam}。 */ @Data @NoArgsConstructor +@Deprecated public class WarehouseStockParam extends SkuStockParam { private static final long serialVersionUID = -5121207621628542490L; 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 index 64d0d2b5b0..28de28565d 100644 --- 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 @@ -9,8 +9,10 @@ * 仓库库存响应 * * @author Zeyes + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.warehouse.WarehouseStockResponse}。 */ @Data +@Deprecated public class WarehouseStockResponse extends WxChannelBaseResponse { private static final long serialVersionUID = 1810645965041317763L; 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..f877b52aed --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/request/AddWindowProductRequest.java @@ -0,0 +1,41 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.window.request.AddWindowProductRequest}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +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..8ecd3cbe9b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/request/GetWindowProductListRequest.java @@ -0,0 +1,59 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.window.request.GetWindowProductListRequest}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +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..d248d117d3 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/request/WindowProductRequest.java @@ -0,0 +1,34 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.window.request.WindowProductRequest}。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Deprecated +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..f0609f1d77 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/response/GetWindowProductListResponse.java @@ -0,0 +1,57 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.window.response.GetWindowProductListResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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..d33715e3d1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/window/response/GetWindowProductResponse.java @@ -0,0 +1,240 @@ +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 + * @deprecated 请迁移至 {@link com.binarywang.wxjava.store.bean.window.response.GetWindowProductResponse}。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Deprecated +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 index eb8cda4996..705eacc898 100644 --- 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 @@ -7,7 +7,9 @@ * 微信视频号错误码 * * @author Zeyes + * @deprecated 请使用 {@link me.chanjar.weixin.common.error.WxError} 替代 */ +@Deprecated public class ChannelWxError extends WxError { private static final long serialVersionUID = -2638512715814977441L; 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 index ad24234fb0..64344dae58 100644 --- 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 @@ -19,6 +19,14 @@ public interface WxChannelConfig { */ String getAccessToken(); + /** + * Is use stable access token api + * + * @link 获取稳定版AccessToken + * @return the boolean + */ + boolean isStableAccessToken(); + /** * Gets access token lock. * 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 index e32bcad83e..1c3930caf5 100644 --- 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 @@ -33,6 +33,8 @@ public class WxChannelDefaultConfigImpl implements WxChannelConfig { 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; @@ -63,6 +65,15 @@ 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; 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 index e0e419efc6..675b5d8a57 100644 --- 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 @@ -16,12 +16,16 @@ public interface MessageEventConstants { 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"; /** 订单确认收货 */ @@ -55,6 +59,8 @@ public interface MessageEventConstants { 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"; @@ -67,4 +73,26 @@ public interface MessageEventConstants { // 其他 /** 进入会话事件 */ 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 index 03047dd3bd..105879dae2 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java @@ -16,28 +16,55 @@ public class WxChannelApiUrlConstants { 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 IMG_UPLOAD_URL = "https://api.weixin.qq.com/shop/ec/basics/img/upload"; /** 上传资质图片 */ - String UPLOAD_QUALIFICATION_FILE = "https://api.weixin.qq.com/channels/ec/basics/qualification/upload"; + String UPLOAD_QUALIFICATION_FILE = "https://api.weixin.qq.com/shop/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"; + /** 获取店铺H5链接 */ + String GET_SHOP_H5URL = "https://api.weixin.qq.com/channels/ec/basics/shop/h5url/get"; + /** 获取店铺二维码 */ + String GET_SHOP_QRCODE = "https://api.weixin.qq.com/channels/ec/basics/shop/qrcode/get"; + /** 获取店铺口令 */ + String GET_SHOP_TAGLINK = "https://api.weixin.qq.com/channels/ec/basics/shop/taglink/get"; + } + + /** 收藏管理相关接口 */ + public interface Favorite { + + /** 获取店铺收藏的人数 */ + String GET_FAVORITE_COUNT = "https://api.weixin.qq.com/channels/ec/favorites/count/get"; + } + + /** 商家客服相关接口 */ + public interface Kf { + + /** 上传客服素材 */ + String COS_UPLOAD_URL = "https://api.weixin.qq.com/channels/ec/commkf/cosupload"; + /** 发送客服消息 */ + String SEND_MSG_URL = "https://api.weixin.qq.com/channels/ec/commkf/sendmsg"; } /** 商品类目相关接口 */ public interface Category { /** 获取所有的类目 */ - String LIST_ALL_CATEGORY_URL = "https://api.weixin.qq.com/channels/ec/category/all"; + String LIST_ALL_CATEGORY_URL = "https://api.weixin.qq.com/shop/ec/category/all"; /** 获取类目详情 */ - String GET_CATEGORY_DETAIL_URL = "https://api.weixin.qq.com/channels/ec/category/detail"; + String GET_CATEGORY_DETAIL_URL = "https://api.weixin.qq.com/shop/ec/category/detail"; /** 获取可用的子类目详情 */ String AVAILABLE_CATEGORY_URL = "https://api.weixin.qq.com/channels/ec/category/availablesoncategories/get"; /** 上传类目资质 */ @@ -45,22 +72,66 @@ public interface Category { /** 获取类目审核结果 */ 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 CANCEL_CATEGORY_AUDIT_URL = "https://api.weixin.qq.com/shop/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 ALL_BRAND_URL = "https://api.weixin.qq.com/shop/ec/brand/all"; /** 新增品牌资质 */ - String ADD_BRAND_URL = "https://api.weixin.qq.com/channels/ec/brand/add"; + String ADD_BRAND_URL = "https://api.weixin.qq.com/shop/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 CANCEL_BRAND_AUDIT_URL = "https://api.weixin.qq.com/shop/ec/brand/audit/cancel"; /** 删除品牌资质 */ String DELETE_BRAND_URL = "https://api.weixin.qq.com/channels/ec/brand/delete"; /** 获取品牌资质申请详情 */ @@ -84,16 +155,70 @@ public interface Spu { 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"; + /** 获取商品移动应用跳转 scheme 码 */ + String SPU_SCHEME_URL = "https://api.weixin.qq.com/channels/ec/product/scheme/get"; + /** 获取商品口令 */ + String SPU_TAGLINK_URL = "https://api.weixin.qq.com/channels/ec/product/taglink/get"; + /** 商品类目推荐 */ + String SPU_CATEGORY_CLASSIFY_URL = "https://api.weixin.qq.com/channels/ec/product/category/classify"; + /** 商品立即开售 */ + String SPU_BEGIN_TIMING_SALE_URL = "https://api.weixin.qq.com/channels/ec/product/begintimingsale"; + /** 取消商品开售 */ + String SPU_CANCEL_TIMING_SALE_URL = "https://api.weixin.qq.com/channels/ec/product/canceltimingsale"; + /** 站内外商品属性映射 */ + String SPU_EXTERNAL_PRODUCT_MAPPING_URL = "https://api.weixin.qq.com/channels/ec/product/externalproductmapping"; + /** 发品前校验 */ + String SPU_CATEGORY_PRE_CHECK_URL = "https://api.weixin.qq.com/channels/ec/product/categoryprecheck"; + /** 获取商品上架策略 */ + String SPU_AUDIT_STRATEGY_GET_URL = "https://api.weixin.qq.com/channels/ec/product/auditstrategy/get"; + /** 设置商品上架策略 */ + String SPU_AUDIT_STRATEGY_SET_URL = "https://api.weixin.qq.com/channels/ec/product/auditstrategy/set"; + /** 获取商品提审限额 */ + String SPU_GET_AUDIT_QUOTA_URL = "https://api.weixin.qq.com/channels/ec/product/getauditquota"; + /** 商品属性映射及推荐 */ + String SPU_EXTERNAL_PRODUCT_MAPPING_NEW_URL = "https://api.weixin.qq.com/channels/ec/product/externalproductmappingnew"; + /** 商品品牌推荐 */ + String SPU_PRODUCT_BRAND_RECOMMEND_URL = "https://api.weixin.qq.com/channels/ec/product/productbrandrecommend"; + /** 新增第三方货源信息 */ + String SPU_ADD_PRODUCT_THIRD_PARTY_SOURCE_URL = "https://api.weixin.qq.com/channels/ec/product/addproductthirdpartysource"; /** 获取实时库存 */ String SPU_GET_STOCK_URL = "https://api.weixin.qq.com/channels/ec/product/stock/get"; + /** 获取库存流水 */ + String SPU_GET_STOCK_FLOW_URL = "https://api.weixin.qq.com/channels/ec/product/stock/getflow"; + /** 获取实时库存 */ + 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 GIFT_PRODUCT_ADD_URL = "https://api.weixin.qq.com/channels/ec/product/gift/add"; + /** 更新非卖商品 */ + String GIFT_PRODUCT_UPDATE_URL = "https://api.weixin.qq.com/channels/ec/product/gift/update"; + /** 在售商品转赠品 */ + String GIFT_PRODUCT_ON_SALE_SET_URL = "https://api.weixin.qq.com/channels/ec/product/gift/onsale/set"; + /** 获取赠品 */ + String GIFT_PRODUCT_GET_URL = "https://api.weixin.qq.com/channels/ec/product/gift/get"; + /** 获取赠品列表 */ + String GIFT_PRODUCT_LIST_URL = "https://api.weixin.qq.com/channels/ec/product/gift/list/get"; + /** 更新赠品库存 */ + String GIFT_PRODUCT_STOCK_UPDATE_URL = "https://api.weixin.qq.com/channels/ec/product/gift/stock/update"; + /** 创建赠品活动 */ + String GIFT_ACTIVITY_ADD_URL = "https://api.weixin.qq.com/channels/ec/product/activity/add"; + /** 删除赠品活动 */ + String GIFT_ACTIVITY_DELETE_URL = "https://api.weixin.qq.com/channels/ec/product/activity/del"; + /** 停止赠品活动 */ + String GIFT_ACTIVITY_STOP_URL = "https://api.weixin.qq.com/channels/ec/product/activity/stop"; /** 添加限时抢购任务 */ String ADD_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/add"; /** 拉取限时抢购任务列表 */ @@ -102,6 +227,21 @@ public interface Spu { 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"; + /** 更新限时抢购任务 */ + String UPDATE_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/update"; + /** 发品前校验 */ + String CATEGORY_PRE_CHECK_URL = "https://api.weixin.qq.com/channels/ec/product/categoryprecheck"; + /** 商品品牌推荐 */ + String PRODUCT_BRAND_RECOMMEND_URL = "https://api.weixin.qq.com/channels/ec/product/productbrandrecommend"; + /** 站内外商品属性映射 */ + String EXTERNAL_PRODUCT_MAPPING_URL = "https://api.weixin.qq.com/channels/ec/product/externalproductmapping"; + /** 商品属性映射及推荐 */ + String EXTERNAL_PRODUCT_MAPPING_NEW_URL = + "https://api.weixin.qq.com/channels/ec/product/externalproductmappingnew"; + /** 商品立即开售 */ + String BEGIN_TIMING_SALE_URL = "https://api.weixin.qq.com/channels/ec/product/begintimingsale"; + /** 取消商品开售 */ + String CANCEL_TIMING_SALE_URL = "https://api.weixin.qq.com/channels/ec/product/canceltimingsale"; } /** 区域仓库 */ @@ -144,12 +284,62 @@ public interface Order { 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"; + /** 礼物订单新增备注信息 */ + String PRESENT_NOTE_ADD_URL = "https://api.weixin.qq.com/channels/ec/order/presentnote/add"; + /** 获取礼物单的子单列表 */ + String PRESENT_SUB_ORDER_GET_URL = "https://api.weixin.qq.com/channels/ec/order/presentsuborder/get"; + /** 获取待发货前更换sku待处理请求 */ + String PRE_SHIPMENT_CHANGE_SKU_GET_URL = "https://api.weixin.qq.com/channels/ec/order/preshipmentchangesku/get"; + /** 同意待发货前更换sku请求 */ + String PRE_SHIPMENT_CHANGE_SKU_APPROVE_URL = "https://api.weixin.qq.com/channels/ec/order/preshipmentchangesku/approve"; + /** 拒绝待发货前更换sku请求 */ + String PRE_SHIPMENT_CHANGE_SKU_REJECT_URL = "https://api.weixin.qq.com/channels/ec/order/preshipmentchangesku/reject"; + /** 申请查看订单真实号码 */ + String REAL_NUMBER_APPLY_URL = "https://api.weixin.qq.com/channels/ec/order/realnumber/apply"; + /** 查看订单真实号审核状态 */ + String REAL_NUMBER_VIEW_AUDIT_GET_URL = "https://api.weixin.qq.com/channels/ec/order/realnumberviewaudit/get"; + /** 订单再次申请虚拟号 */ + String VIRTUAL_NUMBER_APPLY_AGAIN_URL = "https://api.weixin.qq.com/channels/ec/order/virtualnumber/applyagain"; + /** 订单虚拟号延期 */ + String VIRTUAL_NUMBER_DELAY_URL = "https://api.weixin.qq.com/channels/ec/order/virtualnumber/delay"; + /** 订单补发货 */ + String DELIVERY_COMPENSATION_URL = "https://api.weixin.qq.com/channels/ec/order/delivery/compensation"; + } + + /** 虚拟号管理相关接口 */ + public interface PrivateNumber { + + /** 添加待认证的手机号 */ + String ADD_PHONE_URL = "https://api.weixin.qq.com/channels/ec/merchant/privatenumber/addphone"; + /** 获取短信验证码 */ + String SEND_VERIFY_CODE_URL = "https://api.weixin.qq.com/channels/ec/merchant/privatenumber/sendverifycode"; + /** 获取小店手机号认证状态 */ + String GET_PHONE_URL = "https://api.weixin.qq.com/channels/ec/merchant/privatenumber/getphone"; } /** 售后相关接口 */ public interface AfterSale { + String AFTER_SALE_GEN_AFTER_SALE_ORDER_URL = "https://api.weixin.qq.com/channels/ec/aftersale/genaftersaleorder"; + String AFTER_SALE_REFUND_PRICE_DIFF_URL = "https://api.weixin.qq.com/channels/ec/aftersale/refundpricediff"; + String AFTER_SALE_APPLY_VIRTUAL_TEL_NUM_URL = "https://api.weixin.qq.com/channels/ec/aftersale/applyvirtualtelnum"; + String AFTER_SALE_HANDLE_FAST_EXCHANGE_RECEIPT_URL = "https://api.weixin.qq.com/channels/ec/aftersale/handlefastexchangereceipt"; + String AFTER_SALE_GET_GUARANTEE_ORDER_URL = "https://api.weixin.qq.com/channels/ec/aftersale/getguaranteeorder"; + String AFTER_SALE_MERCHANT_ACCEPT_GUARANTEE_URL = "https://api.weixin.qq.com/channels/ec/aftersale/merchantacceptguarantee"; + String AFTER_SALE_MERCHANT_MODIFY_GUARANTEE_URL = "https://api.weixin.qq.com/channels/ec/aftersale/merchantmodifyguarantee"; + String AFTER_SALE_MERCHANT_PROOF_GUARANTEE_URL = "https://api.weixin.qq.com/channels/ec/aftersale/merchantproofguarantee"; + String AFTER_SALE_SYNC_WORK_ORDER_URL = "https://api.weixin.qq.com/channels/ec/aftersale/syncworkorder"; /** 获取售后列表 */ String AFTER_SALE_LIST_URL = "https://api.weixin.qq.com/channels/ec/aftersale/getaftersalelist"; @@ -161,6 +351,28 @@ public interface AfterSale { 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"; + /** 获取保障单列表 */ + String GUARANTEE_ORDER_LIST_URL = "https://api.weixin.qq.com/channels/ec/aftersale/searchguaranteeorder"; + /** 获取保障单详情 */ + String GUARANTEE_ORDER_GET_URL = "https://api.weixin.qq.com/channels/ec/aftersale/getguaranteeorder"; + /** 同意保障单申请 */ + String GUARANTEE_ORDER_ACCEPT_URL = "https://api.weixin.qq.com/channels/ec/aftersale/merchantacceptguarantee"; + /** 商家协商保障单 */ + String GUARANTEE_ORDER_MODIFY_URL = "https://api.weixin.qq.com/channels/ec/aftersale/merchantmodifyguarantee"; + /** 商家举证保障单 */ + String GUARANTEE_ORDER_PROOF_URL = "https://api.weixin.qq.com/channels/ec/aftersale/merchantproofguarantee"; + /** 拒绝保障单申请 */ + String GUARANTEE_ORDER_REFUSE_URL = "https://api.weixin.qq.com/channels/ec/aftersale/merchantrefuseguarantee"; } /** 纠纷相关接口 */ @@ -176,13 +388,50 @@ public interface Complaint { /** 物流相关接口 */ public interface Delivery { - /** 获取快递公司列表 */ + String GET_DELIVERY_COMPANY_NEW_URL = "https://api.weixin.qq.com/channels/ec/order/deliverycompanylist/new/get"; + /** 获取快递公司列表(旧) */ String GET_DELIVERY_COMPANY_URL = "https://api.weixin.qq.com/channels/ec/order/deliverycompanylist/get"; /** 订单发货 */ String DELIVERY_SEND_URL = "https://api.weixin.qq.com/channels/ec/order/delivery/send"; } + /** 电子面单相关接口 */ + public interface Ewaybill { + /** 获取面单标准模板 */ + String GET_TEMPLATE_CONFIG_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/template/config"; + /** 新增面单模板 */ + String CREATE_TEMPLATE_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/template/create"; + /** 删除面单模板 */ + String DELETE_TEMPLATE_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/template/delete"; + /** 更新面单模板 */ + String UPDATE_TEMPLATE_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/template/update"; + /** 获取面单模板信息 */ + String GET_TEMPLATE_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/template/get"; + /** 根据模板ID获取面单模板信息 */ + String GET_TEMPLATE_BY_ID_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/template/getbyid"; + /** 查询开通的电子面单网点/账号信息 */ + String GET_ACCOUNT_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/account/get"; + /** 查询开通的快递公司列表 */ + String GET_DELIVERY_LIST_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/delivery/get"; + /** 电子面单预取号 */ + String PRE_CREATE_ORDER_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/order/precreate"; + /** 电子面单取号 */ + String CREATE_ORDER_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/order/create"; + /** 电子面单子件追加 */ + String ADD_SUB_ORDER_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/order/addsuborder"; + /** 电子面单取消下单 */ + String CANCEL_ORDER_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/order/cancel"; + /** 查询面单详情 */ + String GET_ORDER_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/order/get"; + /** 获取打印报文 */ + String GET_PRINT_CONTENT_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/print/get"; + /** 打印成功通知 */ + String PRINT_ORDER_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/order/print"; + /** 批量打印通知 */ + String BATCH_PRINT_ORDER_URL = "https://api.weixin.qq.com/channels/ec/logistics/ewaybill/biz/order/batchprint"; + } + /** 运费模板相关接口 */ public interface FreightTemplate { @@ -245,6 +494,20 @@ public interface Share { 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 { @@ -253,9 +516,9 @@ public interface Fund { /** 获取结算账户 */ 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/league/funds/getfundsflowdetail"; + 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/league/funds/getfundsflowlist"; + 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"; /** 获取提现记录列表 */ @@ -335,6 +598,34 @@ public interface League { String GET_LEAGUE_ITEM_LIST_URL = "https://api.weixin.qq.com/channels/ec/league/item/list/get"; } + /** 代发管理相关接口 */ + public interface Supplier { + /** 获取供货商列表 */ + String GET_SUPPLIER_LIST_URL = "https://api.weixin.qq.com/channels/ec/supplier/relation/get_supplier_list"; + /** 获取分配方式 */ + String GET_DISTRIBUTE_URL = "https://api.weixin.qq.com/channels/ec/supplier/relation/get_distribute"; + /** 设置全店订单手动分配 */ + String SET_MANUALLY_DISTRIBUTE_URL = "https://api.weixin.qq.com/channels/ec/supplier/relation/set_manually_distribute"; + /** 设置全店订单自动分配 */ + String SET_ALL_DISTRIBUTION_URL = "https://api.weixin.qq.com/channels/ec/supplier/relation/set_all_distribution"; + /** 设置按商品自动分配 */ + String SET_PRODUCT_DISTRIBUTE_URL = "https://api.weixin.qq.com/channels/ec/supplier/relation/set_product_distribute"; + /** 获取商品对应的自动分配供货商 */ + String GET_PRODUCT_DEFAULT_DISTRIBUTE_URL = "https://api.weixin.qq.com/channels/ec/supplier/relation/get_product_default_distribute"; + /** 获取按商品自动分配的商品列表 */ + String GET_PRODUCT_LIST_URL = "https://api.weixin.qq.com/channels/ec/supplier/relation/get_product_list"; + /** 分配订单代发 */ + String ASSIGN_DROPSHIP_URL = "https://api.weixin.qq.com/channels/ec/order/dropship/assign"; + /** 取消分配代发单 */ + String CANCEL_DROPSHIP_URL = "https://api.weixin.qq.com/channels/ec/order/dropship/cancel"; + /** 查询代发单详情 */ + String GET_DROPSHIP_URL = "https://api.weixin.qq.com/channels/ec/order/dropship/get"; + /** 拉取代发单列表 */ + String GET_DROPSHIP_LIST_URL = "https://api.weixin.qq.com/channels/ec/order/dropship/list"; + /** 搜索代发单 */ + String SEARCH_DROPSHIP_URL = "https://api.weixin.qq.com/channels/ec/order/dropship/search"; + } + /** 视频号助手开放接口 */ public interface Assistant { @@ -347,4 +638,179 @@ public interface Assistant { /** 下架橱窗商品 */ 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 Qic { + /** 查询质检仓配置 */ + String GET_INSPECT_CONFIG_URL = "https://api.weixin.qq.com/channels/ec/qic/inspect/config/get"; + /** 查询送检配置模板信息 */ + String GET_SUBMIT_CONFIG_URL = "https://api.weixin.qq.com/channels/ec/qic/inspect/submitconfig/get"; + /** 打印质检码 */ + String PRINT_INSPECT_CODE_URL = "https://api.weixin.qq.com/channels/ec/qic/inspect/code/print"; + /** 绑定送检信息 */ + String SUBMIT_INSPECT_INFO_URL = "https://api.weixin.qq.com/channels/ec/qic/inspect/submit"; + /** 自寄快递送检 */ + String REGISTER_LOGISTICS_URL = "https://api.weixin.qq.com/channels/ec/qic/inspect/register_logistics"; + } + + /** + * 直播大屏数据 + */ + 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 Talent { + + /** + * 获取佣金单列表 + */ + String GET_ORDER_LIST_URL = "https://api.weixin.qq.com/channels/ec/talent/get_order_list"; + + /** + * 获取佣金单详情 + */ + String GET_ORDER_DETAIL_URL = "https://api.weixin.qq.com/channels/ec/talent/get_order_detail"; + + /** + * 获取达人橱窗商品列表 + */ + String GET_WINDOW_PRODUCT_LIST_URL = "https://api.weixin.qq.com/channels/ec/talent/window/product/list/get"; + + /** + * 获取达人橱窗商品详情 + */ + String GET_WINDOW_PRODUCT_DETAIL_URL = "https://api.weixin.qq.com/channels/ec/talent/window/product/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/AfterSaleStatus.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/enums/AfterSaleStatus.java index 60e77d9e53..b249c96bcb 100644 --- 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 @@ -39,6 +39,10 @@ public enum AfterSaleStatus { 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; 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/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 index ea3f8873ec..125e8c39c7 100644 --- 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 @@ -24,12 +24,23 @@ public enum FundsType { /** 8 运费险分账 */ FREIGHT_SHARE(8, "运费险分账"), /** 9 联盟平台抽佣 */ - LEAGUE_COMMISSION(9, "联盟平台抽佣"), - /** 10 小店抽佣 */ - SHOP_COMMISSION(10, "小店抽佣"), + 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; 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/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 index fa0bd60913..c1ba1a3561 100644 --- 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 @@ -9,7 +9,14 @@ */ @JsonFormat(shape = JsonFormat.Shape.OBJECT) public enum PromoteType { - PROMOTE_TYPE_SHOP(1, "小店内推广"); + /** 1 小店内推广 */ + PROMOTE_TYPE_SHOP(1, "小店内推广"), + /** 9 会员券 */ + MEMBER(9, "会员券"), + /** 10 会员开卡礼券 */ + MEMBER_CARD(10, "会员开卡礼券"), + + ; private final int key; private final String val; 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 index 85e4d4f0d6..9b5bc6e809 100644 --- 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 @@ -23,7 +23,11 @@ public enum SendTime { TWENTYFOUR_HOUR("SendTime_TWENTYFOUR_HOUR", "24小时内发货"), /** 48小时内发货 */ FOUTYEIGHT_HOUR("SendTime_FOUTYEIGHT_HOUR", "48小时内发货"), - /** 3天内发货 */ + /** + * 3天内发货 + * @deprecated 已不支持,微信小店发货管理规则调整 + */ + @Deprecated THREE_DAY("SendTime_THREE_DAY", "3天内发货"), // /** 5天内发货 */ // FIVE_DAY("SendTime_FIVE_DAY", "5天内发货"), 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 index b4428dbb24..e2fb09d132 100644 --- 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 @@ -19,7 +19,18 @@ public enum ShareScene { 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, "视频号橱窗二维码"), ; 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 index 3d6063b8cf..71c71d5a26 100644 --- 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 @@ -20,7 +20,13 @@ public enum SpuEditStatus { /** 4 审核成功 */ SUCCESS(4, "审核成功"), /** 5 商品信息写入中 */ - WRITING(5, "商品信息写入中"); + WRITING(5, "商品信息写入中"), + /** 7 商品异步提交,上传中(处于该状态的商品调用上架商品接口会返回10020067) */ + ASYNC_WRITING(7, "商品异步提交,上传中"), + /** 8 商品异步提交,上传失败(请重新提交) */ + ASYNC_FAIL(8, "商品异步提交,上传失败"), + + ; private final int status; private final String 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 index a74fee6b07..8f88d5ffac 100644 --- 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 @@ -16,10 +16,20 @@ public enum SpuStatus { UP(5, "上架"), /** 6 回收站 */ TRASH(6, "回收站"), + /** 9 彻底删除,商品无法再进行任何操作 */ + DELETE(9, "彻底删除"), /** 11 自主下架 */ DOWN(11, "自主下架"), /** 13 违规下架/风控系统下架 */ - SYSTEM_DOWN(13, "违规下架"); + SYSTEM_DOWN(13, "违规下架/风控系统下架"), + /** 14 保证金不足下架 */ + DEPOSIT_INSUFFICIENT(14, "保证金不足下架"), + /** 15 品牌过期下架 */ + BRAND_EXPIRED(15, "品牌过期下架"), + /** 20 商品被封禁 */ + BAN(20, "商品被封禁"), + +; private final int status; private final String desc; 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 index 4a699c20d0..18e88c0b08 100644 --- 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 @@ -8,7 +8,9 @@ * 微信视频号全局返回码 * * @author Zeyes + * @deprecated 请使用 {@link me.chanjar.weixin.common.error.WxChannelErrorMsgEnum} 替代 */ +@Deprecated @Getter public enum WxChannelErrorMsgEnum { /** diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ApacheHttpChannelFileUploadRequestExecutor.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ApacheHttpChannelFileUploadRequestExecutor.java new file mode 100644 index 0000000000..5ccb6f5cb1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ApacheHttpChannelFileUploadRequestExecutor.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.channel.executor; + +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.File; +import java.io.IOException; + +public class ApacheHttpChannelFileUploadRequestExecutor extends ChannelFileUploadRequestExecutor { + public ApacheHttpChannelFileUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public String execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", file) + .setMode(HttpMultipartMode.RFC6532) + .build(); + httpPost.setEntity(entity); + } + return requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + } + + @Override + public void execute(String uri, File data, ResponseHandler handler, WxType wxType) + throws WxErrorException, IOException { + handler.handle(this.execute(uri, data, wxType)); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ApacheHttpChannelMediaDownloadRequestExecutor.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ApacheHttpChannelMediaDownloadRequestExecutor.java new file mode 100644 index 0000000000..b9b44b60e2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ApacheHttpChannelMediaDownloadRequestExecutor.java @@ -0,0 +1,90 @@ +package me.chanjar.weixin.channel.executor; + +import me.chanjar.weixin.channel.bean.image.ChannelImageResponse; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.apache.InputStreamResponseHandler; +import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.entity.ContentType; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class ApacheHttpChannelMediaDownloadRequestExecutor extends ChannelMediaDownloadRequestExecutor { + + public ApacheHttpChannelMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + super(requestHttp, tmpDirFile); + } + + @Override + public ChannelImageResponse execute(String uri, String data, WxType wxType) throws WxErrorException, IOException { + if (data != null) { + if (uri.indexOf('?') == -1) { + uri += '?'; + } + uri += uri.endsWith("?") ? data : '&' + data; + } + + HttpGet httpGet = new HttpGet(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpGet.setConfig(config); + } + + try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet); + InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { + Header[] contentTypeHeader = response.getHeaders("Content-Type"); + String contentType = null; + if (contentTypeHeader != null && contentTypeHeader.length > 0) { + contentType = contentTypeHeader[0].getValue(); + if (contentType.startsWith(ContentType.APPLICATION_JSON.getMimeType())) { + // application/json; encoding=utf-8 下载媒体文件出错 + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + return JsonUtils.decode(responseContent, ChannelImageResponse.class); + } + } + + String fileName = this.getFileName(response); + if (StringUtils.isBlank(fileName)) { + fileName = String.valueOf(System.currentTimeMillis()); + } + + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } + String extension = FilenameUtils.getExtension(fileName); + if (StringUtils.isBlank(extension)) { + extension = "unknown"; + } + File file = createTmpFile(inputStream, baseName, extension, tmpDirFile); + return new ChannelImageResponse(file, contentType); + } + } + + private String getFileName(CloseableHttpResponse response) throws WxErrorException { + Header[] contentDispositionHeader = response.getHeaders("Content-disposition"); + if (contentDispositionHeader == null || contentDispositionHeader.length == 0) { + return createDefaultFileName(); + } + return this.extractFileNameFromContentString(contentDispositionHeader[0].getValue()); + } + + @Override + public void execute(String uri, String data, ResponseHandler handler, WxType wxType) + throws WxErrorException, IOException { + handler.handle(this.execute(uri, data, wxType)); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelFileUploadRequestExecutor.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelFileUploadRequestExecutor.java index 576f1c286a..78a6735192 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelFileUploadRequestExecutor.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelFileUploadRequestExecutor.java @@ -1,66 +1,35 @@ package me.chanjar.weixin.channel.executor; - -import java.io.File; -import java.io.IOException; -import me.chanjar.weixin.common.enums.WxType; -import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; -import me.chanjar.weixin.common.util.http.ResponseHandler; -import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHost; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.mime.HttpMultipartMode; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.File; /** * 视频号小店 图片上传接口 请求的参数是File, 返回的结果是String * * @author Zeyes */ -public class ChannelFileUploadRequestExecutor implements RequestExecutor { +public abstract class ChannelFileUploadRequestExecutor implements RequestExecutor { - protected RequestHttp requestHttp; + protected RequestHttp requestHttp; - public ChannelFileUploadRequestExecutor(RequestHttp requestHttp) { + public ChannelFileUploadRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } - @Override - public String execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { - HttpPost httpPost = new HttpPost(uri); - if (requestHttp.getRequestHttpProxy() != null) { - RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); - httpPost.setConfig(config); - } - if (file != null) { - HttpEntity entity = MultipartEntityBuilder - .create() - .addBinaryBody("media", file) - .setMode(HttpMultipartMode.RFC6532) - .build(); - httpPost.setEntity(entity); + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { + switch (requestHttp.getRequestType()) { + case APACHE_HTTP: + return new ApacheHttpChannelFileUploadRequestExecutor( + (RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsChannelFileUploadRequestExecutor( + (RequestHttp) requestHttp); + default: + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { - return Utf8ResponseHandler.INSTANCE.handleResponse(response); - } finally { - httpPost.releaseConnection(); - } - } - - @Override - public void execute(String uri, File data, ResponseHandler handler, WxType wxType) - throws WxErrorException, IOException { - handler.handle(this.execute(uri, data, wxType)); - } - - public static RequestExecutor create(RequestHttp requestHttp) { - return new ChannelFileUploadRequestExecutor(requestHttp); } } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelMediaDownloadRequestExecutor.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelMediaDownloadRequestExecutor.java index 1b81dc6d15..dd4bf0ba89 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelMediaDownloadRequestExecutor.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/ChannelMediaDownloadRequestExecutor.java @@ -1,6 +1,9 @@ package me.chanjar.weixin.channel.executor; -import static org.apache.commons.io.FileUtils.openOutputStream; +import me.chanjar.weixin.channel.bean.image.ChannelImageResponse; +import me.chanjar.weixin.common.util.http.RequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import org.apache.commons.io.IOUtils; import java.io.File; import java.io.IOException; @@ -9,102 +12,38 @@ import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.channel.bean.image.ChannelImageResponse; -import me.chanjar.weixin.channel.util.JsonUtils; -import me.chanjar.weixin.common.enums.WxType; -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.util.http.RequestExecutor; -import me.chanjar.weixin.common.util.http.RequestHttp; -import me.chanjar.weixin.common.util.http.ResponseHandler; -import me.chanjar.weixin.common.util.http.apache.InputStreamResponseHandler; -import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.http.Header; -import org.apache.http.HttpHost; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.entity.ContentType; -import org.apache.http.impl.client.CloseableHttpClient; + +import static org.apache.commons.io.FileUtils.openOutputStream; /** * 下载媒体文件请求执行器 * * @author Zeyes */ -@Slf4j -public class ChannelMediaDownloadRequestExecutor implements RequestExecutor { +public abstract class ChannelMediaDownloadRequestExecutor implements RequestExecutor { - protected RequestHttp requestHttp; + protected RequestHttp requestHttp; protected File tmpDirFile; private static final Pattern PATTERN = Pattern.compile(".*filename=\"(.*)\""); - public ChannelMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + public ChannelMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { this.requestHttp = requestHttp; this.tmpDirFile = tmpDirFile; } - @Override - public ChannelImageResponse execute(String uri, String data, WxType wxType) throws WxErrorException, IOException { - if (data != null) { - if (uri.indexOf('?') == -1) { - uri += '?'; - } - uri += uri.endsWith("?") ? data : '&' + data; - } - - HttpGet httpGet = new HttpGet(uri); - if (requestHttp.getRequestHttpProxy() != null) { - RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); - httpGet.setConfig(config); + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp, File tmpDirFile) { + switch (requestHttp.getRequestType()) { + case APACHE_HTTP: + return new ApacheHttpChannelMediaDownloadRequestExecutor( + (RequestHttp) requestHttp, tmpDirFile); + case HTTP_COMPONENTS: + return new HttpComponentsChannelMediaDownloadRequestExecutor( + (RequestHttp) requestHttp, tmpDirFile); + default: + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } - - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet); - InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { - Header[] contentTypeHeader = response.getHeaders("Content-Type"); - String contentType = null; - if (contentTypeHeader != null && contentTypeHeader.length > 0) { - contentType = contentTypeHeader[0].getValue(); - if (contentType.startsWith(ContentType.APPLICATION_JSON.getMimeType())) { - // application/json; encoding=utf-8 下载媒体文件出错 - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - return JsonUtils.decode(responseContent, ChannelImageResponse.class); - } - } - - String fileName = this.getFileName(response); - if (StringUtils.isBlank(fileName)) { - fileName = String.valueOf(System.currentTimeMillis()); - } - - String baseName = FilenameUtils.getBaseName(fileName); - if (StringUtils.isBlank(fileName) || baseName.length() < 3) { - baseName = String.valueOf(System.currentTimeMillis()); - } - String extension = FilenameUtils.getExtension(fileName); - if (StringUtils.isBlank(extension)) { - extension = "unknown"; - } - File file = createTmpFile(inputStream, baseName, extension, tmpDirFile); - ChannelImageResponse result = new ChannelImageResponse(file, contentType); - return result; - } finally { - httpGet.releaseConnection(); - } - } - - @Override - public void execute(String uri, String data, ResponseHandler handler, WxType wxType) - throws WxErrorException, IOException { - handler.handle(this.execute(uri, data, wxType)); - } - - public static RequestExecutor create(RequestHttp requestHttp, File tmpDirFile) { - return new ChannelMediaDownloadRequestExecutor(requestHttp, tmpDirFile); } /** @@ -125,20 +64,12 @@ public static File createTmpFile(InputStream inputStream, String name, String ex return resultFile; } - private String getFileName(CloseableHttpResponse response) throws WxErrorException { - Header[] contentDispositionHeader = response.getHeaders("Content-disposition"); - if (contentDispositionHeader == null || contentDispositionHeader.length == 0) { - return createDefaultFileName(); - } - return this.extractFileNameFromContentString(contentDispositionHeader[0].getValue()); - } - - private String createDefaultFileName() { + protected String createDefaultFileName() { return UUID.randomUUID().toString(); } - private String extractFileNameFromContentString(String content) throws WxErrorException { - if (content == null || content.length() == 0) { + protected String extractFileNameFromContentString(String content) { + if (content == null || content.isEmpty()) { return createDefaultFileName(); } Matcher m = PATTERN.matcher(content); diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/HttpComponentsChannelFileUploadRequestExecutor.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/HttpComponentsChannelFileUploadRequestExecutor.java new file mode 100644 index 0000000000..3b1e7076a9 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/HttpComponentsChannelFileUploadRequestExecutor.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.channel.executor; + +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; + +public class HttpComponentsChannelFileUploadRequestExecutor extends ChannelFileUploadRequestExecutor { + public HttpComponentsChannelFileUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public String execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", file) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + return requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + } + + @Override + public void execute(String uri, File data, ResponseHandler handler, WxType wxType) + throws WxErrorException, IOException { + handler.handle(this.execute(uri, data, wxType)); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/HttpComponentsChannelMediaDownloadRequestExecutor.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/HttpComponentsChannelMediaDownloadRequestExecutor.java new file mode 100644 index 0000000000..95a13f6c86 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/executor/HttpComponentsChannelMediaDownloadRequestExecutor.java @@ -0,0 +1,94 @@ +package me.chanjar.weixin.channel.executor; + +import me.chanjar.weixin.channel.bean.image.ChannelImageResponse; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.util.http.hc.InputStreamResponseHandler; +import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class HttpComponentsChannelMediaDownloadRequestExecutor extends ChannelMediaDownloadRequestExecutor { + + public HttpComponentsChannelMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + super(requestHttp, tmpDirFile); + } + + @Override + public ChannelImageResponse execute(String uri, String data, WxType wxType) throws WxErrorException, IOException { + if (data != null) { + if (uri.indexOf('?') == -1) { + uri += '?'; + } + uri += uri.endsWith("?") ? data : '&' + data; + } + + HttpGet httpGet = new HttpGet(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpGet.setConfig(config); + } + + try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet); + InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { + Header[] contentTypeHeader = response.getHeaders("Content-Type"); + String contentType = null; + if (contentTypeHeader != null && contentTypeHeader.length > 0) { + contentType = contentTypeHeader[0].getValue(); + if (contentType.startsWith(ContentType.APPLICATION_JSON.getMimeType())) { + // application/json; encoding=utf-8 下载媒体文件出错 + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + return JsonUtils.decode(responseContent, ChannelImageResponse.class); + } + } + + String fileName = this.getFileName(response); + if (StringUtils.isBlank(fileName)) { + fileName = String.valueOf(System.currentTimeMillis()); + } + + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } + String extension = FilenameUtils.getExtension(fileName); + if (StringUtils.isBlank(extension)) { + extension = "unknown"; + } + File file = createTmpFile(inputStream, baseName, extension, tmpDirFile); + return new ChannelImageResponse(file, contentType); + } catch (HttpException httpException) { + throw new ClientProtocolException(httpException.getMessage(), httpException); + } + } + + private String getFileName(CloseableHttpResponse response) throws WxErrorException { + Header[] contentDispositionHeader = response.getHeaders("Content-disposition"); + if (contentDispositionHeader == null || contentDispositionHeader.length == 0) { + return createDefaultFileName(); + } + return this.extractFileNameFromContentString(contentDispositionHeader[0].getValue()); + } + + @Override + public void execute(String uri, String data, ResponseHandler handler, WxType wxType) + throws WxErrorException, IOException { + handler.handle(this.execute(uri, data, wxType)); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessageRouter.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessageRouter.java index 8ccc2c5050..3236e18303 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessageRouter.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/message/WxChannelMessageRouter.java @@ -121,6 +121,7 @@ public Object route(final WxChannelMessage message, final String content, final final Map context, final WxChannelService service, final WxSessionManager sessionManager) { // 如果是重复消息,那么就不做处理 if (isMsgDuplicated(message)) { + log.info("收到重复消息,{}", content); return null; } @@ -136,7 +137,7 @@ public Object route(final WxChannelMessage message, final String content, final } } - if (matchRules.size() == 0) { + if (matchRules.isEmpty()) { return null; } final List> futures = new ArrayList<>(); @@ -156,7 +157,7 @@ public Object route(final WxChannelMessage message, final String content, final } } - if (futures.size() > 0) { + if (!futures.isEmpty()) { this.executorService.submit(() -> { for (Future future : futures) { try { @@ -178,7 +179,7 @@ public Object route(final WxChannelMessage message, final String content, final * @param wxMessage 消息 * @return 是否重复 */ - private boolean isMsgDuplicated(WxChannelMessage wxMessage) { + protected boolean isMsgDuplicated(WxChannelMessage wxMessage) { String messageId = this.generateMessageId(wxMessage); return this.messageDuplicateChecker.isDuplicate(messageId); } @@ -188,7 +189,7 @@ private boolean isMsgDuplicated(WxChannelMessage wxMessage) { * * @return 消息id */ - private String generateMessageId(WxChannelMessage wxMessage) { + protected String generateMessageId(WxChannelMessage wxMessage) { StringBuilder sb = new StringBuilder(); if (wxMessage.getMsgId() == null) { sb.append(wxMessage.getCreateTime()) @@ -211,10 +212,10 @@ private String generateMessageId(WxChannelMessage wxMessage) { * * @param sessionManager session管理器 * @param message 消息 - * @param sync 是否同步 打印log用 + * @param async 是否异步 打印log用 */ - private void sessionEndAccess(WxSessionManager sessionManager, WxChannelMessage message, boolean sync) { - log.debug("End session access: async={}, sessionId={}", sync, message.getFromUser()); + 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/test/java/me/chanjar/weixin/channel/api/WxChannelStoreCompatibilityTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/WxChannelStoreCompatibilityTest.java new file mode 100644 index 0000000000..22de36f993 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/WxChannelStoreCompatibilityTest.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.api; + +import java.lang.reflect.Method; +import me.chanjar.weixin.channel.bean.address.AddressAddParam; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Verifies that the store split keeps the legacy channel API available for existing consumers. + */ +public class WxChannelStoreCompatibilityTest { + + @Test + public void shouldKeepLegacyStoreApisAndModelsDeprecated() throws NoSuchMethodException { + Method productService = WxChannelService.class.getMethod("getProductService"); + + Assert.assertEquals(productService.getReturnType(), WxChannelProductService.class); + Assert.assertTrue(productService.isAnnotationPresent(Deprecated.class)); + Assert.assertTrue(AddressAddParam.class.isAnnotationPresent(Deprecated.class)); + } + + @Test + public void shouldKeepChannelOnlyApisAvailableWithoutDeprecation() throws NoSuchMethodException { + Method finderLiveService = WxChannelService.class.getMethod("getFinderLiveService"); + + Assert.assertEquals(finderLiveService.getReturnType(), WxFinderLiveService.class); + Assert.assertFalse(finderLiveService.isAnnotationPresent(Deprecated.class)); + } +} 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/WxChannelAfterSaleServiceImplGuaranteeTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImplGuaranteeTest.java new file mode 100644 index 0000000000..e8eaee0fcf --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelAfterSaleServiceImplGuaranteeTest.java @@ -0,0 +1,163 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Answers.CALLS_REAL_METHODS; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.AfterSale.GUARANTEE_ORDER_ACCEPT_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.AfterSale.GUARANTEE_ORDER_GET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.AfterSale.GUARANTEE_ORDER_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.AfterSale.GUARANTEE_ORDER_MODIFY_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.AfterSale.GUARANTEE_ORDER_PROOF_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.AfterSale.GUARANTEE_ORDER_REFUSE_URL; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Arrays; +import me.chanjar.weixin.channel.api.WxChannelAfterSaleService; +import me.chanjar.weixin.channel.bean.after.GuaranteeModifyRequest; +import me.chanjar.weixin.channel.bean.after.GuaranteeOrderIdParam; +import me.chanjar.weixin.channel.bean.after.GuaranteeOrderInfoResponse; +import me.chanjar.weixin.channel.bean.after.GuaranteeOrderListParam; +import me.chanjar.weixin.channel.bean.after.GuaranteeOrderListResponse; +import me.chanjar.weixin.channel.bean.after.GuaranteeProofRequest; +import me.chanjar.weixin.channel.bean.after.GuaranteeRefuseRequest; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import org.testng.annotations.Test; + + +/** + * 保障单模型 JSON 映射测试。 + */ +public class WxChannelAfterSaleServiceImplGuaranteeTest { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + @Test + public void shouldSerializeGuaranteeOperationRequestsWithOfficialFieldNames() throws Exception { + JsonNode modify = OBJECT_MAPPER.readTree(OBJECT_MAPPER.writeValueAsString( + new GuaranteeModifyRequest("2000001077270153", 50, "协商说明"))); + assertEquals(modify.get("guarantee_order_id").asText(), "2000001077270153"); + assertEquals(modify.get("bad_level").asInt(), 50); + assertEquals(modify.get("merchant_remark").asText(), "协商说明"); + + JsonNode proof = OBJECT_MAPPER.readTree(OBJECT_MAPPER.writeValueAsString( + new GuaranteeProofRequest("2000001077270153", "举证说明", Arrays.asList("media-1")))); + assertEquals(proof.get("content").asText(), "举证说明"); + assertEquals(proof.get("pic_list").get(0).asText(), "media-1"); + + JsonNode refuse = OBJECT_MAPPER.readTree(OBJECT_MAPPER.writeValueAsString( + new GuaranteeRefuseRequest("2000001077270153", "拒绝原因", Arrays.asList("media-2")))); + assertEquals(refuse.get("reason").asText(), "拒绝原因"); + assertEquals(refuse.get("pic_list").get(0).asText(), "media-2"); + } + + @Test + public void shouldDeserializeGuaranteeOrderListAndDetail() throws Exception { + String listJson = "{\"errcode\":0,\"total_num\":1,\"guarantee_order_list\":[{" + + "\"guarantee_order_id\":\"2000001077270153\",\"status\":\"PENDING\"," + + "\"product_info\":[{\"product_id\":\"123\"}]}]}"; + GuaranteeOrderListResponse listResponse = OBJECT_MAPPER.readValue( + listJson, GuaranteeOrderListResponse.class); + assertEquals(listResponse.getTotalNum(), Integer.valueOf(1)); + assertEquals(listResponse.getGuaranteeOrderList().get(0).getGuaranteeOrderId(), "2000001077270153"); + assertEquals(listResponse.getGuaranteeOrderList().get(0).getStatus(), "PENDING"); + assertEquals(listResponse.getGuaranteeOrderList().get(0).getProductInfo().get(0).getProductId(), "123"); + + String detailJson = "{\"errcode\":0,\"guarantee_order\":{" + + "\"guarantee_order_id\":\"2000001077270153\",\"status\":\"PENDING\"," + + "\"product_info\":{\"product_id\":\"123\"}}}"; + GuaranteeOrderInfoResponse detailResponse = OBJECT_MAPPER.readValue( + detailJson, GuaranteeOrderInfoResponse.class); + assertEquals(detailResponse.getGuaranteeOrder().getGuaranteeOrderId(), "2000001077270153"); + assertEquals(detailResponse.getGuaranteeOrder().getStatus(), "PENDING"); + assertEquals(detailResponse.getGuaranteeOrder().getProductInfo().getProductId(), "123"); + } + + @Test + public void shouldSerializeGuaranteeOrderListParamWithOfficialFieldNames() throws Exception { + GuaranteeOrderListParam param = OBJECT_MAPPER.readValue( + "{\"guarantee_order_id_list\":[\"g-1\"],\"order_id_list\":[\"o-1\"],\"type\":2," + + "\"begin_time\":1,\"end_time\":2,\"status_list\":\"PENDING\",\"offset\":3,\"limit\":10}", GuaranteeOrderListParam.class); + + JsonNode json = OBJECT_MAPPER.readTree(OBJECT_MAPPER.writeValueAsString(param)); + assertEquals(json.get("guarantee_order_id_list").get(0).asText(), "g-1"); + assertEquals(json.get("order_id_list").get(0).asText(), "o-1"); + assertEquals(json.get("type").asInt(), 2); + assertEquals(json.get("begin_time").asLong(), 1L); + assertEquals(json.get("end_time").asLong(), 2L); + assertEquals(json.get("status_list").asText(), "PENDING"); + assertEquals(json.get("offset").asInt(), 3); + assertEquals(json.get("limit").asInt(), 10); + } + + @Test + public void shouldExposeGuaranteeMethodsAsDefaultMethods() throws Exception { + assertTrue(WxChannelAfterSaleService.class.getMethod("listGuaranteeOrder", GuaranteeOrderListParam.class) + .isDefault()); + assertTrue(WxChannelAfterSaleService.class.getMethod("getGuaranteeOrder", String.class).isDefault()); + assertTrue(WxChannelAfterSaleService.class.getMethod("acceptGuarantee", String.class).isDefault()); + assertTrue(WxChannelAfterSaleService.class.getMethod("modifyGuarantee", GuaranteeModifyRequest.class).isDefault()); + assertTrue(WxChannelAfterSaleService.class.getMethod("proofGuarantee", GuaranteeProofRequest.class).isDefault()); + assertTrue(WxChannelAfterSaleService.class.getMethod("refuseGuarantee", GuaranteeRefuseRequest.class).isDefault()); + + WxChannelAfterSaleService service = mock(WxChannelAfterSaleService.class, CALLS_REAL_METHODS); + try { + service.acceptGuarantee("guarantee-1"); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException ignored) { + // Expected from the compatibility default method. + } + } + + @Test + public void shouldDelegateGuaranteeOrderEndpointsAndDecodeResponses() throws Exception { + BaseWxChannelServiceImpl shopService = mock(BaseWxChannelServiceImpl.class); + WxChannelAfterSaleServiceImpl service = new WxChannelAfterSaleServiceImpl(shopService); + GuaranteeOrderListParam listParam = new GuaranteeOrderListParam(); + listParam.setLimit(10); + GuaranteeModifyRequest modifyRequest = new GuaranteeModifyRequest("guarantee-1", 50, "协商说明"); + GuaranteeProofRequest proofRequest = new GuaranteeProofRequest("guarantee-1", "举证说明", + Arrays.asList("media-1")); + GuaranteeRefuseRequest refuseRequest = new GuaranteeRefuseRequest("guarantee-1", "拒绝原因", + Arrays.asList("media-2")); + + when(shopService.post(eq(GUARANTEE_ORDER_LIST_URL), eq(listParam))).thenReturn( + "{\"errcode\":0,\"total_num\":1,\"guarantee_order_list\":[{\"guarantee_order_id\":\"guarantee-1\"}]}" ); + when(shopService.post(eq(GUARANTEE_ORDER_GET_URL), eq(new GuaranteeOrderIdParam("guarantee-1")))).thenReturn( + "{\"errcode\":0,\"guarantee_order\":{\"guarantee_order_id\":\"guarantee-1\"}}" ); + when(shopService.post(eq(GUARANTEE_ORDER_ACCEPT_URL), eq(new GuaranteeOrderIdParam("guarantee-1")))) + .thenReturn("{\"errcode\":0,\"errmsg\":\"ok\"}"); + when(shopService.post(eq(GUARANTEE_ORDER_MODIFY_URL), eq(modifyRequest))) + .thenReturn("{\"errcode\":0,\"errmsg\":\"ok\"}"); + when(shopService.post(eq(GUARANTEE_ORDER_PROOF_URL), eq(proofRequest))) + .thenReturn("{\"errcode\":0,\"errmsg\":\"ok\"}"); + when(shopService.post(eq(GUARANTEE_ORDER_REFUSE_URL), eq(refuseRequest))) + .thenReturn("{\"errcode\":40001,\"errmsg\":\"invalid credential\"}"); + + GuaranteeOrderListResponse listResponse = service.listGuaranteeOrder(listParam); + GuaranteeOrderInfoResponse detailResponse = service.getGuaranteeOrder("guarantee-1"); + WxChannelBaseResponse acceptResponse = service.acceptGuarantee("guarantee-1"); + WxChannelBaseResponse modifyResponse = service.modifyGuarantee(modifyRequest); + WxChannelBaseResponse proofResponse = service.proofGuarantee(proofRequest); + WxChannelBaseResponse refuseResponse = service.refuseGuarantee(refuseRequest); + + assertEquals(listResponse.getGuaranteeOrderList().get(0).getGuaranteeOrderId(), "guarantee-1"); + assertEquals(detailResponse.getGuaranteeOrder().getGuaranteeOrderId(), "guarantee-1"); + assertTrue(acceptResponse.isSuccess()); + assertTrue(modifyResponse.isSuccess()); + assertTrue(proofResponse.isSuccess()); + assertFalse(refuseResponse.isSuccess()); + verify(shopService).post(GUARANTEE_ORDER_LIST_URL, listParam); + verify(shopService).post(GUARANTEE_ORDER_GET_URL, new GuaranteeOrderIdParam("guarantee-1")); + verify(shopService).post(GUARANTEE_ORDER_ACCEPT_URL, new GuaranteeOrderIdParam("guarantee-1")); + verify(shopService).post(GUARANTEE_ORDER_MODIFY_URL, modifyRequest); + verify(shopService).post(GUARANTEE_ORDER_PROOF_URL, proofRequest); + verify(shopService).post(GUARANTEE_ORDER_REFUSE_URL, refuseRequest); + } +} 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 index 7e54a9eaae..529d455d46 100644 --- 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 @@ -12,6 +12,8 @@ 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; @@ -52,8 +54,8 @@ public void testGet() throws WxErrorException { public void testAccept() throws WxErrorException { WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService(); String afterSaleOrderId = ""; - String addressId = "123"; - WxChannelBaseResponse response = afterSaleService.accept(afterSaleOrderId, addressId); + String addressId = null; + WxChannelBaseResponse response = afterSaleService.accept(afterSaleOrderId, addressId, 2); assertNotNull(response); assertTrue(response.isSuccess()); } @@ -62,8 +64,20 @@ public void testAccept() throws WxErrorException { public void testReject() throws WxErrorException { WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService(); String afterSaleOrderId = ""; - String rejectReason = "123"; - WxChannelBaseResponse response = afterSaleService.reject(afterSaleOrderId, rejectReason); + String rejectReason = null; + WxChannelBaseResponse response = afterSaleService.reject(afterSaleOrderId, rejectReason,1); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testRejectWithCertificates() throws WxErrorException { + WxChannelAfterSaleService afterSaleService = channelService.getAfterSaleService(); + String afterSaleOrderId = ""; + String rejectReason = null; + List rejectCertificates = new ArrayList<>(4); + rejectCertificates.add("THE_FILE_ID_1"); + WxChannelBaseResponse response = afterSaleService.reject(afterSaleOrderId, rejectReason, 1, rejectCertificates); assertNotNull(response); assertTrue(response.isSuccess()); } @@ -109,4 +123,21 @@ public void testGetComplaint() throws WxErrorException { 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 index 5673d85439..120e11245a 100644 --- 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 @@ -20,6 +20,8 @@ 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; @@ -29,9 +31,19 @@ @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(); diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImplTest.java index 9b1a62ada3..06afde2993 100644 --- a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImplTest.java +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelCategoryServiceImplTest.java @@ -4,18 +4,24 @@ import static org.testng.Assert.assertTrue; import com.google.inject.Inject; -import java.util.ArrayList; 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; @@ -24,6 +30,7 @@ /** * @author Zeyes */ +@Slf4j @Guice(modules = ApiTestModule.class) public class WxChannelCategoryServiceImplTest { @@ -36,21 +43,40 @@ public void testListAllCategory() throws WxErrorException { CategoryQualificationResponse response = categoryService.listAllCategory(); assertNotNull(response); assertTrue(response.isSuccess()); - System.out.println(response); + //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 testListAvailableCategory() throws WxErrorException { + public void testListAvailableCategories() throws WxErrorException { WxChannelCategoryService categoryService = channelService.getCategoryService(); - List shopCategories = categoryService.listAvailableCategory("0"); - assertTrue(CollectionUtils.hasElements(shopCategories)); - shopCategories.forEach(System.out::println); + 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("1602"); + CategoryDetailResult categoryDetail = categoryService.getCategoryDetail("7231"); assertNotNull(categoryDetail); assertTrue(categoryDetail.isSuccess()); System.out.println(categoryDetail); @@ -58,11 +84,47 @@ public void testGetCategoryDetail() throws WxErrorException { @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(); - List certificates = new ArrayList<>(); - certificates.add( - "hWNith8iZSrxfN7W2tXOoWSXYWi1qADRJxwImvQl2DC6wqqJrl4g8i/UEZfd59yiiEKAnqy0WETFrOcGZFcJDfpH2ccmNZddzesR1/OpAC7bbfmEiDFBK2QL7MBjhR2m"); - AuditApplyResponse response = categoryService.addCategory("1001", "1002", "1005", certificates); + AuditApplyResponse response = categoryService.addCategory(info); assertNotNull(response); assertTrue(response.isSuccess()); System.out.println(response); @@ -96,4 +158,14 @@ public void testListPassCategory() throws WxErrorException { assertTrue(response.isSuccess()); System.out.println(response); } + + @Test + public void testListRelationCategory() throws WxErrorException { + WxChannelCategoryService categoryService = channelService.getCategoryService(); + me.chanjar.weixin.channel.bean.category.RelationCategoryResponse response = + categoryService.listRelationCategory(true, 1); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(response); + } } diff --git a/weixin-java-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/WxChannelEwaybillServiceAccessorTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelEwaybillServiceAccessorTest.java new file mode 100644 index 0000000000..0f53dad6c9 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelEwaybillServiceAccessorTest.java @@ -0,0 +1,17 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertNotNull; + +import org.testng.annotations.Test; + +/** + * @author GitHub Copilot + */ +public class WxChannelEwaybillServiceAccessorTest { + + @Test + public void testGetEwaybillService() { + WxChannelServiceImpl service = new WxChannelServiceImpl(); + assertNotNull(service.getEwaybillService()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelFavoriteServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelFavoriteServiceImplTest.java new file mode 100644 index 0000000000..1e5dfd7c5d --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelFavoriteServiceImplTest.java @@ -0,0 +1,35 @@ +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.WxChannelFavoriteService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.favorite.FavoriteCountResponse; +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 GitHub Copilot + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelFavoriteServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testGetFavoriteCount() throws WxErrorException { + WxChannelFavoriteService favoriteService = channelService.getFavoriteService(); + FavoriteCountResponse response = favoriteService.getFavoriteCount(); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelKfServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelKfServiceImplTest.java new file mode 100644 index 0000000000..02353aab37 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelKfServiceImplTest.java @@ -0,0 +1,120 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertSame; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgParam; +import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgResponse; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.bean.CommonUploadParam; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestExecutor; +import org.testng.annotations.Test; + +/** 商家客服服务离线测试。 */ +public class WxChannelKfServiceImplTest { + + @Test + public void shouldUploadMediaWithDocumentedUrlAndMultipartFields() throws WxErrorException { + RecordingChannelService channelService = new RecordingChannelService(); + channelService.uploadResult = "{\"errcode\":0,\"errmsg\":\"ok\",\"cos_url\":\"https://cos.example.com/image.png\"}"; + + String cosUrl = new WxChannelKfServiceImpl(channelService) + .uploadMedia("open-id", "image", "image.png", new byte[]{1, 2, 3}); + + assertEquals(channelService.uploadUrl, "https://api.weixin.qq.com/channels/ec/commkf/cosupload"); + assertNotNull(channelService.uploadParam); + assertEquals(channelService.uploadParam.getName(), "file"); + assertEquals(channelService.uploadParam.getData().getFileName(), "image.png"); + assertEquals(channelService.uploadParam.getData().readAllBytes(), new byte[]{1, 2, 3}); + assertEquals(channelService.uploadParam.getFormFields().get("open_id"), "open-id"); + assertEquals(channelService.uploadParam.getFormFields().get("msg_type"), "image"); + assertEquals(cosUrl, "https://cos.example.com/image.png"); + } + + @Test + public void shouldSendJsonMessageAndDecodeResponse() throws WxErrorException { + RecordingChannelService channelService = new RecordingChannelService(); + channelService.postResult = "{\"errcode\":0,\"errmsg\":\"ok\",\"msg_id\":\"message-id\"}"; + WxChannelKfSendMsgParam param = new WxChannelKfSendMsgParam(); + param.setRequestId("request-id"); + param.setOpenId("open-id"); + param.setMsgType("text"); + WxChannelKfSendMsgParam.Text text = new WxChannelKfSendMsgParam.Text(); + text.setContent("hello"); + param.setText(text); + + WxChannelKfSendMsgResponse response = new WxChannelKfServiceImpl(channelService).sendMessage(param); + + assertEquals(channelService.postUrl, "https://api.weixin.qq.com/channels/ec/commkf/sendmsg"); + assertEquals(channelService.postJson, + "{\"request_id\":\"request-id\",\"open_id\":\"open-id\",\"msg_type\":\"text\",\"text\":{\"content\":\"hello\"}}"); + assertEquals(channelService.executeWithoutLogCalled, true); + assertEquals(response.getMsgId(), "message-id"); + assertEquals(response.getErrCode(), 0); + } + + @Test + public void shouldProvideFreshUploadStreamForEachAttempt() throws IOException { + CommonUploadParam uploadParam = CommonUploadParam.fromBytes("file", "image.png", new byte[]{1, 2, 3}); + + assertEquals(readAllBytes(uploadParam.getData().getInputStream()), new byte[]{1, 2, 3}); + assertEquals(readAllBytes(uploadParam.getData().getInputStream()), new byte[]{1, 2, 3}); + } + + @Test + public void shouldCacheKfServiceEntryPoint() { + WxChannelServiceImpl channelService = new WxChannelServiceImpl(); + + assertSame(channelService.getKfService(), channelService.getKfService()); + } + + private static class RecordingChannelService extends WxChannelServiceImpl { + + private String uploadResult; + private String postResult; + private String uploadUrl; + private CommonUploadParam uploadParam; + private String postUrl; + private String postJson; + private boolean executeWithoutLogCalled; + + @Override + public String upload(String url, CommonUploadParam param) { + this.uploadUrl = url; + this.uploadParam = param; + return uploadResult; + } + + @Override + public String post(String url, Object obj) { + this.postUrl = url; + this.postJson = JsonUtils.encode(obj); + return postResult; + } + + @Override + @SuppressWarnings("unchecked") + public T executeWithoutLog(RequestExecutor executor, String uri, E data) { + this.executeWithoutLogCalled = true; + this.postUrl = uri; + this.postJson = (String) data; + return (T) postResult; + } + } + + private byte[] readAllBytes(InputStream inputStream) throws IOException { + try (InputStream stream = inputStream; ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + byte[] buffer = new byte[16]; + int count; + while ((count = stream.read(buffer)) != -1) { + outputStream.write(buffer, 0, count); + } + return outputStream.toByteArray(); + } + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImplTest.java new file mode 100644 index 0000000000..2ee7c5b701 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImplTest.java @@ -0,0 +1,62 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertEquals; + +import java.util.Arrays; +import me.chanjar.weixin.channel.bean.limit.LimitSkuUpdate; +import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateParam; +import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateResponse; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Test; + +/** + * Tests for {@link WxChannelLimitedDiscountServiceImpl}. + */ +public class WxChannelLimitedDiscountServiceImplTest { + + @Test + public void shouldUpdateLimitedDiscountTaskAndDecodeResponse() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"," + + "\"task_id\":\"task-id\",\"title\":\"updated title\"}"; + LimitSkuUpdate sku = new LimitSkuUpdate(); + sku.setProductId("product-id"); + sku.setSkuId("sku-id"); + sku.setSalePrice(2888); + sku.setSaleStock(5); + LimitTaskUpdateParam param = new LimitTaskUpdateParam(); + param.setTaskId("task-id"); + param.setStatus(0); + param.setStartTime(1_700_000_000L); + param.setEndTime(1_700_003_600L); + param.setTitle("updated title"); + param.setSkus(Arrays.asList(sku)); + + LimitTaskUpdateResponse response = channelService.getLimitedDiscountService().updateLimitTask(param); + + assertEquals(channelService.url, + "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/update"); + assertEquals(channelService.request, + "{\"task_id\":\"task-id\",\"status\":0,\"start_time\":1700000000," + + "\"end_time\":1700003600,\"title\":\"updated title\"," + + "\"limited_discount_skus\":[{\"product_id\":\"product-id\"," + + "\"sku_id\":\"sku-id\",\"sale_price\":2888,\"sale_stock\":5}]}"); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getErrMsg(), "ok"); + assertEquals(response.getTaskId(), "task-id"); + assertEquals(response.getTitle(), "updated title"); + } + + private static class CapturingChannelService extends WxChannelServiceImpl { + private String url; + private String request; + private String response; + + @Override + public String post(String url, String postData) { + this.url = url; + this.request = postData; + return this.response; + } + } +} 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 index 810d81a005..c72d6da513 100644 --- a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImplTest.java +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelOrderServiceImplTest.java @@ -12,13 +12,22 @@ 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.OrderCompensationDeliveryParam; 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.PreShipmentChangeSkuResponse; +import me.chanjar.weixin.channel.bean.order.PresentSubOrderResponse; +import me.chanjar.weixin.channel.bean.order.PrivateNumberGetPhoneResponse; +import me.chanjar.weixin.channel.bean.order.RealNumberViewAuditResponse; +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; @@ -42,6 +51,16 @@ public void testGetOrder() throws WxErrorException { assertTrue(response.isSuccess()); } + @Test + public void testGetOrder2() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + boolean encodeSensitiveInfo = true; + OrderInfoResponse response = orderService.getOrder(orderId, encodeSensitiveInfo); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + @Test public void testGetOrders() throws WxErrorException { WxChannelOrderService orderService = channelService.getOrderService(); @@ -55,6 +74,10 @@ public void testGetOrders() throws WxErrorException { public void testSearchOrder() throws WxErrorException { WxChannelOrderService orderService = channelService.getOrderService(); OrderSearchParam param = new OrderSearchParam(); + param.setPageSize(100); + OrderSearchCondition searchCondition = new OrderSearchCondition(); + searchCondition.setTitle(""); + param.setSearchCondition(searchCondition); OrderListResponse response = orderService.searchOrder(param); assertNotNull(response); assertTrue(response.isSuccess()); @@ -102,6 +125,24 @@ public void testUpdateDelivery() throws WxErrorException { 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(); @@ -114,7 +155,7 @@ public void testCloseOrder() { @Test public void testListDeliveryCompany() throws WxErrorException { WxChannelOrderService orderService = channelService.getOrderService(); - DeliveryCompanyResponse response = orderService.listDeliveryCompany(); + DeliveryCompanyResponse response = orderService.listDeliveryCompany(false); assertNotNull(response); assertTrue(response.isSuccess()); } @@ -130,4 +171,155 @@ public void testDeliveryOrder() throws WxErrorException { 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()); + } + + @Test + public void testAddPresentNote() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + String note = "测试备注"; + WxChannelBaseResponse response = orderService.addPresentNote(orderId, note); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetPresentSubOrders() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + PresentSubOrderResponse response = orderService.getPresentSubOrders(orderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetPreShipmentChangeSku() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + PreShipmentChangeSkuResponse response = orderService.getPreShipmentChangeSku(orderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testApprovePreShipmentChangeSku() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + WxChannelBaseResponse response = orderService.approvePreShipmentChangeSku(orderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testRejectPreShipmentChangeSku() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + String rejectReason = "库存不足"; + WxChannelBaseResponse response = orderService.rejectPreShipmentChangeSku(orderId, rejectReason); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testApplyRealNumber() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + WxChannelBaseResponse response = orderService.applyRealNumber(orderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetRealNumberViewAudit() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + RealNumberViewAuditResponse response = orderService.getRealNumberViewAudit(orderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testApplyVirtualNumberAgain() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + WxChannelBaseResponse response = orderService.applyVirtualNumberAgain(orderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testDelayVirtualNumber() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String orderId = ""; + WxChannelBaseResponse response = orderService.delayVirtualNumber(orderId); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testAddPrivatePhone() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String phone = "13800138000"; + WxChannelBaseResponse response = orderService.addPrivatePhone(phone); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testSendPrivatePhoneVerifyCode() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + String phone = "13800138000"; + WxChannelBaseResponse response = orderService.sendPrivatePhoneVerifyCode(phone); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetPrivatePhone() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + PrivateNumberGetPhoneResponse response = orderService.getPrivatePhone(); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testCompensationDelivery() throws WxErrorException { + WxChannelOrderService orderService = channelService.getOrderService(); + OrderCompensationDeliveryParam param = new OrderCompensationDeliveryParam(); + param.setOrderId(""); + WxChannelBaseResponse response = orderService.compensationDelivery(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } } diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImplTest.java new file mode 100644 index 0000000000..7c19b137f9 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImplTest.java @@ -0,0 +1,169 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.util.Arrays; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.assistant.BeginTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.assistant.CancelTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckParam; +import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalAttribute; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewParam; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingParam; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendParam; +import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendResponse; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Test; + +/** + * Tests for {@link WxChannelProductAssistantServiceImpl}. + */ +public class WxChannelProductAssistantServiceImplTest { + + @Test + public void shouldPreCheckCategoryAndDecodeResponse() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"," + + "\"all_pass\":false,\"fail_reasons\":[\"保证金不足\"]}"; + CategoryPreCheckParam param = new CategoryPreCheckParam(); + param.setCatId(6261L); + + CategoryPreCheckResponse response = channelService.getProductAssistantService().categoryPreCheck(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/categoryprecheck"); + assertEquals(channelService.request, "{\"cat_id\":6261}"); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getErrMsg(), "ok"); + assertEquals(response.getAllPass(), Boolean.FALSE); + assertEquals(response.getFailReasons(), Arrays.asList("保证金不足")); + } + + @Test + public void shouldRecommendProductBrandAndDecodeResponse() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\",\"brand_id\":2100000000," + + "\"brand_name_chinese\":\"品牌\",\"brand_name_english\":\"brand\"}"; + ProductBrandRecommendParam param = new ProductBrandRecommendParam(); + param.setCatId(6000L); + param.setHeadImgs(Arrays.asList("https://example.com/head.jpg")); + param.setDetailImgs(Arrays.asList("https://example.com/detail.jpg")); + param.setTitle("测试商品"); + + ProductBrandRecommendResponse response = + channelService.getProductAssistantService().getProductBrandRecommend(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/productbrandrecommend"); + assertEquals(channelService.request, + "{\"cat_id\":6000,\"head_imgs\":[\"https://example.com/head.jpg\"]," + + "\"detail_imgs\":[\"https://example.com/detail.jpg\"],\"title\":\"测试商品\"}"); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getErrMsg(), "ok"); + assertEquals(response.getBrandId(), Long.valueOf(2_100_000_000L)); + assertEquals(response.getBrandNameChinese(), "品牌"); + assertEquals(response.getBrandNameEnglish(), "brand"); + } + + @Test + public void shouldMapExternalProductAttributeAndDecodeResponse() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"," + + "\"external_attribute_name\":\"帮面材质\",\"external_attribute_value\":\"塑胶\"," + + "\"internal_attribute_name\":\"鞋面材质\",\"internal_attribute_value\":[\"塑胶\"]}"; + ExternalProductMappingParam param = new ExternalProductMappingParam(); + param.setCatId(6261L); + param.setExternalAttributeName("帮面材质"); + param.setExternalAttributeValue("塑胶"); + param.setExternalCategoryName("母婴:童鞋:雨鞋"); + + ExternalProductMappingResponse response = + channelService.getProductAssistantService().externalProductMapping(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/externalproductmapping"); + assertEquals(channelService.request, + "{\"cat_id\":6261,\"external_attribute_name\":\"帮面材质\"," + + "\"external_attribute_value\":\"塑胶\",\"external_category_name\":\"母婴:童鞋:雨鞋\"}"); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getExternalAttributeName(), "帮面材质"); + assertEquals(response.getExternalAttributeValue(), "塑胶"); + assertEquals(response.getInternalAttributeName(), "鞋面材质"); + assertEquals(response.getInternalAttributeValue(), Arrays.asList("塑胶")); + } + + @Test + public void shouldMapMultipleExternalProductAttributesAndDecodeResponse() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"," + + "\"attributes\":[{\"key\":\"鞋面材质\",\"value\":\"塑胶\"}]}"; + ExternalAttribute externalAttribute = new ExternalAttribute(); + externalAttribute.setKey("帮面材质"); + externalAttribute.setValue("塑胶"); + ExternalProductMappingNewParam param = new ExternalProductMappingNewParam(); + param.setCatId(6000L); + param.setExternalCategoryName("母婴:童鞋:雨鞋"); + param.setHeadImgs(Arrays.asList("https://example.com/head.jpg")); + param.setDetailImgs(Arrays.asList("https://example.com/detail.jpg")); + param.setTitle("测试商品"); + param.setExternalAttributes(Arrays.asList(externalAttribute)); + + ExternalProductMappingNewResponse response = + channelService.getProductAssistantService().externalProductMappingNew(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/externalproductmappingnew"); + assertEquals(channelService.request, + "{\"cat_id\":6000,\"external_category_name\":\"母婴:童鞋:雨鞋\"," + + "\"head_imgs\":[\"https://example.com/head.jpg\"]," + + "\"detail_imgs\":[\"https://example.com/detail.jpg\"],\"title\":\"测试商品\"," + + "\"external_attributes\":[{\"key\":\"帮面材质\",\"value\":\"塑胶\"}]}"); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getAttributes().size(), 1); + assertEquals(response.getAttributes().get(0).getKey(), "鞋面材质"); + assertEquals(response.getAttributes().get(0).getValue(), "塑胶"); + } + + @Test + public void shouldBeginTimingSaleWithStringIdentifiers() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"}"; + BeginTimingSaleParam param = new BeginTimingSaleParam(); + param.setProductId("9007199254740993"); + param.setTaskId("000123456789"); + + WxChannelBaseResponse response = channelService.getProductAssistantService().beginTimingSale(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/begintimingsale"); + assertEquals(channelService.request, + "{\"product_id\":\"9007199254740993\",\"task_id\":\"000123456789\"}"); + assertTrue(response.isSuccess()); + } + + @Test + public void shouldCancelTimingSaleWithStringProductIdentifier() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"}"; + CancelTimingSaleParam param = new CancelTimingSaleParam(); + param.setProductId("9007199254740993"); + + WxChannelBaseResponse response = channelService.getProductAssistantService().cancelTimingSale(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/canceltimingsale"); + assertEquals(channelService.request, "{\"product_id\":\"9007199254740993\"}"); + assertTrue(response.isSuccess()); + } + + private static class CapturingChannelService extends WxChannelServiceImpl { + private String url; + private String request; + private String response; + + @Override + public String post(String url, String postData) { + this.url = url; + this.request = postData; + return this.response; + } + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductManagementServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductManagementServiceImplTest.java new file mode 100644 index 0000000000..9ccaeed3d7 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductManagementServiceImplTest.java @@ -0,0 +1,283 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.AddProductThirdPartySourceParam; +import me.chanjar.weixin.channel.bean.product.AddProductThirdPartySourceResponse; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingNewParam; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingNewResponse; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingParam; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditQuotaResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategyInfo; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategyResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategySetParam; +import me.chanjar.weixin.channel.bean.product.ProductBrandRecommendParam; +import me.chanjar.weixin.channel.bean.product.ProductBrandRecommendResponse; +import me.chanjar.weixin.channel.bean.product.ProductCategoryClassifyParam; +import me.chanjar.weixin.channel.bean.product.ProductCategoryClassifyResponse; +import me.chanjar.weixin.channel.bean.product.ProductCategoryPreCheckParam; +import me.chanjar.weixin.channel.bean.product.ProductCategoryPreCheckResponse; +import me.chanjar.weixin.channel.bean.product.ProductSchemeParam; +import me.chanjar.weixin.channel.bean.product.ProductSchemeResponse; +import me.chanjar.weixin.channel.bean.product.ProductStockFlowParam; +import me.chanjar.weixin.channel.bean.product.ProductStockFlowResponse; +import me.chanjar.weixin.channel.bean.product.ProductTimingSaleParam; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Test; + +/** Tests the product-management endpoint contracts without making network calls. */ +public class WxChannelProductManagementServiceImplTest { + + @Test + public void shouldGetProductScheme() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"openlink\":\"weixin://dl/business/?t=abc\"}"); + ProductSchemeParam param = new ProductSchemeParam(); + param.setProductId("10001"); + param.setFromAppid("wx-app"); + param.setExpire(3600); + param.setExtInfo("source"); + + ProductSchemeResponse result = productService(channelService).getProductScheme(param); + + assertRequest(channelService, "/channels/ec/product/scheme/get", "{\"product_id\":\"10001\",\"from_appid\":\"wx-app\",\"expire\":3600,\"ext_info\":\"source\"}"); + assertEquals(result.getOpenlink(), "weixin://dl/business/?t=abc"); + } + + @Test + public void shouldClassifyProductCategoryAndDecodePermission() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"categories\":[{\"cats\":[{\"cat_info\":{\"cat_id\":\"6000\",\"cat_name\":\"童鞋\"},\"has_permission\":true}]}],\"wrong_cat\":false}"); + ProductCategoryClassifyParam param = new ProductCategoryClassifyParam(); + param.setReqType(2); + param.setTitle("儿童雨鞋"); + param.setHeadImgs(Arrays.asList("https://example.com/image")); + param.setCatId("6000"); + + ProductCategoryClassifyResponse result = productService(channelService).classifyProductCategory(param); + + assertRequest(channelService, "/channels/ec/product/category/classify", "{\"req_type\":2,\"title\":\"儿童雨鞋\",\"head_imgs\":[\"https://example.com/image\"],\"cat_id\":\"6000\"}"); + assertEquals(result.getCategories().get(0).getCats().get(0).getCatInfo().getCatId(), "6000"); + assertTrue(result.getCategories().get(0).getCats().get(0).getHasPermission()); + } + + @Test + public void shouldBeginTimingSale() throws WxErrorException { + RecordingChannelService channelService = response(okResponse()); + ProductTimingSaleParam param = new ProductTimingSaleParam(); + param.setProductId("10001"); + param.setTaskId(123L); + + WxChannelBaseResponse result = productService(channelService).beginTimingSale(param); + + assertRequest(channelService, "/channels/ec/product/begintimingsale", "{\"product_id\":\"10001\",\"task_id\":123}"); + assertTrue(result.isSuccess()); + } + + @Test + public void shouldCancelTimingSale() throws WxErrorException { + RecordingChannelService channelService = response(okResponse()); + + WxChannelBaseResponse result = productService(channelService).cancelTimingSale("10001"); + + assertRequest(channelService, "/channels/ec/product/canceltimingsale", "{\"product_id\":\"10001\"}"); + assertTrue(result.isSuccess()); + } + + @Test + public void shouldMapExternalProductAttribute() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"external_attribute_name\":\"材质\",\"external_attribute_value\":\"塑胶\",\"internal_attribute_name\":\"鞋面材质\",\"internal_attribute_value\":[\"塑胶\"]}"); + ExternalProductMappingParam param = new ExternalProductMappingParam(); + param.setCatId(6000L); + param.setExternalAttributeName("材质"); + param.setExternalAttributeValue("塑胶"); + param.setExternalCategoryName("母婴:童鞋"); + + ExternalProductMappingResponse result = productService(channelService).externalProductMapping(param); + + assertRequest(channelService, "/channels/ec/product/externalproductmapping", "{\"cat_id\":6000,\"external_attribute_name\":\"材质\",\"external_attribute_value\":\"塑胶\",\"external_category_name\":\"母婴:童鞋\"}"); + assertEquals(result.getInternalAttributeValue(), Arrays.asList("塑胶")); + } + + @Test + public void shouldPreCheckCategory() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"all_pass\":false,\"fail_reasons\":[\"缺少资质\"]}"); + ProductCategoryPreCheckParam param = new ProductCategoryPreCheckParam(); + param.setCatId(6000L); + + ProductCategoryPreCheckResponse result = productService(channelService).categoryPreCheck(param); + + assertRequest(channelService, "/channels/ec/product/categoryprecheck", "{\"cat_id\":6000}"); + assertEquals(result.getFailReasons(), Arrays.asList("缺少资质")); + } + + @Test + public void shouldGetProductAuditStrategy() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"audit_strategy\":{\"hide_err_field_flag\":1,\"hit_duplicated_flag\":0,\"hit_low_risk_rule_flag\":1}}"); + + ProductAuditStrategyResponse result = productService(channelService).getProductAuditStrategy(); + + assertRequest(channelService, "/channels/ec/product/auditstrategy/get", "{}"); + assertEquals(result.getAuditStrategy().getHideErrFieldFlag(), Integer.valueOf(1)); + } + + @Test + public void shouldSetProductAuditStrategy() throws WxErrorException { + RecordingChannelService channelService = response(okResponse()); + ProductAuditStrategyInfo strategy = new ProductAuditStrategyInfo(); + strategy.setHideErrFieldFlag(1); + ProductAuditStrategySetParam param = new ProductAuditStrategySetParam(); + param.setAuditStrategy(strategy); + + WxChannelBaseResponse result = productService(channelService).setProductAuditStrategy(param); + + assertRequest(channelService, "/channels/ec/product/auditstrategy/set", "{\"audit_strategy\":{\"hide_err_field_flag\":1}}"); + assertTrue(result.isSuccess()); + } + + @Test + public void shouldGetProductAuditQuota() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"audit_quota\":{\"block_status\":0,\"avail_quota\":20,\"total_quota\":100,\"unlimited_type\":0,\"audit_total_quota\":100,\"audit_total_remaining\":80,\"new_product_total_quota\":50,\"new_product_remaining\":40}}"); + + ProductAuditQuotaResponse result = productService(channelService).getProductAuditQuota(); + + assertRequest(channelService, "/channels/ec/product/getauditquota", "{}"); + assertEquals(result.getAuditQuota().getNewProductRemaining(), Integer.valueOf(40)); + } + + @Test + public void shouldMapAndRecommendExternalProductAttributes() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"attributes\":[{\"key\":\"鞋面材质\",\"value\":\"塑胶\"}]}"); + ExternalProductMappingNewParam.ExternalAttribute attribute = new ExternalProductMappingNewParam.ExternalAttribute(); + attribute.setKey("材质"); + attribute.setValue("塑胶"); + ExternalProductMappingNewParam param = new ExternalProductMappingNewParam(); + param.setCatId(6000L); + param.setExternalCategoryName("母婴:童鞋"); + param.setHeadImgs(Arrays.asList("https://example.com/head")); + param.setTitle("儿童雨鞋"); + param.setExternalAttributes(Arrays.asList(attribute)); + + ExternalProductMappingNewResponse result = productService(channelService).externalProductMappingNew(param); + + assertRequest(channelService, "/channels/ec/product/externalproductmappingnew", "{\"cat_id\":6000,\"external_category_name\":\"母婴:童鞋\",\"head_imgs\":[\"https://example.com/head\"],\"title\":\"儿童雨鞋\",\"external_attributes\":[{\"key\":\"材质\",\"value\":\"塑胶\"}]}"); + assertTrue(result.getAttributes().get(0) instanceof ExternalProductMappingNewResponse.Attribute); + assertEquals(result.getAttributes().get(0).getKey(), "鞋面材质"); + } + + @Test + public void shouldRecommendProductBrand() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"brand_id\":123,\"brand_name_chinese\":\"品牌\",\"brand_name_english\":\"Brand\"}"); + ProductBrandRecommendParam param = new ProductBrandRecommendParam(); + param.setCatId(6000L); + param.setHeadImgs(Arrays.asList("https://example.com/head")); + param.setTitle("商品"); + + ProductBrandRecommendResponse result = productService(channelService).productBrandRecommend(param); + + assertRequest(channelService, "/channels/ec/product/productbrandrecommend", "{\"cat_id\":6000,\"head_imgs\":[\"https://example.com/head\"],\"title\":\"商品\"}"); + assertEquals(result.getBrandId(), Long.valueOf(123)); + } + + @Test + public void shouldAddThirdPartyProductSource() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"third_party_source_id\":12345}"); + AddProductThirdPartySourceParam param = new AddProductThirdPartySourceParam(); + param.setSceneValue(1); + param.setPublishMethod(2); + + AddProductThirdPartySourceResponse result = productService(channelService).addProductThirdPartySource(param); + + assertRequest(channelService, "/channels/ec/product/addproductthirdpartysource", "{\"scene_value\":1,\"publish_method\":2}"); + assertEquals(result.getThirdPartySourceId(), Long.valueOf(12345)); + } + + @Test + public void shouldGetStockFlowWithRequiredPaginationAndStockTypeId() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"data\":{\"stock_flow_info_list\":[{\"amount\":2}],\"next_key\":\"next\"}}"); + ProductStockFlowParam param = new ProductStockFlowParam(); + param.setProductId("10001"); + param.setSkuId("10002"); + param.setStockType(2); + param.setStockTypeId("activity-1"); + param.setBeginTime(100L); + param.setEndTime(200L); + param.setPageSize(10); + + ProductStockFlowResponse result = productService(channelService).getStockFlow(param); + + assertRequest(channelService, "/channels/ec/product/stock/getflow", "{\"product_id\":\"10001\",\"sku_id\":\"10002\",\"stock_type\":2,\"begin_time\":100,\"end_time\":200,\"page_size\":10,\"stock_type_id\":\"activity-1\"}"); + assertEquals(result.getData().getStockFlowInfoList().size(), 1); + assertEquals(result.getData().getNextKey(), "next"); + } + + @Test + public void shouldSerializeNestedProductManagementResponses() throws IOException { + ProductAuditQuotaResponse.AuditQuota auditQuota = new ProductAuditQuotaResponse.AuditQuota(); + ProductCategoryClassifyResponse.Category category = new ProductCategoryClassifyResponse.Category(); + ProductCategoryClassifyResponse.CategoryLevel categoryLevel = new ProductCategoryClassifyResponse.CategoryLevel(); + ProductCategoryClassifyResponse.CategoryInfo categoryInfo = new ProductCategoryClassifyResponse.CategoryInfo(); + ProductStockFlowResponse.StockFlowData stockFlowData = new ProductStockFlowResponse.StockFlowData(); + + assertSerializable(auditQuota); + assertSerializable(category); + assertSerializable(categoryLevel); + assertSerializable(categoryInfo); + assertSerializable(stockFlowData); + } + + private static WxChannelProductServiceImpl productService(RecordingChannelService channelService) { + return new WxChannelProductServiceImpl(channelService); + } + + private static RecordingChannelService response(String response) { + return new RecordingChannelService(response); + } + + private static String okResponse() { + return "{\"errcode\":0,\"errmsg\":\"ok\"}"; + } + + private static void assertRequest(RecordingChannelService channelService, String path, String expectedJson) { + assertTrue(channelService.getUrl().endsWith(path)); + assertEquals(JsonUtils.decode(channelService.getRequestJson(), Object.class), JsonUtils.decode(expectedJson, Object.class)); + } + + private static void assertSerializable(Object value) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try (ObjectOutputStream objectOutput = new ObjectOutputStream(output)) { + objectOutput.writeObject(value); + } + } + + private static class RecordingChannelService extends WxChannelServiceHttpClientImpl { + private final String response; + private String url; + private String requestJson; + + RecordingChannelService(String response) { + this.response = response; + } + + @Override + public String post(String url, String postData) { + this.url = url; + this.requestJson = postData; + return response; + } + + String getUrl() { + return url; + } + + String getRequestJson() { + return requestJson; + } + } +} 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 index b14bc9b3fc..cd5042314b 100644 --- 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 @@ -4,17 +4,30 @@ 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.GiftActivityAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftActivityInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductGetResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductListParam; +import me.chanjar.weixin.channel.bean.product.GiftProductListResponse; +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.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.channel.test.ApiTestModule; import me.chanjar.weixin.common.error.WxErrorException; import org.testng.annotations.Guice; @@ -32,7 +45,7 @@ public class WxChannelProductServiceImplTest { @Test public void testAddProduct() throws WxErrorException { WxChannelProductService productService = channelService.getProductService(); - SpuInfo spuInfo = new SpuInfo(); + SpuUpdateInfo spuInfo = new SpuUpdateInfo(); // ... SpuUpdateResponse response = productService.addProduct(spuInfo); assertNotNull(response); @@ -42,7 +55,7 @@ public void testAddProduct() throws WxErrorException { @Test public void testUpdateProduct() throws WxErrorException { WxChannelProductService productService = channelService.getProductService(); - SpuInfo spuInfo = new SpuInfo(); + SpuUpdateInfo spuInfo = new SpuUpdateInfo(); // ... SpuUpdateResponse response = productService.updateProduct(spuInfo); assertNotNull(response); @@ -121,13 +134,53 @@ public void testDownProduct() throws WxErrorException { @Test public void testGetSkuStock() throws WxErrorException { WxChannelProductService productService = channelService.getProductService(); - String productId = ""; - String skuId = ""; + 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(); @@ -166,4 +219,80 @@ public void testDeleteLimitTask() throws WxErrorException { assertNotNull(response); assertTrue(response.isSuccess()); } + + @Test + public void testAddGiftProduct() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + GiftProductInfo info = new GiftProductInfo(); + GiftProductAddResponse response = productService.addGiftProduct(info); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateGiftProduct() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + GiftProductInfo info = new GiftProductInfo(); + WxChannelBaseResponse response = productService.updateGiftProduct(info); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testSetProductAsGift() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + WxChannelBaseResponse response = productService.setProductAsGift(""); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetGiftProduct() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + GiftProductGetResponse response = productService.getGiftProduct(""); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testListGiftProduct() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + GiftProductListParam param = new GiftProductListParam(10, null, null); + GiftProductListResponse response = productService.listGiftProduct(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testUpdateGiftStock() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + WxChannelBaseResponse response = productService.updateGiftStock("", "", 1, 10); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testAddGiftActivity() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + GiftActivityInfo info = new GiftActivityInfo(); + GiftActivityAddResponse response = productService.addGiftActivity(info); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testDeleteGiftActivity() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + WxChannelBaseResponse response = productService.deleteGiftActivity(""); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testStopGiftActivity() throws WxErrorException { + WxChannelProductService productService = channelService.getProductService(); + WxChannelBaseResponse response = productService.stopGiftActivity(""); + assertNotNull(response); + assertTrue(response.isSuccess()); + } } diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImplTest.java new file mode 100644 index 0000000000..86c26043fb --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImplTest.java @@ -0,0 +1,81 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertEquals; + +import java.util.Arrays; +import me.chanjar.weixin.channel.bean.product.stock.StockFlowInfo; +import me.chanjar.weixin.channel.bean.product.stock.StockFlowParam; +import me.chanjar.weixin.channel.bean.product.stock.StockFlowResponse; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Test; + +/** + * Tests for {@link WxChannelProductStockServiceImpl}. + */ +public class WxChannelProductStockServiceImplTest { + + @Test + public void shouldGetStockFlowAndDecodeResponse() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\",\"data\":{" + + "\"stock_flow_info_list\":[{\"amount\":300,\"beginning_amount\":842," + + "\"ending_amount\":542,\"stock_sub_type\":1,\"op_type\":6," + + "\"update_time\":1689735682,\"ext_info\":{\"unmove_from_stock_sub_type\":3," + + "\"move_to_stock_sub_type\":4," + + "\"upload_source\":2,\"order_id\":\"order-id\"," + + "\"out_warehouse_id\":\"warehouse-id\"," + + "\"limited_discount_id\":\"discount-id\",\"finder_id\":\"finder-id\"}}]," + + "\"next_key\":\"next-page\"}}"; + StockFlowParam param = new StockFlowParam(); + param.setProductId("product-id"); + param.setSkuId("sku-id"); + param.setStockType(1); + param.setFinderId("finder-id"); + param.setBeginTime(1_689_218_360L); + param.setEndTime(1_689_736_760L); + param.setOpTypeList(Arrays.asList(1, 2)); + param.setPageSize(10); + param.setNextKey("current-page"); + param.setStockTypeId("stock-type-id"); + + StockFlowResponse response = channelService.getProductStockService().getStockFlow(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/stock/getflow"); + assertEquals(channelService.request, + "{\"product_id\":\"product-id\",\"sku_id\":\"sku-id\",\"stock_type\":1," + + "\"finder_id\":\"finder-id\",\"begin_time\":1689218360," + + "\"end_time\":1689736760,\"op_type_list\":[1,2],\"page_size\":10," + + "\"next_key\":\"current-page\",\"stock_type_id\":\"stock-type-id\"}"); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getErrMsg(), "ok"); + assertEquals(response.getNextKey(), "next-page"); + assertEquals(response.getStockFlowInfoList().size(), 1); + StockFlowInfo flow = response.getStockFlowInfoList().get(0); + assertEquals(flow.getAmount(), Integer.valueOf(300)); + assertEquals(flow.getBeginningAmount(), Integer.valueOf(842)); + assertEquals(flow.getEndingAmount(), Integer.valueOf(542)); + assertEquals(flow.getStockSubType(), Integer.valueOf(1)); + assertEquals(flow.getOpType(), Integer.valueOf(6)); + assertEquals(flow.getUpdateTime(), Long.valueOf(1_689_735_682L)); + assertEquals(flow.getExtInfo().getUnmoveFromStockSubType(), Integer.valueOf(3)); + assertEquals(flow.getExtInfo().getMoveToStockSubType(), Integer.valueOf(4)); + assertEquals(flow.getExtInfo().getUploadSource(), Integer.valueOf(2)); + assertEquals(flow.getExtInfo().getOrderId(), "order-id"); + assertEquals(flow.getExtInfo().getOutWarehouseId(), "warehouse-id"); + assertEquals(flow.getExtInfo().getLimitedDiscountId(), "discount-id"); + assertEquals(flow.getExtInfo().getFinderId(), "finder-id"); + } + + private static class CapturingChannelService extends WxChannelServiceImpl { + private String url; + private String request; + private String response; + + @Override + public String post(String url, String postData) { + this.url = url; + this.request = postData; + return this.response; + } + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelQicServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelQicServiceImplTest.java new file mode 100644 index 0000000000..3a2b657bc5 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelQicServiceImplTest.java @@ -0,0 +1,93 @@ +package me.chanjar.weixin.channel.api.impl; + +import com.google.inject.Inject; +import me.chanjar.weixin.channel.api.WxChannelQicService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.qic.InspectCodeResponse; +import me.chanjar.weixin.channel.bean.qic.InspectConfigResponse; +import me.chanjar.weixin.channel.bean.qic.RegisterLogisticsRequest; +import me.chanjar.weixin.channel.bean.qic.SubmitConfigResponse; +import me.chanjar.weixin.channel.bean.qic.SubmitInspectRequest; +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.util.Collections; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +@Guice(modules = ApiTestModule.class) +public class WxChannelQicServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testGetInspectConfig() throws WxErrorException { + WxChannelQicService qicService = channelService.getQicService(); + InspectConfigResponse response = qicService.getInspectConfig(); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetSubmitConfig() throws WxErrorException { + WxChannelQicService qicService = channelService.getQicService(); + SubmitConfigResponse response = qicService.getSubmitConfig("123456"); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testPrintInspectCode() throws WxErrorException { + WxChannelQicService qicService = channelService.getQicService(); + InspectCodeResponse response = qicService.printInspectCode("123456"); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testSubmitInspectInfo() throws WxErrorException { + WxChannelQicService qicService = channelService.getQicService(); + SubmitInspectRequest request = new SubmitInspectRequest(); + request.setOrderId("123456"); + SubmitInspectRequest.InspectInfo inspectInfo = new SubmitInspectRequest.InspectInfo(); + inspectInfo.setDeliveryId("YTO"); + inspectInfo.setBackupDeliveryId("SF"); + inspectInfo.setExpressInsure(Boolean.FALSE); + inspectInfo.setExpressMerge(Boolean.FALSE); + inspectInfo.setInspectOrgId("ORG_1"); + inspectInfo.setRefundIntercept(0); + inspectInfo.setInspectOrgName("机构A"); + inspectInfo.setWarehouseName("质检仓"); + inspectInfo.setWarehouseAddr("质检仓地址"); + inspectInfo.setDeliveryProductId(1L); + inspectInfo.setBackupDeliveryProductId(2L); + request.setInspectInfo(inspectInfo); + + WxChannelBaseResponse response = qicService.submitInspectInfo(request); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testRegisterLogistics() throws WxErrorException { + WxChannelQicService qicService = channelService.getQicService(); + RegisterLogisticsRequest request = new RegisterLogisticsRequest(); + request.setOrderIdList(Collections.singletonList("123456")); + + RegisterLogisticsRequest.LogisticsInfo logisticsInfo = new RegisterLogisticsRequest.LogisticsInfo(); + logisticsInfo.setWaybillId("YT1234567890"); + logisticsInfo.setDeliveryId("YTO"); + logisticsInfo.setDeliveryName("圆通速递"); + logisticsInfo.setDeliveryType(1); + request.setLogisticsInfo(logisticsInfo); + + WxChannelBaseResponse response = qicService.registerLogistics(request); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java new file mode 100644 index 0000000000..a4bb5db6c2 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java @@ -0,0 +1,154 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.limit.LimitSku; +import me.chanjar.weixin.channel.bean.limit.LimitTaskParam; +import me.chanjar.weixin.channel.bean.product.GiftProductInfo; +import org.testng.annotations.Test; + +/** + * Verifies domain service accessors and product service compatibility delegates. + */ +public class WxChannelServiceImplTest { + + @Test + public void shouldKeepNewDomainServiceAccessorsCompatibleWithExistingImplementations() throws Exception { + assertDefaultMethod("getGiftService"); + assertDefaultMethod("getLimitedDiscountService"); + assertDefaultMethod("getProductStockService"); + assertDefaultMethod("getProductAssistantService"); + } + + @Test + public void shouldExposeProductDomainServices() { + CapturingChannelService channelService = new CapturingChannelService(); + assertNotNull(channelService.getGiftService()); + assertNotNull(channelService.getLimitedDiscountService()); + assertNotNull(channelService.getProductStockService()); + assertNotNull(channelService.getProductAssistantService()); + } + + @Test + public void shouldRouteGiftProductCallsExactlyOnce() throws Exception { + CapturingChannelService channelService = new CapturingChannelService(); + GiftProductInfo info = new GiftProductInfo(); + info.setListing(1); + + assertRequest(channelService, new RequestCall() { + @Override + public void call() throws Exception { + channelService.getGiftService().addGiftProduct(info); + } + }, "https://api.weixin.qq.com/channels/ec/product/gift/add", "{\"listing\":1}"); + assertRequest(channelService, new RequestCall() { + @Override + public void call() throws Exception { + channelService.getProductService().addGiftProduct(info); + } + }, "https://api.weixin.qq.com/channels/ec/product/gift/add", "{\"listing\":1}"); + } + + @Test + public void shouldRouteLimitedDiscountCallsExactlyOnce() throws Exception { + CapturingChannelService channelService = new CapturingChannelService(); + LimitTaskParam param = new LimitTaskParam(); + param.setProductId("product-id"); + param.setStartTime(new Date(1_000)); + param.setEndTime(new Date(2_000)); + param.setSkus(Arrays.asList(new LimitSku("sku-id", 100, 2))); + + assertRequest(channelService, new RequestCall() { + @Override + public void call() throws Exception { + channelService.getLimitedDiscountService().addLimitTask(param); + } + }, "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/add", + "{\"product_id\":\"product-id\",\"start_time\":1000,\"end_time\":2000," + + "\"limited_discount_skus\":[{\"sku_id\":\"sku-id\",\"sale_price\":100,\"sale_stock\":2}]}"); + assertRequest(channelService, new RequestCall() { + @Override + public void call() throws Exception { + channelService.getProductService().addLimitTask(param); + } + }, "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/add", + "{\"product_id\":\"product-id\",\"start_time\":1000,\"end_time\":2000," + + "\"limited_discount_skus\":[{\"sku_id\":\"sku-id\",\"sale_price\":100,\"sale_stock\":2}]}"); + } + + @Test + public void shouldRouteStockCallsExactlyOnce() throws Exception { + CapturingChannelService channelService = new CapturingChannelService(); + + assertRequest(channelService, new RequestCall() { + @Override + public void call() throws Exception { + channelService.getProductStockService().updateStock("product-id", "sku-id", 1, 2); + } + }, "https://api.weixin.qq.com/channels/ec/product/stock/update", + "{\"product_id\":\"product-id\",\"sku_id\":\"sku-id\",\"diff_type\":1,\"num\":2}"); + assertRequest(channelService, new RequestCall() { + @Override + public void call() throws Exception { + channelService.getProductService().updateStock("product-id", "sku-id", 1, 2); + } + }, "https://api.weixin.qq.com/channels/ec/product/stock/update", + "{\"product_id\":\"product-id\",\"sku_id\":\"sku-id\",\"diff_type\":1,\"num\":2}"); + } + + private void assertDefaultMethod(String methodName) throws Exception { + Method method = WxChannelService.class.getMethod(methodName); + assertTrue(method.isDefault(), methodName + " must remain compatible with existing implementations"); + } + + private void assertRequest(CapturingChannelService channelService, RequestCall call, + String expectedUrl, String expectedJson) throws Exception { + channelService.clearRequests(); + call.call(); + List requests = channelService.getRequests(); + assertEquals(requests.size(), 1); + assertEquals(requests.get(0).url, expectedUrl); + assertEquals(requests.get(0).json, expectedJson); + } + + private interface RequestCall { + void call() throws Exception; + } + + private static class CapturingChannelService extends WxChannelServiceImpl { + private final List requests = new ArrayList<>(); + + @Override + public String post(String url, String postData) { + this.requests.add(new Request(url, postData)); + return "{\"errcode\":0}"; + } + + private List getRequests() { + return new ArrayList<>(this.requests); + } + + private void clearRequests() { + this.requests.clear(); + } + } + + private static class Request { + private final String url; + private final String json; + + private Request(String url, String json) { + this.url = url; + this.json = json; + } + + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelShopLinkServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelShopLinkServiceImplTest.java new file mode 100644 index 0000000000..87785a5396 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelShopLinkServiceImplTest.java @@ -0,0 +1,56 @@ +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.WxChannelBasicService; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.shop.ShopH5UrlResponse; +import me.chanjar.weixin.channel.bean.shop.ShopQrCodeResponse; +import me.chanjar.weixin.channel.bean.shop.ShopTagLinkResponse; +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 GitHub Copilot + */ +@Guice(modules = ApiTestModule.class) +public class WxChannelShopLinkServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testGetShopH5Url() throws WxErrorException { + WxChannelBasicService basicService = channelService.getBasicService(); + ShopH5UrlResponse response = basicService.getShopH5Url(); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testGetShopQrCode() throws WxErrorException { + WxChannelBasicService basicService = channelService.getBasicService(); + // qrcodeType: 1=二维码, 2=标准物料, 3=送礼物物料 + ShopQrCodeResponse response = basicService.getShopQrCode(1); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } + + @Test + public void testGetShopTagLink() throws WxErrorException { + WxChannelBasicService basicService = channelService.getBasicService(); + ShopTagLinkResponse response = basicService.getShopTagLink(); + assertNotNull(response); + assertTrue(response.isSuccess()); + System.out.println(JsonUtils.encode(response)); + } +} 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/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/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/api/impl/WxTalentServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxTalentServiceImplTest.java new file mode 100644 index 0000000000..2e0b5ba781 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxTalentServiceImplTest.java @@ -0,0 +1,70 @@ +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.WxTalentService; +import me.chanjar.weixin.channel.bean.talent.TalentOrderDetailParam; +import me.chanjar.weixin.channel.bean.talent.TalentOrderDetailResponse; +import me.chanjar.weixin.channel.bean.talent.TalentOrderListParam; +import me.chanjar.weixin.channel.bean.talent.TalentOrderListResponse; +import me.chanjar.weixin.channel.bean.talent.TalentWindowProductDetailParam; +import me.chanjar.weixin.channel.bean.talent.TalentWindowProductDetailResponse; +import me.chanjar.weixin.channel.bean.talent.TalentWindowProductListParam; +import me.chanjar.weixin.channel.bean.talent.TalentWindowProductListResponse; +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 GitHub Copilot + */ +@Guice(modules = ApiTestModule.class) +public class WxTalentServiceImplTest { + + @Inject + private WxChannelService channelService; + + @Test + public void testGetOrderList() throws WxErrorException { + WxTalentService talentService = channelService.getTalentService(); + TalentOrderListParam param = new TalentOrderListParam(); + TalentOrderListResponse response = talentService.getOrderList(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetOrderDetail() throws WxErrorException { + WxTalentService talentService = channelService.getTalentService(); + TalentOrderDetailParam param = new TalentOrderDetailParam(); + param.setOrderId(""); + TalentOrderDetailResponse response = talentService.getOrderDetail(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetWindowProductList() throws WxErrorException { + WxTalentService talentService = channelService.getTalentService(); + TalentWindowProductListParam param = new TalentWindowProductListParam(); + TalentWindowProductListResponse response = talentService.getWindowProductList(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } + + @Test + public void testGetWindowProductDetail() throws WxErrorException { + WxTalentService talentService = channelService.getTalentService(); + TalentWindowProductDetailParam param = new TalentWindowProductDetailParam(); + param.setProductId(""); + TalentWindowProductDetailResponse response = talentService.getWindowProductDetail(param); + assertNotNull(response); + assertTrue(response.isSuccess()); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/after/AfterSaleContractTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/after/AfterSaleContractTest.java new file mode 100644 index 0000000000..b226cfb1bd --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/after/AfterSaleContractTest.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.after; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Collections; +import org.testng.annotations.Test; + +public class AfterSaleContractTest { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + @Test + public void shouldUseOfficialAfterSaleAndGuaranteeFieldNames() throws Exception { + AfterSaleGenAfterSaleOrderParam afterSaleParam = new AfterSaleGenAfterSaleOrderParam(); + ExchangeSkuInfo exchangeSkuInfo = new ExchangeSkuInfo(); + exchangeSkuInfo.setNewSkuId("new-sku"); + afterSaleParam.setExchangeSkuInfo(exchangeSkuInfo); + + AfterSaleHandleFastExchangeReceiptParam receiptParam = new AfterSaleHandleFastExchangeReceiptParam(); + receiptParam.setRejectConfirmExchange(Collections.singletonList("media-1")); + + GuaranteeOrderIdParam guaranteeParam = new GuaranteeOrderIdParam("guarantee-1"); + assertTrue(OBJECT_MAPPER.writeValueAsString(afterSaleParam).contains("\"exchange_sku_info\":{\"new_sku_id\":\"new-sku\"}")); + assertTrue(OBJECT_MAPPER.writeValueAsString(receiptParam).contains("\"reject_confirm_exchange\":[\"media-1\"]")); + assertTrue(OBJECT_MAPPER.writeValueAsString(guaranteeParam).contains("\"guarantee_order_id\":\"guarantee-1\"")); + } + + @Test + public void shouldDecodeOfficialResponseFields() throws Exception { + AfterSaleCreateResponse createResponse = OBJECT_MAPPER.readValue( + "{\"errcode\":0,\"after_sale_order_id\":\"after-1\"}", AfterSaleCreateResponse.class); + GuaranteeOrderResponse guaranteeResponse = OBJECT_MAPPER.readValue( + "{\"errcode\":0,\"guarantee_order\":{\"guarantee_order_id\":\"guarantee-1\"}}", GuaranteeOrderResponse.class); + + assertEquals(createResponse.getAfterSaleOrderId(), "after-1"); + assertEquals(guaranteeResponse.getGuaranteeOrder().get("guarantee_order_id").asText(), "guarantee-1"); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/ewaybill/PrintContentParamTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/ewaybill/PrintContentParamTest.java new file mode 100644 index 0000000000..9b2e234366 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/ewaybill/PrintContentParamTest.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.Arrays; +import me.chanjar.weixin.channel.util.JsonUtils; +import org.testng.annotations.Test; + +/** 电子面单打印请求 JSON 契约测试。 */ +public class PrintContentParamTest { + + @Test + public void shouldHaveNoArgsConstructor() { + PrintContentParam param = new PrintContentParam(); + param.setEwaybillOrderId("order_1"); + + assertTrue(JsonUtils.encode(param).contains("\"ewaybill_order_id\"")); + } + + @Test + public void shouldEncodeWaybillIdsAndOptionalTemplateId() { + String json = JsonUtils.encode(new PrintContentParam("order_1", "tpl_1")); + + assertTrue(json.contains("\"ewaybill_order_id\"")); + assertTrue(json.contains("\"template_id\":\"tpl_1\"")); + assertFalse(json.contains("\"waybill_id\"")); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/ewaybill/WxChannelEwaybillBeanTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/ewaybill/WxChannelEwaybillBeanTest.java new file mode 100644 index 0000000000..dcb79908bc --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/ewaybill/WxChannelEwaybillBeanTest.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.channel.bean.ewaybill; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.util.Arrays; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.channel.util.ResponseUtils; +import org.testng.annotations.Test; + +/** + * @author GitHub Copilot + */ +public class WxChannelEwaybillBeanTest { + + @Test + public void testTemplateIdParamEncode() { + TemplateIdParam param = new TemplateIdParam("tpl_1"); + String json = JsonUtils.encode(param); + assertNotNull(json); + assertTrue(json.contains("\"template_id\":\"tpl_1\"")); + } + + @Test + public void testTemplateCodeParamNoArgsConstructor() { + TemplateCodeParam param = new TemplateCodeParam(); + param.setTemplateCode("standard_tpl"); + + assertTrue(JsonUtils.encode(param).contains("\"template_code\":\"standard_tpl\"")); + } + + @Test + public void testWaybillIdsParamEncode() { + WaybillIdsParam param = new WaybillIdsParam(Arrays.asList("wb_1", "wb_2")); + String json = JsonUtils.encode(param); + assertNotNull(json); + assertTrue(json.contains("\"waybill_ids\"")); + assertTrue(json.contains("wb_1")); + assertTrue(json.contains("wb_2")); + } + + @Test + public void testDynamicRequestEncode() { + PreCreateRequest request = new PreCreateRequest(); + request.addParam("order_id", "o_1"); + request.addParam("package_quantity", 2); + + String json = JsonUtils.encode(request); + assertNotNull(json); + assertTrue(json.contains("\"order_id\":\"o_1\"")); + assertTrue(json.contains("\"package_quantity\":2")); + assertFalse(json.contains("\"params\"")); + } + + @Test + public void testDynamicResponseDecode() { + String json = "{\"errcode\":0,\"errmsg\":\"ok\",\"future_extension\":\"value\"}"; + PrintContentResponse response = ResponseUtils.decode(json, PrintContentResponse.class); + + assertNotNull(response); + assertTrue(response.isSuccess()); + assertEquals(response.getExtra().get("future_extension"), "value"); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfBeanTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfBeanTest.java new file mode 100644 index 0000000000..458cf44f25 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfBeanTest.java @@ -0,0 +1,141 @@ +package me.chanjar.weixin.channel.bean.kf; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import me.chanjar.weixin.channel.util.JsonUtils; +import org.testng.annotations.Test; + +/** JSON serialization tests for channel customer service models. */ +public class WxChannelKfBeanTest { + + @Test + public void testSendMsgParamJson() { + WxChannelKfSendMsgParam param = createSendMsgParam("text"); + WxChannelKfSendMsgParam.Text text = new WxChannelKfSendMsgParam.Text(); + text.setContent("hello"); + param.setText(text); + + String json = JsonUtils.encode(param); + assertNotNull(json); + assertFalse(json.contains("requestId")); + assertFalse(json.contains("openId")); + assertFalse(json.contains("msgType")); + WxChannelKfSendMsgParam decoded = JsonUtils.decode(json, WxChannelKfSendMsgParam.class); + assertEquals(decoded.getRequestId(), "request-1"); + assertEquals(decoded.getOpenId(), "open-1"); + assertEquals(decoded.getMsgType(), "text"); + assertNotNull(decoded.getText()); + assertEquals(decoded.getText().getContent(), "hello"); + } + + @Test + public void testImageMessageJson() { + WxChannelKfSendMsgParam param = createSendMsgParam("image"); + WxChannelKfSendMsgParam.CosUrlMessage image = new WxChannelKfSendMsgParam.CosUrlMessage(); + image.setCosUrl("https://example.test/image"); + param.setImage(image); + + assertCosUrlMessageJson(param, "image", "https://example.test/image"); + } + + @Test + public void testVideoMessageJson() { + WxChannelKfSendMsgParam param = createSendMsgParam("video"); + WxChannelKfSendMsgParam.CosUrlMessage video = new WxChannelKfSendMsgParam.CosUrlMessage(); + video.setCosUrl("https://example.test/video"); + param.setVideo(video); + + assertCosUrlMessageJson(param, "video", "https://example.test/video"); + } + + @Test + public void testFileMessageJson() { + WxChannelKfSendMsgParam param = createSendMsgParam("file"); + WxChannelKfSendMsgParam.CosUrlMessage file = new WxChannelKfSendMsgParam.CosUrlMessage(); + file.setCosUrl("https://example.test/file"); + param.setFile(file); + + assertCosUrlMessageJson(param, "file", "https://example.test/file"); + } + + @Test + public void testProductShareMessageJson() { + WxChannelKfSendMsgParam param = createSendMsgParam("product_share"); + WxChannelKfSendMsgParam.ProductShareMessage product = + new WxChannelKfSendMsgParam.ProductShareMessage(); + product.setProductId("product-1"); + param.setProductShare(product); + + String json = JsonUtils.encode(param); + assertTrue(json.contains("\"product_share\":{\"product_id\":\"product-1\"}")); + assertFalse(json.contains("productShare")); + assertFalse(json.contains("productId")); + WxChannelKfSendMsgParam decoded = JsonUtils.decode(json, WxChannelKfSendMsgParam.class); + assertEquals(decoded.getProductShare().getProductId(), "product-1"); + } + + @Test + public void testOrderShareMessageJson() { + WxChannelKfSendMsgParam param = createSendMsgParam("order_share"); + WxChannelKfSendMsgParam.OrderShareMessage order = new WxChannelKfSendMsgParam.OrderShareMessage(); + order.setOrderId("order-1"); + param.setOrderShare(order); + + String json = JsonUtils.encode(param); + assertTrue(json.contains("\"order_share\":{\"order_id\":\"order-1\"}")); + assertFalse(json.contains("orderShare")); + assertFalse(json.contains("orderId")); + WxChannelKfSendMsgParam decoded = JsonUtils.decode(json, WxChannelKfSendMsgParam.class); + assertEquals(decoded.getOrderShare().getOrderId(), "order-1"); + } + + @Test + public void testCosUploadResponseJson() { + WxChannelKfCosUploadResponse response = JsonUtils.decode( + "{\"errcode\":0,\"errmsg\":\"ok\",\"cos_url\":\"https://example.test/media\"}", + WxChannelKfCosUploadResponse.class); + + assertEquals(response.getErrCode(), 0); + assertEquals(response.getCosUrl(), "https://example.test/media"); + assertEquals(JsonUtils.decode(JsonUtils.encode(response), WxChannelKfCosUploadResponse.class) + .getCosUrl(), "https://example.test/media"); + } + + @Test + public void testSendMsgResponseJson() { + WxChannelKfSendMsgResponse response = JsonUtils.decode( + "{\"errcode\":0,\"errmsg\":\"ok\",\"msg_id\":\"msg-1\"}", + WxChannelKfSendMsgResponse.class); + + assertEquals(response.getErrCode(), 0); + assertEquals(response.getMsgId(), "msg-1"); + assertEquals(JsonUtils.decode(JsonUtils.encode(response), WxChannelKfSendMsgResponse.class) + .getMsgId(), "msg-1"); + } + + private WxChannelKfSendMsgParam createSendMsgParam(String msgType) { + WxChannelKfSendMsgParam param = new WxChannelKfSendMsgParam(); + param.setRequestId("request-1"); + param.setOpenId("open-1"); + param.setMsgType(msgType); + return param; + } + + private void assertCosUrlMessageJson(WxChannelKfSendMsgParam param, String fieldName, + String cosUrl) { + String json = JsonUtils.encode(param); + assertTrue(json.contains("\"" + fieldName + "\":{\"cos_url\":\"" + cosUrl + "\"}")); + assertFalse(json.contains("cosUrl")); + WxChannelKfSendMsgParam decoded = JsonUtils.decode(json, WxChannelKfSendMsgParam.class); + if ("image".equals(fieldName)) { + assertEquals(decoded.getImage().getCosUrl(), cosUrl); + } else if ("video".equals(fieldName)) { + assertEquals(decoded.getVideo().getCosUrl(), cosUrl); + } else { + assertEquals(decoded.getFile().getCosUrl(), cosUrl); + } + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/supplier/WxChannelSupplierBeanTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/supplier/WxChannelSupplierBeanTest.java new file mode 100644 index 0000000000..2c562b8dbc --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/supplier/WxChannelSupplierBeanTest.java @@ -0,0 +1,62 @@ +package me.chanjar.weixin.channel.bean.supplier; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; + +import java.util.Arrays; +import me.chanjar.weixin.channel.util.JsonUtils; +import org.testng.annotations.Test; + +/** + * @author GitHub Copilot + */ +public class WxChannelSupplierBeanTest { + + @Test + public void testEncodeProductDistributeRequest() { + ProductDistributeRequest request = new ProductDistributeRequest(); + request.setSupplierId("1001"); + request.setProductIdList(Arrays.asList("p1", "p2")); + + String json = JsonUtils.encode(request); + assertNotNull(json); + assertFalse(json.contains("supplierId")); + assertFalse(json.contains("productIdList")); + ProductDistributeRequest decoded = JsonUtils.decode(json, ProductDistributeRequest.class); + assertEquals(decoded.getSupplierId(), "1001"); + assertEquals(decoded.getProductIdList(), Arrays.asList("p1", "p2")); + } + + @Test + public void testEncodeDropshipAssignRequest() { + DropshipAssignRequest request = new DropshipAssignRequest(); + request.setOrderId("o1"); + request.setSupplierId("s1"); + + String json = JsonUtils.encode(request); + assertNotNull(json); + DropshipAssignRequest decoded = JsonUtils.decode(json, DropshipAssignRequest.class); + assertEquals(decoded.getOrderId(), "o1"); + assertEquals(decoded.getSupplierId(), "s1"); + } + + @Test + public void testDecodeSupplierListResponse() { + String json = "{\"errcode\":0,\"errmsg\":\"ok\",\"supplier_list\":[{\"supplier_id\":\"s1\",\"supplier_name\":\"供货商1\",\"status\":1}],\"has_more\":false}"; + SupplierListResponse response = JsonUtils.decode(json, SupplierListResponse.class); + + assertNotNull(response); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getSupplierList().size(), 1); + assertEquals(response.getSupplierList().get(0).getSupplierId(), "s1"); + } + + @Test + public void testDecodeDropshipInfo() { + DropshipInfo info = JsonUtils.decode("{\"ds_order_id\":\"d1\"}", DropshipInfo.class); + + assertNotNull(info); + assertEquals(info.getDropshipId(), "d1"); + } +} 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 index d1e2f72174..bff360f7cc 100644 --- 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 @@ -6,8 +6,7 @@ import org.testng.annotations.Test; /** - * @author LiXiZe - * @since 2023-04-20 + * @author Zeyes */ public class WxChannelMessageRouterRuleTest { 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 index 755886fa4b..9d683edce6 100644 --- 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 @@ -1,7 +1,9 @@ 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; @@ -10,6 +12,8 @@ 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; @@ -18,8 +22,7 @@ import org.testng.annotations.Test; /** - * @author LiXiZe - * @since 2023-04-21 + * @author Zeyes */ @Slf4j @Guice(modules = ApiTestModule.class) @@ -56,6 +59,48 @@ public void test1() { } } + @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)); @@ -67,6 +112,16 @@ public void spuAudit(SpuAuditMessage message, String content, String appId, 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)); + } + /** * 添加一条规则进入路由器 * 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 index 8eba81a07e..ddfb3a4b8a 100644 --- 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 @@ -29,6 +29,7 @@ public void configure(Binder binder) { "测试配置文件【" + TEST_CONFIG_XML + "】未找到,请参照test-config-sample.xml文件生成"); } + // 提示xml相关依赖不存在时,引入一下就好 TestConfig config = this.fromXml(TestConfig.class, inputStream); WxChannelService service = new WxChannelServiceImpl(); diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/util/WxChCryptUtilsTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/util/WxChCryptUtilsTest.java new file mode 100644 index 0000000000..0e3b9da080 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/util/WxChCryptUtilsTest.java @@ -0,0 +1,100 @@ +package me.chanjar.weixin.channel.util; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.expectThrows; + +import java.nio.charset.StandardCharsets; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import me.chanjar.weixin.channel.config.impl.WxChannelDefaultConfigImpl; +import me.chanjar.weixin.common.error.WxRuntimeException; +import me.chanjar.weixin.common.util.crypto.SHA1; +import org.apache.commons.codec.binary.Base64; +import org.testng.annotations.Test; + +/** + * {@link WxChCryptUtils} 单元测试 + */ +public class WxChCryptUtilsTest { + + private static final String APP_ID = "wx0000000000000002"; + private static final String TOKEN = "test_channel_token"; + /** 43 位 EncodingAESKey 占位值,非真实密钥 */ + private static final String AES_KEY = "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY"; + + private static final Pattern ENCRYPT_PATTERN = Pattern.compile(""); + private static final Pattern SIGNATURE_PATTERN = + Pattern.compile(""); + private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("(.*?)"); + private static final Pattern NONCE_PATTERN = Pattern.compile(""); + + private WxChCryptUtils cryptUtils() { + WxChannelDefaultConfigImpl config = new WxChannelDefaultConfigImpl(); + config.setAppid(APP_ID); + config.setToken(TOKEN); + config.setAesKey(AES_KEY); + return new WxChCryptUtils(config); + } + + private String group(Pattern pattern, String text) { + Matcher matcher = pattern.matcher(text); + assertTrue(matcher.find(), "未匹配到期望的节点:" + pattern.pattern()); + return matcher.group(1); + } + + @Test + public void testEncryptThenDecrypt() { + WxChCryptUtils cryptUtils = cryptUtils(); + String plainText = "" + + ""; + + String encryptedXml = cryptUtils.encrypt(plainText); + assertNotNull(encryptedXml); + + String encrypt = group(ENCRYPT_PATTERN, encryptedXml); + String signature = group(SIGNATURE_PATTERN, encryptedXml); + String timestamp = group(TIMESTAMP_PATTERN, encryptedXml); + String nonce = group(NONCE_PATTERN, encryptedXml); + + assertEquals(signature, SHA1.gen(TOKEN, timestamp, nonce, encrypt)); + assertEquals(cryptUtils.decryptXml(signature, timestamp, nonce, encryptedXml), plainText); + } + + @Test + public void testDecryptXmlWithWrongSignature() { + WxChCryptUtils cryptUtils = cryptUtils(); + String encryptedXml = cryptUtils.encrypt(""); + String timestamp = group(TIMESTAMP_PATTERN, encryptedXml); + String nonce = group(NONCE_PATTERN, encryptedXml); + + expectThrows(WxRuntimeException.class, + () -> cryptUtils.decryptXml("wrong_signature", timestamp, nonce, encryptedXml)); + } + + @Test + public void testDecryptWithSessionKey() throws Exception { + byte[] keyBytes = "0123456789abcdef".getBytes(StandardCharsets.UTF_8); + byte[] ivBytes = "fedcba9876543210".getBytes(StandardCharsets.UTF_8); + String plainText = "{\"openid\":\"o0000000000000000000\"}"; + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), new IvParameterSpec(ivBytes)); + String encryptedData = Base64.encodeBase64String(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))); + + String decrypted = WxChCryptUtils.decrypt(Base64.encodeBase64String(keyBytes), encryptedData, + Base64.encodeBase64String(ivBytes)); + assertEquals(decrypted, plainText); + } + + @Test + public void testDecryptWithInvalidSessionKey() { + expectThrows(RuntimeException.class, + () -> WxChCryptUtils.decrypt(Base64.encodeBase64String("short_key".getBytes(StandardCharsets.UTF_8)), + "invalid", Base64.encodeBase64String("fedcba9876543210".getBytes(StandardCharsets.UTF_8)))); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/util/XmlUtilsTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/util/XmlUtilsTest.java new file mode 100644 index 0000000000..e22e31760a --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/util/XmlUtilsTest.java @@ -0,0 +1,75 @@ +package me.chanjar.weixin.channel.util; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import me.chanjar.weixin.channel.bean.base.AttrInfo; +import org.testng.annotations.Test; + +/** + * {@link XmlUtils} 单元测试 + */ +public class XmlUtilsTest { + + private static final String XML = "这是Key这是Value"; + + @Test + public void testEncode() { + String xml = XmlUtils.encode(new AttrInfo("这是Key", "这是Value")); + assertNotNull(xml); + assertEquals(xml, XML); + } + + @Test + public void testEncodeWithObjectMapper() { + String xml = XmlUtils.encode(new XmlMapper(), new AttrInfo("这是Key", "这是Value")); + assertNotNull(xml); + assertEquals(XmlUtils.decode(xml, AttrInfo.class).getKey(), "这是Key"); + } + + @Test + public void testDecode() { + AttrInfo info = XmlUtils.decode(XML, AttrInfo.class); + assertNotNull(info); + assertEquals(info.getKey(), "这是Key"); + assertEquals(info.getValue(), "这是Value"); + } + + @Test + public void testDecodeUnknownProperty() { + String xml = "kv"; + AttrInfo info = XmlUtils.decode(xml, AttrInfo.class); + assertNotNull(info); + assertEquals(info.getKey(), "k"); + assertNull(info.getValue()); + } + + @Test + public void testDecodeEmptyOrInvalidXml() { + assertNull(XmlUtils.decode((String) null, AttrInfo.class)); + assertNull(XmlUtils.decode("", AttrInfo.class)); + assertNull(XmlUtils.decode("not a xml", AttrInfo.class)); + } + + @Test + public void testDecodeWithTypeReference() { + AttrInfo info = XmlUtils.decode(XML, new TypeReference() { + }); + assertNotNull(info); + assertEquals(info.getValue(), "这是Value"); + } + + @Test + public void testDecodeInputStream() { + InputStream is = new ByteArrayInputStream(XML.getBytes(StandardCharsets.UTF_8)); + AttrInfo info = XmlUtils.decode(is, AttrInfo.class); + assertNotNull(info); + assertEquals(info.getKey(), "这是Key"); + } +} diff --git a/weixin-java-channel/src/test/resources/test-config.sample.xml b/weixin-java-channel/src/test/resources/test-config.sample.xml index b04a50e5a7..b84d8d9109 100644 --- a/weixin-java-channel/src/test/resources/test-config.sample.xml +++ b/weixin-java-channel/src/test/resources/test-config.sample.xml @@ -4,6 +4,7 @@ secret Token EncodingAESKey + false 可以不填写 可以不填写 diff --git a/weixin-java-channel/src/test/resources/testng.xml b/weixin-java-channel/src/test/resources/testng.xml index afa0aa32f2..0552b4dbc5 100644 --- a/weixin-java-channel/src/test/resources/testng.xml +++ b/weixin-java-channel/src/test/resources/testng.xml @@ -3,9 +3,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml index bae97ea3ef..80701f31c1 100644 --- a/weixin-java-common/pom.xml +++ b/weixin-java-common/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 4.5.3.B + 4.8.6.B weixin-java-common @@ -24,15 +24,14 @@ okhttp provided - + - org.slf4j - slf4j-api - - - com.thoughtworks.xstream - xstream + org.apache.httpcomponents.client5 + httpclient5 + provided + + org.apache.httpcomponents httpclient @@ -47,6 +46,15 @@ org.apache.httpcomponents httpmime + + + org.slf4j + slf4j-api + + + com.thoughtworks.xstream + xstream + org.slf4j jcl-over-slf4j @@ -77,6 +85,11 @@ org.projectlombok lombok + + org.mockito + mockito-core + test + ch.qos.logback @@ -88,11 +101,7 @@ testng test - - org.mockito - mockito-all - test - + com.google.inject guice @@ -139,6 +148,9 @@ org.springframework.data spring-data-redis + ${spring-data-redis.version} + true + provided 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 3d2f62affe..5129410999 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java @@ -12,7 +12,7 @@ /** * 微信开发所使用到的常量类. * - * @author Daniel Qian & binarywang & Wang_Wong + * @author Daniel Qian, binarywang, Wang_Wong */ @UtilityClass public class WxConsts { @@ -53,6 +53,7 @@ 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 TRANSFER_BIZ_AI_IVR = "transfer_biz_ai_ivr"; public static final String UPDATE_TASKCARD = "update_taskcard"; public static final String UPDATE_BUTTON = "update_button"; } @@ -301,6 +302,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"; /** * 扫码推事件的事件推送 */ @@ -430,6 +433,12 @@ public static class EventType { */ public static final String WEAPP_AUDIT_FAIL = "weapp_audit_fail"; + + /** + * 小程序审核事件:审核延后 + */ + public static final String WEAPP_AUDIT_DELAY = "weapp_audit_delay"; + /** * 小程序自定义交易组件支付通知 */ @@ -453,6 +462,48 @@ public static class EventType { * 订阅通知事件:发送订阅通知回调 */ 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"; + /** + * 虚拟支付 iOS 退款查询通知 + * 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/virtual-payment/ios.html + */ + public static final String XPAY_SUBSCRIBE_IOS_REFUND_QUERY_NOTIFY = "xpay_subscribe_ios_refund_query_notify"; } /** @@ -586,4 +637,19 @@ public static class AppIdType { */ public static final String MINI_TYPE = "mini"; } + + /** + * 新建文章类型 + */ + @UtilityClass + public static class ArticleType { + /** + * 图文消息 + */ + public static final String NEWS = "news"; + /** + * 图片消息 + */ + public static final String NEWS_PIC = "newspic"; + } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/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..34fb66da15 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/CommonUploadParam.java @@ -0,0 +1,129 @@ +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; +import java.util.HashMap; +import java.util.Map; + +/** + * 通用文件上传参数 + * + * @author 广州跨界 + * created on 2024/01/11 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CommonUploadParam implements Serializable { + + /** + * 文件对应的接口参数名称(非文件名),如:media + */ + @NotNull + private String name; + + /** + * 上传数据 + */ + @NotNull + private CommonUploadData data; + + /** + * 额外的表单字段,用于在上传文件的同时提交其他表单数据 + * 例如:上传视频素材时需要提交description字段(JSON格式的视频描述信息) + */ + @Nullable + private Map formFields; + + /** + * 为保持向后兼容保留的 2 参数构造函数。 + *

+ * 仅设置文件参数名和上传数据,额外表单字段将为 {@code null}。 + * + * @param name 参数名,如:media + * @param data 上传数据 + * @deprecated 请使用包含 formFields 参数的构造函数或静态工厂方法 {@link #fromFile(String, File)}、{@link #fromBytes(String, String, byte[])} + */ + @Deprecated + public CommonUploadParam(@NotNull String name, @NotNull CommonUploadData data) { + this(name, data, null); + } + + /** + * 从文件构造 + * + * @param name 参数名,如:media + * @param file 文件 + * @return 文件上传参数对象 + */ + @SneakyThrows + public static CommonUploadParam fromFile(String name, File file) { + return new CommonUploadParam(name, CommonUploadData.fromFile(file), null); + } + + /** + * 从字节数组构造 + * + * @param name 参数名,如:media + * @param bytes 字节数组 + * @return 文件上传参数对象 + */ + @SneakyThrows + public static CommonUploadParam fromBytes(String name, @Nullable String fileName, byte[] bytes) { + return new CommonUploadParam(name, new ByteArrayUploadData(fileName, bytes), null); + } + + private static class ByteArrayUploadData extends CommonUploadData { + + private final byte[] bytes; + + private ByteArrayUploadData(@Nullable String fileName, byte[] bytes) { + super(fileName, new ByteArrayInputStream(bytes), bytes.length); + this.bytes = bytes; + } + + @Override + public ByteArrayInputStream getInputStream() { + return new ByteArrayInputStream(bytes); + } + + @Override + public byte[] readAllBytes() { + return bytes.clone(); + } + } + + /** + * 添加额外的表单字段 + * + * @param fieldName 表单字段名 + * @param fieldValue 表单字段值 + * @return 当前对象,支持链式调用 + */ + public CommonUploadParam addFormField(String fieldName, String fieldValue) { + if (fieldName == null || fieldName.trim().isEmpty()) { + throw new IllegalArgumentException("表单字段名不能为空"); + } + if (fieldValue == null) { + throw new IllegalArgumentException("表单字段值不能为null"); + } + if (this.formFields == null) { + this.formFields = new HashMap<>(); + } + this.formFields.put(fieldName, fieldValue); + return this; + } + + @Override + public String toString() { + return String.format("{name:%s, data:%s, formFields:%s}", name, data, formFields); + } +} 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 index 63e568d3f4..906e9de2b1 100644 --- 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 @@ -58,11 +58,7 @@ public class WxOAuth2UserInfo implements Serializable { */ @SerializedName("privilege") private String[] privileges; - /** - * is_snapshotuser 是否为快照页模式虚拟账号,值为0时是普通用户,1时是虚拟帐号 - */ - @SerializedName("is_snapshotuser") - private Integer snapshotUser; + 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/oauth2/WxOAuth2AccessToken.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java index 4faf9df375..b339844ad6 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java @@ -7,7 +7,10 @@ import java.io.Serializable; /** - * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842 + * OAuth2 AccessToken + *

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

* * @author Daniel Qian */ @@ -29,13 +32,17 @@ public class WxOAuth2AccessToken implements Serializable { @SerializedName("scope") private String scope; - + /** + * 是否为快照页模式虚拟账号,只有当用户是快照页模式虚拟账号时返回,值为1 + */ @SerializedName("is_snapshotuser") private Integer snapshotUser; /** - * https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&announce_id=11513156443eZYea&version=&lang=zh_CN. * 本接口在scope参数为snsapi_base时不再提供unionID字段。 + *

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

*/ @SerializedName("unionid") private String unionId; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrMenuResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrMenuResult.java new file mode 100644 index 0000000000..6f5a2bc27e --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ocr/WxOcrMenuResult.java @@ -0,0 +1,47 @@ +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; + +/** + * OCR菜单识别结果. + * + * @author GitHub Copilot + */ +@Data +public class WxOcrMenuResult implements Serializable { + private static final long serialVersionUID = -8062516251827437945L; + + @SerializedName("img_size") + private WxOcrImgSize imgSize; + @SerializedName("items") + private List items; + + public static WxOcrMenuResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxOcrMenuResult.class); + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + + @Data + public static class Items implements Serializable { + private static final long serialVersionUID = 3066181677009102792L; + + @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/result/WxMinishopImageUploadCustomizeResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadCustomizeResult.java index cd700be7c1..5427d5cada 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadCustomizeResult.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadCustomizeResult.java @@ -16,7 +16,7 @@ public class WxMinishopImageUploadCustomizeResult implements Serializable { private WxMinishopPicFileCustomizeResult imgInfo; public static WxMinishopImageUploadCustomizeResult fromJson(String json) { - JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject(); + JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); WxMinishopImageUploadCustomizeResult result = new WxMinishopImageUploadCustomizeResult(); result.setErrcode(jsonObject.get(WxConsts.ERR_CODE).getAsNumber().toString()); if (result.getErrcode().equals("0")) { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadResult.java index 324232d0ee..9c2cbaf3ba 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadResult.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMinishopImageUploadResult.java @@ -21,7 +21,7 @@ public class WxMinishopImageUploadResult implements Serializable { public static WxMinishopImageUploadResult fromJson(String json) { - JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject(); + JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); WxMinishopImageUploadResult result = new WxMinishopImageUploadResult(); result.setErrcode(jsonObject.get(WxConsts.ERR_CODE).getAsNumber().toString()); if (result.getErrcode().equals("0")) { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/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 ea1e9e7c68..356d1dbbf9 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java @@ -453,7 +453,7 @@ public enum WxCpErrorMsgEnum { */ CODE_60008(60008, "部门已存在;部门ID或者部门名称已存在"), /** - * 部门名称含有非法字符;不能含有 \\:?*“< >| 等字符. + * {@code 部门名称含有非法字符;不能含有 \\:?*"< >| 等字符.} */ CODE_60009(60009, "部门名称含有非法字符;不能含有 \\ :?*“< >| 等字符"), /** @@ -521,7 +521,7 @@ public enum WxCpErrorMsgEnum { */ CODE_60124(60124, "无效的父部门id;父部门不存在通讯录中"), /** - * 非法部门名字;不能为空,且不能超过64字节,且不能含有\\:*?”< >|等字符. + * {@code 非法部门名字;不能为空,且不能超过64字节,且不能含有\\:*?"< >|等字符.} */ CODE_60125(60125, "非法部门名字;不能为空,且不能超过64字节,且不能含有\\:*?”< >|等字符"), /** diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java index 1156636ea3..1aab7f1f20 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java @@ -12,11 +12,13 @@ /** * 微信错误码. + *

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

* - * @author Daniel Qian & Binary Wang + * @author Daniel Qian, Binary Wang */ @Data @NoArgsConstructor @@ -91,6 +93,13 @@ public static WxError fromJson(String json, WxType type) { } 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/WxMaErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java index 46c1d3d3d2..3f380543b0 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java @@ -46,23 +46,23 @@ public enum WxMaErrorMsgEnum { */ CODE_40003(40003, "openid 不正确"), /** - *
    * 无效媒体文件类型
-   * 对应操作:uploadTempMedia
+   * 

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

+ *

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

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

+ *

*/ CODE_40007(40007, "无效媒体文件 ID"), /** @@ -99,29 +99,29 @@ public enum WxMaErrorMsgEnum { */ CODE_41028(41028, "form_id 不正确,或者过期"), /** - *
    * code 或 template_id 不正确.
-   * 对应操作:code2Session, sendUniformMessage, sendTemplateMessage
+   * 

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

+ *

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

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

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

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

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

+ *

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

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

*/ CODE_45080(45080, "下发输入状态,需要之前30秒内跟用户有过消息交互"), /** @@ -686,7 +687,7 @@ public enum WxMaErrorMsgEnum { /** * 89252 - * 法人&企业信息一致性校验中 front checking + * {@code 法人&企业信息一致性校验中 front checking} */ CODE_89252(89252, "法人&企业信息一致性校验中"), @@ -711,6 +712,160 @@ public enum WxMaErrorMsgEnum { 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, "授权次数到达上限"), + + /** + * 微信小程序虚拟支付错误码 + * + * @see 虚拟支付 API 文档 + */ + CODE_268490001(268490001, "openid错误"), + CODE_268490002(268490002, "请求参数字段错误,具体看errmsg"), + CODE_268490003(268490003, "签名错误"), + CODE_268490004(268490004, "重复操作(赠送和代币支付和充值广告金相关接口会返回,表示之前的操作已经成功)"), + CODE_268490005(268490005, "订单已经通过cancel_currency_pay接口退款,不支持再退款"), + CODE_268490006(268490006, "代币的退款/支付操作金额不足"), + CODE_268490007(268490007, "图片或文字存在敏感内容,禁止使用"), + CODE_268490008(268490008, "代币未发布,不允许进行代币操作"), + CODE_268490009(268490009, "用户session_key不存在或已过期,请重新登录"), + CODE_268490011(268490011, "数据生成中,请稍后调用本接口获取"), + CODE_268490012(268490012, "批量任务运行中,请等待完成后才能再次运行"), + CODE_268490013(268490013, "禁止对核销状态的单进行退款"), + CODE_268490014(268490014, "退款操作进行中,稍后可以使用相同参数重试"), + CODE_268490015(268490015, "频率限制"), + CODE_268490016(268490016, "退款的left_fee字段与实际不符,请通过query_order接口查询确认"), + CODE_268490018(268490018, "广告金充值账户行业id不匹配"), + CODE_268490019(268490019, "广告金充值账户id已绑定其他appid"), + CODE_268490020(268490020, "广告金充值账户主体名称错误"), + CODE_268490021(268490021, "账户未完成进件"), + CODE_268490022(268490022, "广告金充值账户无效"), + CODE_268490023(268490023, "广告金余额不足"), + CODE_268490024(268490024, "广告金充值金额必须大于0"), + ; private final int code; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java index 28fb5de8ad..ba910e988b 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxOpenErrorMsgEnum.java @@ -527,7 +527,7 @@ public enum WxOpenErrorMsgEnum { CODE_40099(40099, "invalid code, this code has consumed."), /** - * invalid DateInfo, Make Sure OldDateInfoType==NewDateInfoType && NewBeginTime<=OldBeginTime && OldEndTime<= NewEndTime + * {@code invalid DateInfo, Make Sure OldDateInfoType==NewDateInfoType && NewBeginTime<=OldBeginTime && OldEndTime<= NewEndTime} */ CODE_40100(40100, "invalid DateInfo, Make Sure OldDateInfoType==NewDateInfoType && NewBeginTime<=OldBeginTime && OldEndTime<= NewEndTime"), @@ -572,7 +572,7 @@ public enum WxOpenErrorMsgEnum { CODE_40108(40108, "invalid client version"), /** - * too many code size, must <= 100 + * {@code too many code size, must <= 100} */ CODE_40109(40109, "too many code size, must <= 100"), @@ -702,7 +702,7 @@ public enum WxOpenErrorMsgEnum { CODE_40135(40135, "invalid not supply bonus, can not change card_id which supply bonus to be not supply"), /** - * invalid use DepositCodeMode, make sure sku.quantity>DepositCode.quantity + * {@code invalid use DepositCodeMode, make sure sku.quantity>DepositCode.quantity} */ CODE_40136(40136, "invalid use DepositCodeMode, make sure sku.quantity>DepositCode.quantity"), @@ -1082,7 +1082,7 @@ public enum WxOpenErrorMsgEnum { CODE_40211(40211, "invalid scope_data"), /** - * paegs 当中存在不合法的query,query格式遵循URL标准,即k1=v1&k2=v2 invalid query + * {@code paegs 当中存在不合法的query,query格式遵循URL标准,即k1=v1&k2=v2 invalid query} */ CODE_40212(40212, "paegs 当中存在不合法的query,query格式遵循URL标准,即k1=v1&k2=v2"), @@ -4242,7 +4242,7 @@ public enum WxOpenErrorMsgEnum { CODE_71005(71005, "limit exe count"), /** - * limit coin count, 1 <= coin_count <= 100000 + * {@code limit coin count, 1 <= coin_count <= 100000} */ CODE_71006(71006, "limit coin count, 1 <= coin_count <= 100000"), @@ -4347,7 +4347,7 @@ public enum WxOpenErrorMsgEnum { CODE_72018(72018, "duplicate order id, invoice had inserted to user"), /** - * limit msg operation card list size, must <= 5 + * {@code limit msg operation card list size, must <= 5} */ CODE_72019(72019, "limit msg operation card list size, must <= 5"), @@ -6432,7 +6432,7 @@ public enum WxOpenErrorMsgEnum { CODE_88009(88009, "reply is not exists"), /** - * count range error. cout <= 0 or count > 50 + * {@code count range error. cout <= 0 or count > 50} */ CODE_88010(88010, "count range error. cout <= 0 or count > 50"), @@ -6682,7 +6682,7 @@ public enum WxOpenErrorMsgEnum { CODE_89251(89251, "模板消息已下发,待法人人脸核身校验"), /** - * 法人&企业信息一致性校验中 front checking + * {@code 法人&企业信息一致性校验中 front checking} */ CODE_89253(89253, "法人&企业信息一致性校验中"), @@ -7257,7 +7257,7 @@ public enum WxOpenErrorMsgEnum { CODE_200021(200021, "场景描述 sceneDesc 参数错误"), /** - * 禁止创建/更新商品(如商品创建功能被封禁) 或 禁止编辑&更新房间 + * {@code 禁止创建/更新商品(如商品创建功能被封禁) 或 禁止编辑&更新房间} */ CODE_300001(300001, "禁止创建/更新商品(如商品创建功能被封禁) 或 禁止编辑&更新房间"), @@ -8382,7 +8382,7 @@ public enum WxOpenErrorMsgEnum { CODE_9300003(9300003, "begin_time must less than end_time"), /** - * end_time - begin_time > 1year + * {@code end_time - begin_time > 1year} */ CODE_9300004(9300004, "end_time - begin_time > 1year"), @@ -8397,7 +8397,7 @@ public enum WxOpenErrorMsgEnum { CODE_9300006(9300006, "invalid activity status"), /** - * gift_num must >0 and <=15 + * {@code gift_num must >0 and <=15} */ CODE_9300007(9300007, "gift_num must >0 and <=15"), @@ -8412,7 +8412,7 @@ public enum WxOpenErrorMsgEnum { CODE_9300009(9300009, "activity can not finish"), /** - * card_info_list must >= 2 + * {@code card_info_list must >= 2} */ CODE_9300010(9300010, "card_info_list must >= 2"), diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutor.java 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..dba92e27da --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorApacheImpl.java @@ -0,0 +1,86 @@ +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()); + MultipartEntityBuilder entityBuilder = MultipartEntityBuilder + .create() + .addPart(param.getName(), part) + .setMode(HttpMultipartMode.RFC6532); + + // 添加额外的表单字段 + if (param.getFormFields() != null && !param.getFormFields().isEmpty()) { + for (java.util.Map.Entry entry : param.getFormFields().entrySet()) { + entityBuilder.addTextBody(entry.getKey(), entry.getValue(), ContentType.TEXT_PLAIN.withCharset("UTF-8")); + } + } + + HttpEntity entity = entityBuilder.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..f79e4cd96f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorHttpComponentsImpl.java @@ -0,0 +1,83 @@ +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()); + MultipartEntityBuilder entityBuilder = MultipartEntityBuilder + .create() + .addPart(param.getName(), part) + .setMode(HttpMultipartMode.EXTENDED); + + // 添加额外的表单字段 + if (param.getFormFields() != null && !param.getFormFields().isEmpty()) { + for (java.util.Map.Entry entry : param.getFormFields().entrySet()) { + entityBuilder.addTextBody(entry.getKey(), entry.getValue(), ContentType.TEXT_PLAIN.withCharset("UTF-8")); + } + } + + HttpEntity entity = entityBuilder.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..182820d076 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorJoddHttpImpl.java @@ -0,0 +1,99 @@ +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())); + + // 添加额外的表单字段 + if (param.getFormFields() != null && !param.getFormFields().isEmpty()) { + for (java.util.Map.Entry entry : param.getFormFields().entrySet()) { + request.form(entry.getKey(), entry.getValue()); + } + } + + 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..6a0343980f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorOkHttpImpl.java @@ -0,0 +1,99 @@ +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()); + MultipartBody.Builder bodyBuilder = new MultipartBody.Builder() + .setType(MediaType.get("multipart/form-data")) + .addFormDataPart(param.getName(), param.getData().getFileName(), requestBody); + + // 添加额外的表单字段 + if (param.getFormFields() != null && !param.getFormFields().isEmpty()) { + for (java.util.Map.Entry entry : param.getFormFields().entrySet()) { + bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue()); + } + } + + RequestBody body = bodyBuilder.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/RedisTemplateWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java index 19d4046c92..d531a2a307 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java @@ -29,7 +29,7 @@ public void setValue(String key, String value, int expire, TimeUnit timeUnit) { @Override public Long getExpire(String key) { - return redisTemplate.getExpire(key); + return redisTemplate.getExpire(key, TimeUnit.SECONDS); } @Override diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernApacheHttpRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernApacheHttpRequestExecutor.java index 22cdab3f92..03bec013dd 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernApacheHttpRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernApacheHttpRequestExecutor.java @@ -8,7 +8,6 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; @@ -24,7 +23,7 @@ * created on 2019/6/27 14:06 */ public class OcrDiscernApacheHttpRequestExecutor extends OcrDiscernRequestExecutor { - public OcrDiscernApacheHttpRequestExecutor(RequestHttp requestHttp) { + public OcrDiscernApacheHttpRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -43,15 +42,11 @@ public String execute(String uri, File file, WxType wxType) throws WxErrorExcept .build(); httpPost.setEntity(entity); } - try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; - } finally { - httpPost.releaseConnection(); + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); } + return responseContent; } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernHttpComponentsRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernHttpComponentsRequestExecutor.java new file mode 100644 index 0000000000..2d02c965a8 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernHttpComponentsRequestExecutor.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.common.requestexecuter.ocr; + +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.hc.Utf8ResponseHandler; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; + +public class OcrDiscernHttpComponentsRequestExecutor extends OcrDiscernRequestExecutor { + public OcrDiscernHttpComponentsRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public String execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("file", file) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return responseContent; + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernRequestExecutor.java index 870f77d2ed..542ab4a378 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/requestexecuter/ocr/OcrDiscernRequestExecutor.java @@ -5,6 +5,8 @@ import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.ResponseHandler; +import org.apache.http.HttpHost; +import org.apache.http.impl.client.CloseableHttpClient; import java.io.File; import java.io.IOException; @@ -18,7 +20,7 @@ public abstract class OcrDiscernRequestExecutor implements RequestExecutor { protected RequestHttp requestHttp; - public OcrDiscernRequestExecutor(RequestHttp requestHttp) { + public OcrDiscernRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } @@ -27,12 +29,17 @@ public void execute(String uri, File data, ResponseHandler handler, WxTy handler.handle(this.execute(uri, data, wxType)); } - public static RequestExecutor create(RequestHttp requestHttp) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new OcrDiscernApacheHttpRequestExecutor(requestHttp); + return new OcrDiscernApacheHttpRequestExecutor( + (RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new OcrDiscernHttpComponentsRequestExecutor( + (RequestHttp) requestHttp); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java index 39a8a93754..08524eeafc 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java @@ -7,12 +7,15 @@ 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 me.chanjar.weixin.common.bean.ocr.WxOcrMenuResult; import java.io.File; /** * 基于小程序或 H5 的身份证、银行卡、行驶证 OCR 识别. - * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=21516712284rHWMX + *

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

* * @author Binary Wang * created on 2019-06-22 @@ -128,4 +131,22 @@ public interface WxOcrService { * @throws WxErrorException . */ WxOcrCommResult comm(File imgFile) throws WxErrorException; + + /** + * 菜单OCR识别接口 + * 文件大小限制:小于2M + * @param imgUrl 图片url地址 + * @return WxOcrMenuResult + * @throws WxErrorException . + */ + WxOcrMenuResult menu(String imgUrl) throws WxErrorException; + + /** + * 菜单OCR识别接口 + * 文件大小限制:小于2M + * @param imgFile 图片文件对象 + * @return WxOcrMenuResult + * @throws WxErrorException . + */ + WxOcrMenuResult menu(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 index 497c1c0546..f894cba44f 100644 --- 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 @@ -1,6 +1,7 @@ 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; @@ -60,4 +61,14 @@ public interface WxService { * @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/util/BeanUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java index 73b6cff368..d3f8d00406 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java @@ -2,7 +2,6 @@ import com.google.common.collect.Lists; import me.chanjar.weixin.common.annotation.Required; -import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/DataUtils.java index 983d9a668f..2e9f5d4c7b 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; /** @@ -16,8 +17,10 @@ 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=******&"); + if (data instanceof String) { + String stringData = (String) data; + stringData = RegExUtils.replaceAll(stringData, "(^|[?&])secret=[^&]*", "$1secret=******"); + dataForLog = (E) RegExUtils.replaceAll(stringData, "(\\\"openid\\\"\\s*:\\s*\\\")[^\\\"]*(\\\")", "$1******$2"); } return dataForLog; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java index bbb11992bc..a9017c0d16 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java @@ -4,12 +4,24 @@ public class RandomUtils { private static final String RANDOM_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - private static final java.util.Random RANDOM = new java.util.Random(); + private static volatile java.util.Random random; + + private static java.util.Random getRandom() { + if (random == null) { + synchronized (RandomUtils.class) { + if (random == null) { + random = new java.util.Random(); + } + } + } + return random; + } public static String getRandomStr() { StringBuilder sb = new StringBuilder(); + java.util.Random r = getRandom(); for (int i = 0; i < 16; i++) { - sb.append(RANDOM_STR.charAt(RANDOM.nextInt(RANDOM_STR.length()))); + sb.append(RANDOM_STR.charAt(r.nextInt(RANDOM_STR.length()))); } return sb.toString(); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java index fc3579d45c..1886209f98 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/SignUtils.java @@ -25,6 +25,7 @@ public class SignUtils { * * @param message 签名数据 * @param key 签名密钥 + * @return 签名结果 */ public static String createHmacSha256Sign(String message, String key) { try { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java index facf564e32..67faf319f4 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java @@ -65,7 +65,7 @@ private static Object element2MapOrString(Element element) { final List names = names(nodes); // 判断节点下有无非文本节点(非Text和CDATA),如无,直接取Text文本内容 - if (names.size() < 1) { + if (names.isEmpty()) { return element.getText(); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java index 9b9f776768..43cc54b43d 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java @@ -29,7 +29,10 @@ public static String gen(String... arr) { } /** - * 用&串接arr参数,生成sha1 digest. + * {@code 用&串接arr参数,生成sha1 digest.} + * + * @param arr 参数数组 + * @return sha1摘要 */ public static String genWithAmple(String... arr) { if (StringUtils.isAnyEmpty(arr)) { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java index f02d087b7d..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,6 +1,5 @@ package me.chanjar.weixin.common.util.crypto; -import com.google.common.base.CharMatcher; import lombok.AllArgsConstructor; import lombok.Data; import me.chanjar.weixin.common.error.WxRuntimeException; @@ -38,6 +37,19 @@ public class WxCryptUtil { private static final Base64 BASE64 = new Base64(); private static final Charset CHARSET = StandardCharsets.UTF_8; + private static volatile Random random; + + private static Random getRandom() { + if (random == null) { + synchronized (WxCryptUtil.class) { + if (random == null) { + random = new Random(); + } + } + } + return random; + } + private static final ThreadLocal BUILDER_LOCAL = ThreadLocal.withInitial(() -> { try { final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); @@ -110,10 +122,10 @@ private static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) { */ private static String genRandomStr() { String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - Random random = new Random(); + Random r = getRandom(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 16; i++) { - int number = random.nextInt(base.length()); + int number = r.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); @@ -185,6 +197,7 @@ public EncryptContext encryptContext(String plainText) { /** * 对明文进行加密. * + * @param randomStr 随机字符串 * @param plainText 需要加密的明文 * @return 加密后base64编码的字符串 */ @@ -321,14 +334,28 @@ public String decrypt(String cipherText) { byte[] bytes = PKCS7Encoder.decode(original); // 分离16位随机字符串,网络字节序和AppId + if (bytes == null || bytes.length < 20) { + throw new WxRuntimeException("解密后数据长度异常,可能为错误的密文或EncodingAESKey"); + } byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); int xmlLength = bytesNetworkOrder2Number(networkOrder); - xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); - fromAppid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); + // 长度边界校验,避免非法长度导致的越界/参数异常 + int startIndex = 20; + int endIndex = startIndex + xmlLength; + if (xmlLength < 0 || endIndex > bytes.length) { + throw new WxRuntimeException("解密后数据格式非法:消息长度不正确,可能为错误的密文或EncodingAESKey"); + } + + xmlContent = new String(Arrays.copyOfRange(bytes, startIndex, endIndex), CHARSET); + fromAppid = new String(Arrays.copyOfRange(bytes, endIndex, bytes.length), CHARSET); } catch (Exception e) { - throw new WxRuntimeException(e); + if (e instanceof WxRuntimeException) { + throw (WxRuntimeException) e; + } else { + throw new WxRuntimeException(e); + } } // appid不相同的情况 暂时忽略这段判断 diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java index 4a267153c6..8419c87d5a 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 @@ -57,13 +57,10 @@ public static File createTmpFile(InputStream inputStream, String name, String ex * @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); + return Base64.getEncoder().encodeToString(IOUtils.toByteArray(in)); } catch (IOException e) { e.printStackTrace(); } finally { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java index ed5ec17bc9..8304742524 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/BaseMediaDownloadRequestExecutor.java @@ -1,13 +1,18 @@ package me.chanjar.weixin.common.util.http; -import java.io.File; -import java.io.IOException; - +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.apache.ApacheMediaDownloadRequestExecutor; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsMediaDownloadRequestExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpMediaDownloadRequestExecutor; import me.chanjar.weixin.common.util.http.okhttp.OkHttpMediaDownloadRequestExecutor; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; + +import java.io.File; +import java.io.IOException; /** * 下载媒体文件请求执行器. @@ -30,16 +35,21 @@ public void execute(String uri, String data, ResponseHandler handler, WxTy handler.handle(this.execute(uri, data, wxType)); } - public static RequestExecutor create(RequestHttp requestHttp, File tmpDirFile) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp, File tmpDirFile) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheMediaDownloadRequestExecutor(requestHttp, tmpDirFile); + return new ApacheMediaDownloadRequestExecutor( + (RequestHttp) requestHttp, tmpDirFile); case JODD_HTTP: - return new JoddHttpMediaDownloadRequestExecutor(requestHttp, tmpDirFile); + return new JoddHttpMediaDownloadRequestExecutor((RequestHttp) requestHttp, tmpDirFile); case OK_HTTP: - return new OkHttpMediaDownloadRequestExecutor(requestHttp, tmpDirFile); + return new OkHttpMediaDownloadRequestExecutor((RequestHttp) requestHttp, tmpDirFile); + case HTTP_COMPONENTS: + return new HttpComponentsMediaDownloadRequestExecutor( + (RequestHttp) requestHttp, tmpDirFile); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpType.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpClientType.java similarity index 59% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpType.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpClientType.java index eff5907f7a..a4e22be9b4 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpType.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpClientType.java @@ -3,17 +3,21 @@ /** * Created by ecoolper on 2017/4/28. */ -public enum HttpType { +public enum HttpClientType { /** * jodd-http. */ JODD_HTTP, /** - * apache httpclient. + * apache httpclient 4.x. */ APACHE_HTTP, /** * okhttp. */ - OK_HTTP + OK_HTTP, + /** + * apache httpclient 5.x. + */ + HTTP_COMPONENTS } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java index f338ece672..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,87 +1,68 @@ 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("无法获取到文件名,Content-disposition为空"); - } + String getFileName() throws WxErrorException; - return this.extractFileNameFromContentString(contentDispositionHeader[0].getValue()); - } - - 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); - } - - private String extractFileNameFromContentString(String content) throws WxErrorException { - if (content == null || content.length() == 0) { + static String extractFileNameFromContentString(String content) throws WxErrorException { + if (content == null || content.isEmpty()) { throw new WxErrorException("无法获取到文件名,content为空"); } - Matcher m = PATTERN.matcher(content); - if (m.matches()) { - return m.group(1); + // 查找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); + } + } + + // 查找普通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("无法获取到文件名,header信息有问题"); diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java index d07873f3c4..f03932984f 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamData.java @@ -10,8 +10,9 @@ /** * 输入流数据. - *

+ *

* InputStreamData + *

* * @author zichuan.zhou91@gmail.com * created on 2022/2/15 diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaInputStreamUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaInputStreamUploadRequestExecutor.java index de4be21709..22c426ca54 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaInputStreamUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaInputStreamUploadRequestExecutor.java @@ -1,11 +1,16 @@ package me.chanjar.weixin.common.util.http; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.apache.ApacheMediaInputStreamUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsMediaInputStreamUploadRequestExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpMediaInputStreamUploadRequestExecutor; import me.chanjar.weixin.common.util.http.okhttp.OkHttpMediaInputStreamUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; import java.io.IOException; @@ -18,7 +23,7 @@ public abstract class MediaInputStreamUploadRequestExecutor implements RequestExecutor { protected RequestHttp requestHttp; - public MediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { + public MediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } @@ -27,16 +32,21 @@ public void execute(String uri, InputStreamData data, ResponseHandler create(RequestHttp requestHttp) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheMediaInputStreamUploadRequestExecutor(requestHttp); + return new ApacheMediaInputStreamUploadRequestExecutor( + (RequestHttp) requestHttp); case JODD_HTTP: - return new JoddHttpMediaInputStreamUploadRequestExecutor(requestHttp); + return new JoddHttpMediaInputStreamUploadRequestExecutor((RequestHttp) requestHttp); case OK_HTTP: - return new OkHttpMediaInputStreamUploadRequestExecutor(requestHttp); + return new OkHttpMediaInputStreamUploadRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsMediaInputStreamUploadRequestExecutor( + (RequestHttp) requestHttp); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java index 14724412f1..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.enums.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 index e94b2d8d6a..0e8684a1db 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestCustomizeExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestCustomizeExecutor.java @@ -1,11 +1,16 @@ package me.chanjar.weixin.common.util.http; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadCustomizeResult; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.apache.ApacheMinishopMediaUploadRequestCustomizeExecutor; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsMinishopMediaUploadRequestCustomizeExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpMinishopMediaUploadRequestCustomizeExecutor; import me.chanjar.weixin.common.util.http.okhttp.OkHttpMinishopMediaUploadRequestCustomizeExecutor; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; import java.io.File; import java.io.IOException; @@ -16,13 +21,12 @@ public abstract class MinishopUploadRequestCustomizeExecutor implements Re protected String uploadType; protected String imgUrl; - public MinishopUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { + public MinishopUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { this.requestHttp = requestHttp; this.respType = respType; if (imgUrl == null || imgUrl.isEmpty()) { this.uploadType = "0"; - } - else { + } else { this.uploadType = "1"; this.imgUrl = imgUrl; } @@ -33,16 +37,21 @@ public void execute(String uri, File data, ResponseHandler create(RequestHttp requestHttp, String respType, String imgUrl) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp, String respType, String imgUrl) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheMinishopMediaUploadRequestCustomizeExecutor(requestHttp, respType, imgUrl); + return new ApacheMinishopMediaUploadRequestCustomizeExecutor( + (RequestHttp) requestHttp, respType, imgUrl); case JODD_HTTP: - return new JoddHttpMinishopMediaUploadRequestCustomizeExecutor(requestHttp, respType, imgUrl); + return new JoddHttpMinishopMediaUploadRequestCustomizeExecutor((RequestHttp) requestHttp, respType, imgUrl); case OK_HTTP: - return new OkHttpMinishopMediaUploadRequestCustomizeExecutor(requestHttp, respType, imgUrl); + return new OkHttpMinishopMediaUploadRequestCustomizeExecutor((RequestHttp) requestHttp, respType, imgUrl); + case HTTP_COMPONENTS: + return new HttpComponentsMinishopMediaUploadRequestCustomizeExecutor( + (RequestHttp) requestHttp, respType, imgUrl); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestExecutor.java index ee4608edf3..e6018a7791 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MinishopUploadRequestExecutor.java @@ -1,11 +1,16 @@ package me.chanjar.weixin.common.util.http; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.apache.ApacheMinishopMediaUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsMinishopMediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpMinishopMediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.okhttp.OkHttpMinishopMediaUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.OkHttpClient; import java.io.File; import java.io.IOException; @@ -13,7 +18,7 @@ public abstract class MinishopUploadRequestExecutor implements RequestExecutor { protected RequestHttp requestHttp; - public MinishopUploadRequestExecutor(RequestHttp requestHttp) { + public MinishopUploadRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } @@ -22,16 +27,21 @@ public void execute(String uri, File data, ResponseHandler create(RequestHttp requestHttp) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheMinishopMediaUploadRequestExecutor(requestHttp); + return new ApacheMinishopMediaUploadRequestExecutor( + (RequestHttp) requestHttp); case JODD_HTTP: - return new JoddHttpMinishopMediaUploadRequestExecutor(requestHttp); + return new JoddHttpMinishopMediaUploadRequestExecutor((RequestHttp) requestHttp); case OK_HTTP: - return new OkHttpMinishopMediaUploadRequestExecutor(requestHttp); + return new OkHttpMinishopMediaUploadRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsMinishopMediaUploadRequestExecutor( + (RequestHttp) requestHttp); default: - return null; + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java index b7bc850f8f..36be78b8ae 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java @@ -26,6 +26,6 @@ public interface RequestHttp { * * @return HttpType */ - HttpType getRequestType(); + HttpClientType getRequestType(); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java index 266fd226e7..a880a9323c 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java @@ -1,11 +1,16 @@ package me.chanjar.weixin.common.util.http; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.apache.ApacheSimpleGetRequestExecutor; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsSimpleGetRequestExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimpleGetRequestExecutor; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimpleGetRequestExecutor; +import okhttp3.OkHttpClient; import java.io.IOException; @@ -27,16 +32,21 @@ public void execute(String uri, String data, ResponseHandler handler, Wx handler.handle(this.execute(uri, data, wxType)); } + @SuppressWarnings("unchecked") public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheSimpleGetRequestExecutor(requestHttp); + return new ApacheSimpleGetRequestExecutor( + (RequestHttp) requestHttp); case JODD_HTTP: - return new JoddHttpSimpleGetRequestExecutor(requestHttp); + return new JoddHttpSimpleGetRequestExecutor((RequestHttp) requestHttp); case OK_HTTP: - return new OkHttpSimpleGetRequestExecutor(requestHttp); + return new OkHttpSimpleGetRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsSimpleGetRequestExecutor( + (RequestHttp) requestHttp); default: - throw new IllegalArgumentException("非法请求参数"); + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java index 0366b156af..2cc086cd0f 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java @@ -1,11 +1,16 @@ package me.chanjar.weixin.common.util.http; +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.apache.ApacheSimplePostRequestExecutor; +import me.chanjar.weixin.common.util.http.hc.HttpComponentsSimplePostRequestExecutor; import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimplePostRequestExecutor; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimplePostRequestExecutor; +import okhttp3.OkHttpClient; import org.jetbrains.annotations.NotNull; import java.io.IOException; @@ -18,7 +23,7 @@ public abstract class SimplePostRequestExecutor implements RequestExecutor { protected RequestHttp requestHttp; - public SimplePostRequestExecutor(RequestHttp requestHttp) { + public SimplePostRequestExecutor(RequestHttp requestHttp) { this.requestHttp = requestHttp; } @@ -28,16 +33,21 @@ public void execute(String uri, String data, ResponseHandler handler, Wx handler.handle(this.execute(uri, data, wxType)); } - public static RequestExecutor create(RequestHttp requestHttp) { + @SuppressWarnings("unchecked") + public static RequestExecutor create(RequestHttp requestHttp) { switch (requestHttp.getRequestType()) { case APACHE_HTTP: - return new ApacheSimplePostRequestExecutor(requestHttp); + return new ApacheSimplePostRequestExecutor( + (RequestHttp) requestHttp); case JODD_HTTP: - return new JoddHttpSimplePostRequestExecutor(requestHttp); + return new JoddHttpSimplePostRequestExecutor((RequestHttp) requestHttp); case OK_HTTP: - return new OkHttpSimplePostRequestExecutor(requestHttp); + return new OkHttpSimplePostRequestExecutor((RequestHttp) requestHttp); + case HTTP_COMPONENTS: + return new HttpComponentsSimplePostRequestExecutor( + (RequestHttp) requestHttp); default: - throw new IllegalArgumentException("非法请求参数"); + throw new IllegalArgumentException("不支持的http执行器类型:" + requestHttp.getRequestType()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheBasicResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheBasicResponseHandler.java new file mode 100644 index 0000000000..a91fc383ca --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheBasicResponseHandler.java @@ -0,0 +1,9 @@ +package me.chanjar.weixin.common.util.http.apache; + +import org.apache.http.impl.client.BasicResponseHandler; + +public class ApacheBasicResponseHandler extends BasicResponseHandler { + + public static final ApacheBasicResponseHandler INSTANCE = new ApacheBasicResponseHandler(); + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java index 0d5073de14..5b13e7cc17 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java @@ -21,36 +21,66 @@ public interface ApacheHttpClientBuilder { /** * 代理服务器地址. + * + * @param httpProxyHost 代理服务器地址 + * @return ApacheHttpClientBuilder */ ApacheHttpClientBuilder httpProxyHost(String httpProxyHost); /** * 代理服务器端口. + * + * @param httpProxyPort 代理服务器端口 + * @return ApacheHttpClientBuilder */ ApacheHttpClientBuilder httpProxyPort(int httpProxyPort); /** * 代理服务器用户名. + * + * @param httpProxyUsername 代理服务器用户名 + * @return ApacheHttpClientBuilder */ ApacheHttpClientBuilder httpProxyUsername(String httpProxyUsername); /** * 代理服务器密码. + * + * @param httpProxyPassword 代理服务器密码 + * @return ApacheHttpClientBuilder */ ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword); /** * 重试策略. + * + * @param httpRequestRetryHandler 重试处理器 + * @return ApacheHttpClientBuilder */ - ApacheHttpClientBuilder httpRequestRetryHandler(HttpRequestRetryHandler httpRequestRetryHandler ); + ApacheHttpClientBuilder httpRequestRetryHandler(HttpRequestRetryHandler httpRequestRetryHandler); /** * 超时时间. + * + * @param keepAliveStrategy 保持连接策略 + * @return ApacheHttpClientBuilder */ ApacheHttpClientBuilder keepAliveStrategy(ConnectionKeepAliveStrategy keepAliveStrategy); /** * ssl连接socket工厂. + * + * @param sslConnectionSocketFactory SSL连接Socket工厂 + * @return ApacheHttpClientBuilder */ ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory); + + /** + * 支持的TLS协议版本. + * Supported TLS protocol versions. + * + * @param supportedProtocols 支持的协议版本数组 + * @return ApacheHttpClientBuilder + */ + ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java index 6a136600e5..b3ebf350be 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java @@ -117,6 +117,13 @@ public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFac return this; } + @Override + public ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols) { + // This implementation doesn't use the supportedProtocols parameter as it relies on the provided SSLConnectionSocketFactory + // Users should configure the SSLConnectionSocketFactory with desired protocols before setting it + return this; + } + /** * 获取链接的超时时间设置,默认3000ms *

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

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

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

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

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

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

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

+ */ + private int idleConnTimeout = 60000; + /** + * 检查空间链接的间隔周期,默认60000ms. + */ + private int checkWaitTime = 60000; + /** + * 每路的最大链接数,默认10 + */ + private int maxConnPerHost = 10; + /** + * 最大总连接数,默认50 + */ + private int maxTotalConn = 50; + /** + * 自定义httpclient的User Agent + */ + private String userAgent; + + /** + * 自定义请求拦截器 + */ + private List requestInterceptors = new ArrayList<>(); + + /** + * 自定义响应拦截器 + */ + private List responseInterceptors = new ArrayList<>(); + + /** + * 自定义重试策略 + */ + private HttpRequestRetryStrategy httpRequestRetryStrategy; + + /** + * 自定义KeepAlive策略 + */ + private ConnectionKeepAliveStrategy connectionKeepAliveStrategy; + + private String httpProxyHost; + private int httpProxyPort; + private String httpProxyUsername; + private char[] httpProxyPassword; + /** + * 持有client对象,仅初始化一次,避免多service实例的时候造成重复初始化的问题 + */ + private CloseableHttpClient closeableHttpClient; + + private DefaultHttpComponentsClientBuilder() { + } + + public static DefaultHttpComponentsClientBuilder get() { + return SingletonHolder.INSTANCE; + } + + @Override + public HttpComponentsClientBuilder httpProxyHost(String httpProxyHost) { + this.httpProxyHost = httpProxyHost; + return this; + } + + @Override + public HttpComponentsClientBuilder httpProxyPort(int httpProxyPort) { + this.httpProxyPort = httpProxyPort; + return this; + } + + @Override + public HttpComponentsClientBuilder httpProxyUsername(String httpProxyUsername) { + this.httpProxyUsername = httpProxyUsername; + return this; + } + + @Override + public HttpComponentsClientBuilder httpProxyPassword(char[] httpProxyPassword) { + this.httpProxyPassword = httpProxyPassword; + return this; + } + + @Override + public HttpComponentsClientBuilder httpRequestRetryStrategy(HttpRequestRetryStrategy httpRequestRetryStrategy) { + this.httpRequestRetryStrategy = httpRequestRetryStrategy; + return this; + } + + @Override + public HttpComponentsClientBuilder keepAliveStrategy(ConnectionKeepAliveStrategy keepAliveStrategy) { + this.connectionKeepAliveStrategy = keepAliveStrategy; + return this; + } + + private synchronized void prepare() { + if (prepared.get()) { + return; + } + + SSLContext sslcontext; + try { + sslcontext = SSLContexts.custom() + .loadTrustMaterial(TrustAllStrategy.INSTANCE) // 忽略对服务器端证书的校验 + .build(); + } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { + log.error("构建 SSLContext 时发生异常!", e); + throw new RuntimeException(e); + } + + PoolingHttpClientConnectionManager connManager = PoolingHttpClientConnectionManagerBuilder.create() + .setTlsSocketStrategy(new DefaultClientTlsStrategy(sslcontext, NoopHostnameVerifier.INSTANCE)) + .setMaxConnTotal(this.maxTotalConn) + .setMaxConnPerRoute(this.maxConnPerHost) + .setDefaultSocketConfig(SocketConfig.custom() + .setSoTimeout(this.soTimeout, TimeUnit.MILLISECONDS) + .build()) + .setDefaultConnectionConfig(ConnectionConfig.custom() + .setConnectTimeout(this.connectionTimeout, TimeUnit.MILLISECONDS) + .build()) + .build(); + + HttpClientBuilder httpClientBuilder = HttpClients.custom() + .setConnectionManager(connManager) + .setConnectionManagerShared(true) + .setDefaultRequestConfig(RequestConfig.custom() + .setConnectionRequestTimeout(this.connectionRequestTimeout, TimeUnit.MILLISECONDS) + .build() + ); + + // 设置重试策略,没有则使用默认 + httpClientBuilder.setRetryStrategy(ObjectUtils.defaultIfNull(httpRequestRetryStrategy, NoopRetryStrategy.INSTANCE)); + + // 设置KeepAliveStrategy,没有使用默认 + if (connectionKeepAliveStrategy != null) { + httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy); + } + + if (StringUtils.isNotBlank(this.httpProxyHost) && StringUtils.isNotBlank(this.httpProxyUsername)) { + // 使用代理服务器 需要用户认证的代理服务器 + BasicCredentialsProvider provider = new BasicCredentialsProvider(); + provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort), + new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword)); + httpClientBuilder.setDefaultCredentialsProvider(provider); + httpClientBuilder.setProxy(new HttpHost(this.httpProxyHost, this.httpProxyPort)); + } + + if (StringUtils.isNotBlank(this.userAgent)) { + httpClientBuilder.setUserAgent(this.userAgent); + } + + //添加自定义的请求拦截器 + requestInterceptors.forEach(httpClientBuilder::addRequestInterceptorFirst); + + //添加自定义的响应拦截器 + responseInterceptors.forEach(httpClientBuilder::addResponseInterceptorLast); + + this.closeableHttpClient = httpClientBuilder.build(); + prepared.set(true); + } + + @Override + public CloseableHttpClient build() { + if (!prepared.get()) { + prepare(); + } + return this.closeableHttpClient; + } + + /** + * DefaultApacheHttpClientBuilder 改为单例模式,并持有唯一的CloseableHttpClient(仅首次调用创建) + */ + private static class SingletonHolder { + private static final DefaultHttpComponentsClientBuilder INSTANCE = new DefaultHttpComponentsClientBuilder(); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsClientBuilder.java new file mode 100644 index 0000000000..66cd58e15f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsClientBuilder.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import org.apache.hc.client5.http.ConnectionKeepAliveStrategy; +import org.apache.hc.client5.http.HttpRequestRetryStrategy; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; + +/** + * httpclient build interface. + * + * @author altusea + */ +public interface HttpComponentsClientBuilder { + + /** + * 构建httpclient实例. + * + * @return new instance of CloseableHttpClient + */ + CloseableHttpClient build(); + + /** + * 代理服务器地址. + */ + HttpComponentsClientBuilder httpProxyHost(String httpProxyHost); + + /** + * 代理服务器端口. + */ + HttpComponentsClientBuilder httpProxyPort(int httpProxyPort); + + /** + * 代理服务器用户名. + */ + HttpComponentsClientBuilder httpProxyUsername(String httpProxyUsername); + + /** + * 代理服务器密码. + */ + HttpComponentsClientBuilder httpProxyPassword(char[] httpProxyPassword); + + /** + * 重试策略. + */ + HttpComponentsClientBuilder httpRequestRetryStrategy(HttpRequestRetryStrategy httpRequestRetryStrategy); + + /** + * 超时时间. + */ + HttpComponentsClientBuilder keepAliveStrategy(ConnectionKeepAliveStrategy keepAliveStrategy); +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaDownloadRequestExecutor.java new file mode 100644 index 0000000000..26fbed93f3 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaDownloadRequestExecutor.java @@ -0,0 +1,79 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.fs.FileUtils; +import me.chanjar.weixin.common.util.http.BaseMediaDownloadRequestExecutor; +import me.chanjar.weixin.common.util.http.HttpResponseProxy; +import me.chanjar.weixin.common.util.http.RequestHttp; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +/** + * ApacheMediaDownloadRequestExecutor + * + * @author altusea + */ +public class HttpComponentsMediaDownloadRequestExecutor extends BaseMediaDownloadRequestExecutor { + + public HttpComponentsMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + super(requestHttp, tmpDirFile); + } + + @Override + public File execute(String uri, String queryParam, WxType wxType) throws WxErrorException, IOException { + if (queryParam != null) { + if (uri.indexOf('?') == -1) { + uri += '?'; + } + uri += uri.endsWith("?") ? queryParam : '&' + queryParam; + } + + HttpGet httpGet = new HttpGet(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpGet.setConfig(config); + } + + try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet); + InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { + Header[] contentTypeHeader = response.getHeaders("Content-Type"); + if (contentTypeHeader != null && contentTypeHeader.length > 0) { + if (contentTypeHeader[0].getValue().startsWith(ContentType.APPLICATION_JSON.getMimeType())) { + // application/json; encoding=utf-8 下载媒体文件出错 + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + throw new WxErrorException(WxError.fromJson(responseContent, wxType)); + } + } + + String fileName = HttpResponseProxy.from(response).getFileName(); + if (StringUtils.isBlank(fileName)) { + fileName = String.valueOf(System.currentTimeMillis()); + } + + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } + + return FileUtils.createTmpFile(inputStream, baseName, FilenameUtils.getExtension(fileName), super.tmpDirFile); + } catch (HttpException httpException) { + throw new ClientProtocolException(httpException.getMessage(), httpException); + } + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaInputStreamUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaInputStreamUploadRequestExecutor.java new file mode 100644 index 0000000000..4853b1572b --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaInputStreamUploadRequestExecutor.java @@ -0,0 +1,54 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.InputStreamData; +import me.chanjar.weixin.common.util.http.MediaInputStreamUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.IOException; + +/** + * 文件输入流上传. + * + * @author altusea + */ +public class HttpComponentsMediaInputStreamUploadRequestExecutor extends MediaInputStreamUploadRequestExecutor { + + public HttpComponentsMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMediaUploadResult execute(String uri, InputStreamData data, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (data != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", data.getInputStream(), ContentType.DEFAULT_BINARY, data.getFilename()) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return WxMediaUploadResult.fromJson(responseContent); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaUploadRequestExecutor.java new file mode 100644 index 0000000000..e65d855d52 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMediaUploadRequestExecutor.java @@ -0,0 +1,53 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; + +/** + * ApacheMediaUploadRequestExecutor + * + * @author altusea + */ +public class HttpComponentsMediaUploadRequestExecutor extends MediaUploadRequestExecutor { + + public HttpComponentsMediaUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMediaUploadResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", file) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return WxMediaUploadResult.fromJson(responseContent); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestCustomizeExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestCustomizeExecutor.java new file mode 100644 index 0000000000..711f538309 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestCustomizeExecutor.java @@ -0,0 +1,71 @@ +package me.chanjar.weixin.common.util.http.hc; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadCustomizeResult; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.MinishopUploadRequestCustomizeExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; + +/** + * ApacheMinishopMediaUploadRequestCustomizeExecutor + * + * @author altusea + */ +@Slf4j +public class HttpComponentsMinishopMediaUploadRequestCustomizeExecutor extends MinishopUploadRequestCustomizeExecutor { + + public HttpComponentsMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { + super(requestHttp, respType, imgUrl); + } + + @Override + public WxMinishopImageUploadCustomizeResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (this.uploadType.equals("0")) { + if (file == null) { + throw new WxErrorException("上传文件为空"); + } + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", file) + .addTextBody("resp_type", this.respType) + .addTextBody("upload_type", this.uploadType) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + else { + HttpEntity entity = MultipartEntityBuilder + .create() + .addTextBody("resp_type", this.respType) + .addTextBody("upload_type", this.uploadType) + .addTextBody("img_url", this.imgUrl) + .setMode(org.apache.hc.client5.http.entity.mime.HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + log.info("responseContent: {}", responseContent); + return WxMinishopImageUploadCustomizeResult.fromJson(responseContent); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestExecutor.java new file mode 100644 index 0000000000..72c1f2765f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsMinishopMediaUploadRequestExecutor.java @@ -0,0 +1,56 @@ +package me.chanjar.weixin.common.util.http.hc; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.MinishopUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; + +import java.io.File; +import java.io.IOException; + +/** + * ApacheMinishopMediaUploadRequestExecutor + * + * @author altusea + */ +@Slf4j +public class HttpComponentsMinishopMediaUploadRequestExecutor extends MinishopUploadRequestExecutor { + + public HttpComponentsMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMinishopImageUploadResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", file) + .setMode(HttpMultipartMode.EXTENDED) + .build(); + httpPost.setEntity(entity); + } + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + log.info("responseContent: {}", responseContent); + return WxMinishopImageUploadResult.fromJson(responseContent); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsResponseProxy.java new file mode 100644 index 0000000000..d55ff0735f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsResponseProxy.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.HttpResponseProxy; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.Header; + +public class HttpComponentsResponseProxy implements HttpResponseProxy { + + private final CloseableHttpResponse response; + + public HttpComponentsResponseProxy(CloseableHttpResponse closeableHttpResponse) { + this.response = closeableHttpResponse; + } + + @Override + public String getFileName() throws WxErrorException { + Header[] contentDispositionHeader = this.response.getHeaders("Content-disposition"); + if (contentDispositionHeader == null || contentDispositionHeader.length == 0) { + throw new WxErrorException("无法获取到文件名,Content-disposition为空"); + } + + return HttpResponseProxy.extractFileNameFromContentString(contentDispositionHeader[0].getValue()); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimpleGetRequestExecutor.java new file mode 100644 index 0000000000..0d212fe7e2 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimpleGetRequestExecutor.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.HttpHost; + +import java.io.IOException; + +/** + * ApacheSimpleGetRequestExecutor + * + * @author altusea + */ +public class HttpComponentsSimpleGetRequestExecutor extends SimpleGetRequestExecutor { + + public HttpComponentsSimpleGetRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public String execute(String uri, String queryParam, WxType wxType) throws WxErrorException, IOException { + if (queryParam != null) { + if (uri.indexOf('?') == -1) { + uri += '?'; + } + uri += uri.endsWith("?") ? queryParam : '&' + queryParam; + } + HttpGet httpGet = new HttpGet(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpGet.setConfig(config); + } + + String responseContent = requestHttp.getRequestHttpClient().execute(httpGet, Utf8ResponseHandler.INSTANCE); + return handleResponse(wxType, responseContent); + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimplePostRequestExecutor.java new file mode 100644 index 0000000000..45d2ca9f6e --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/HttpComponentsSimplePostRequestExecutor.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.common.util.http.hc; + +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.io.entity.StringEntity; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * ApacheSimplePostRequestExecutor + * + * @author altusea + */ +public class HttpComponentsSimplePostRequestExecutor extends SimplePostRequestExecutor { + + public HttpComponentsSimplePostRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public String execute(String uri, String postEntity, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + + if (postEntity != null) { + StringEntity entity = new StringEntity(postEntity, ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8)); + httpPost.setEntity(entity); + } + + String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE); + return this.handleResponse(wxType, responseContent); + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/InputStreamResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/InputStreamResponseHandler.java new file mode 100644 index 0000000000..27308151f7 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/InputStreamResponseHandler.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.common.util.http.hc; + +import org.apache.hc.client5.http.impl.classic.AbstractHttpClientResponseHandler; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; + +import java.io.IOException; +import java.io.InputStream; + +/** + * InputStreamResponseHandler + * + * @author altusea + */ +public class InputStreamResponseHandler extends AbstractHttpClientResponseHandler { + + public static final HttpClientResponseHandler INSTANCE = new InputStreamResponseHandler(); + + @Override + public InputStream handleEntity(HttpEntity entity) throws IOException { + return entity.getContent(); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/NoopRetryStrategy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/NoopRetryStrategy.java new file mode 100644 index 0000000000..9b4e3bc384 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/NoopRetryStrategy.java @@ -0,0 +1,34 @@ +package me.chanjar.weixin.common.util.http.hc; + +import org.apache.hc.client5.http.HttpRequestRetryStrategy; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.util.TimeValue; + +import java.io.IOException; + +/** + * NoopRetryStrategy + * + * @author altusea + */ +public class NoopRetryStrategy implements HttpRequestRetryStrategy { + + public static final HttpRequestRetryStrategy INSTANCE = new NoopRetryStrategy(); + + @Override + public boolean retryRequest(HttpRequest request, IOException exception, int execCount, HttpContext context) { + return false; + } + + @Override + public boolean retryRequest(HttpResponse response, int execCount, HttpContext context) { + return false; + } + + @Override + public TimeValue getRetryInterval(HttpResponse response, int execCount, HttpContext context) { + return TimeValue.ZERO_MILLISECONDS; + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/Utf8ResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/Utf8ResponseHandler.java new file mode 100644 index 0000000000..81699ef57b --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/hc/Utf8ResponseHandler.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.common.util.http.hc; + +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.impl.classic.AbstractHttpClientResponseHandler; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.apache.hc.core5.http.io.entity.EntityUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * Utf8ResponseHandler + * + * @author altusea + */ +public class Utf8ResponseHandler extends AbstractHttpClientResponseHandler { + + public static final HttpClientResponseHandler INSTANCE = new Utf8ResponseHandler(); + + @Override + public String handleEntity(HttpEntity entity) throws IOException { + try { + return EntityUtils.toString(entity, StandardCharsets.UTF_8); + } catch (final ParseException ex) { + throw new ClientProtocolException(ex); + } + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java index 920cf2d03b..bc2fbc17f3 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java @@ -4,7 +4,6 @@ import jodd.http.HttpRequest; import jodd.http.HttpResponse; import jodd.http.ProxyInfo; -import jodd.util.StringPool; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; @@ -28,7 +27,7 @@ * created on 2017/5/5 */ public class JoddHttpMediaDownloadRequestExecutor extends BaseMediaDownloadRequestExecutor { - public JoddHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + public JoddHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { super(requestHttp, tmpDirFile); } @@ -56,7 +55,7 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError throw new WxErrorException(WxError.fromJson(response.bodyText(), wxType)); } - String fileName = new HttpResponseProxy(response).getFileName(); + String fileName = HttpResponseProxy.from(response).getFileName(); if (StringUtils.isBlank(fileName)) { return null; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaInputStreamUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaInputStreamUploadRequestExecutor.java index 311b7c49c5..915db21c65 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaInputStreamUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaInputStreamUploadRequestExecutor.java @@ -25,7 +25,7 @@ * created on 2022/02/15 */ public class JoddHttpMediaInputStreamUploadRequestExecutor extends MediaInputStreamUploadRequestExecutor { - public JoddHttpMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { + public JoddHttpMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java index 876caa29fb..1ed59a71da 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java @@ -4,7 +4,6 @@ import jodd.http.HttpRequest; import jodd.http.HttpResponse; import jodd.http.ProxyInfo; -import jodd.util.StringPool; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.error.WxError; @@ -23,7 +22,7 @@ * created on 2017/5/5 */ public class JoddHttpMediaUploadRequestExecutor extends MediaUploadRequestExecutor { - public JoddHttpMediaUploadRequestExecutor(RequestHttp requestHttp) { + public JoddHttpMediaUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestCustomizeExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestCustomizeExecutor.java index 1d6f24fa2a..66074d8103 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestCustomizeExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestCustomizeExecutor.java @@ -22,7 +22,7 @@ */ @Slf4j public class JoddHttpMinishopMediaUploadRequestCustomizeExecutor extends MinishopUploadRequestCustomizeExecutor { - public JoddHttpMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { + public JoddHttpMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { super(requestHttp, respType, imgUrl); } @@ -51,7 +51,7 @@ public WxMinishopImageUploadCustomizeResult execute(String uri, File file, WxTyp if (error.getErrorCode() != 0) { throw new WxErrorException(error); } - log.info("responseContent: " + responseContent); + log.info("responseContent: {}", responseContent); return WxMinishopImageUploadCustomizeResult.fromJson(responseContent); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestExecutor.java index 4cb9c50ee0..c7c35dd798 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMinishopMediaUploadRequestExecutor.java @@ -5,12 +5,10 @@ import jodd.http.HttpResponse; import jodd.http.ProxyInfo; import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.MinishopUploadRequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; @@ -26,7 +24,7 @@ */ @Slf4j public class JoddHttpMinishopMediaUploadRequestExecutor extends MinishopUploadRequestExecutor { - public JoddHttpMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { + public JoddHttpMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -46,7 +44,7 @@ public WxMinishopImageUploadResult execute(String uri, File file, WxType wxType) if (error.getErrorCode() != 0) { throw new WxErrorException(error); } - log.info("responseContent: " + responseContent); + log.info("responseContent: {}", responseContent); return WxMinishopImageUploadResult.fromJson(responseContent); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpResponseProxy.java new file mode 100644 index 0000000000..1bda38a497 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpResponseProxy.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.common.util.http.jodd; + +import jodd.http.HttpResponse; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.HttpResponseProxy; + +public class JoddHttpResponseProxy implements HttpResponseProxy { + + private final HttpResponse response; + + public JoddHttpResponseProxy(HttpResponse httpResponse) { + this.response = httpResponse; + } + + @Override + public String getFileName() throws WxErrorException { + String content = response.header("Content-disposition"); + return HttpResponseProxy.extractFileNameFromContentString(content); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java index 869ea8c04e..ed8288b04f 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java @@ -4,7 +4,6 @@ import jodd.http.HttpRequest; import jodd.http.HttpResponse; import jodd.http.ProxyInfo; -import jodd.util.StringPool; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestHttp; @@ -20,7 +19,7 @@ * created on 2017/5/4 */ public class JoddHttpSimpleGetRequestExecutor extends SimpleGetRequestExecutor { - public JoddHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { + public JoddHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java index 654378271c..095493c75e 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java @@ -19,7 +19,7 @@ * created on 2017/5/4 */ public class JoddHttpSimplePostRequestExecutor extends SimplePostRequestExecutor { - public JoddHttpSimplePostRequestExecutor(RequestHttp requestHttp) { + public JoddHttpSimplePostRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java index dda52e2f7b..0610d3f51c 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java @@ -25,7 +25,7 @@ */ @Slf4j public class OkHttpMediaDownloadRequestExecutor extends BaseMediaDownloadRequestExecutor { - public OkHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { + public OkHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { super(requestHttp, tmpDirFile); } @@ -51,7 +51,7 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError throw new WxErrorException(WxError.fromJson(response.body().string(), wxType)); } - String fileName = new HttpResponseProxy(response).getFileName(); + String fileName = HttpResponseProxy.from(response).getFileName(); if (StringUtils.isBlank(fileName)) { return null; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaInputStreamUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaInputStreamUploadRequestExecutor.java index 613bd7ecfa..c30cc619aa 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaInputStreamUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaInputStreamUploadRequestExecutor.java @@ -20,7 +20,7 @@ * created on 2022/02/15 */ public class OkHttpMediaInputStreamUploadRequestExecutor extends MediaInputStreamUploadRequestExecutor { - public OkHttpMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { + public OkHttpMediaInputStreamUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaUploadRequestExecutor.java index 1b5241ff70..6a7b0b794d 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaUploadRequestExecutor.java @@ -18,7 +18,7 @@ * created on 2017/5/5 */ public class OkHttpMediaUploadRequestExecutor extends MediaUploadRequestExecutor { - public OkHttpMediaUploadRequestExecutor(RequestHttp requestHttp) { + public OkHttpMediaUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestCustomizeExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestCustomizeExecutor.java index a8b76321ca..a2c78f423b 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestCustomizeExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestCustomizeExecutor.java @@ -18,7 +18,7 @@ */ @Slf4j public class OkHttpMinishopMediaUploadRequestCustomizeExecutor extends MinishopUploadRequestCustomizeExecutor { - public OkHttpMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { + public OkHttpMinishopMediaUploadRequestCustomizeExecutor(RequestHttp requestHttp, String respType, String imgUrl) { super(requestHttp, respType, imgUrl); } @@ -50,7 +50,7 @@ public WxMinishopImageUploadCustomizeResult execute(String uri, File file, WxTyp if (error.getErrorCode() != 0) { throw new WxErrorException(error); } - log.info("responseContent: " + responseContent); + log.info("responseContent: {}", responseContent); return WxMinishopImageUploadCustomizeResult.fromJson(responseContent); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestExecutor.java index 5c40b1f6ba..f2df3c7e73 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMinishopMediaUploadRequestExecutor.java @@ -1,12 +1,10 @@ package me.chanjar.weixin.common.util.http.okhttp; import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.bean.result.WxMinishopImageUploadResult; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.MinishopUploadRequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import okhttp3.*; @@ -22,7 +20,7 @@ */ @Slf4j public class OkHttpMinishopMediaUploadRequestExecutor extends MinishopUploadRequestExecutor { - public OkHttpMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { + public OkHttpMinishopMediaUploadRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } @@ -43,7 +41,7 @@ public WxMinishopImageUploadResult execute(String uri, File file, WxType wxType) if (error.getErrorCode() != 0) { throw new WxErrorException(error); } - log.info("responseContent: " + responseContent); + log.info("responseContent: {}", responseContent); return WxMinishopImageUploadResult.fromJson(responseContent); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpResponseProxy.java new file mode 100644 index 0000000000..95c290735c --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpResponseProxy.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.common.util.http.okhttp; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.HttpResponseProxy; +import okhttp3.Response; + +public class OkHttpResponseProxy implements HttpResponseProxy { + + private final Response response; + + public OkHttpResponseProxy(Response response) { + this.response = response; + } + + @Override + public String getFileName() throws WxErrorException { + String content = this.response.header("Content-disposition"); + return HttpResponseProxy.extractFileNameFromContentString(content); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java index ec031d3afe..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 @@ -11,13 +11,13 @@ import java.io.IOException; /** - * . + * * * @author ecoolper * created on 2017/5/4 */ public class OkHttpSimpleGetRequestExecutor extends SimpleGetRequestExecutor { - public OkHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { + public OkHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java index a289f362e3..3044f29d60 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java @@ -18,7 +18,7 @@ */ @Slf4j public class OkHttpSimplePostRequestExecutor extends SimplePostRequestExecutor { - public OkHttpSimplePostRequestExecutor(RequestHttp requestHttp) { + public OkHttpSimplePostRequestExecutor(RequestHttp requestHttp) { super(requestHttp); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonHelper.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonHelper.java index 0d807402ac..84ebffe72f 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 @@ -108,7 +108,7 @@ public static float getAsPrimitiveFloat(JsonElement element) { } public static Integer[] getIntArray(JsonObject o, String string) { - JsonArray jsonArray = getAsJsonArray(o.getAsJsonArray(string)); + JsonArray jsonArray = getAsJsonArray(o.get(string)); if (jsonArray == null) { return null; } @@ -122,7 +122,7 @@ public static Integer[] getIntArray(JsonObject o, String string) { } public static String[] getStringArray(JsonObject o, String string) { - JsonArray jsonArray = getAsJsonArray(o.getAsJsonArray(string)); + JsonArray jsonArray = getAsJsonArray(o.get(string)); if (jsonArray == null) { return null; } @@ -136,7 +136,7 @@ public static String[] getStringArray(JsonObject o, String string) { } public static Long[] getLongArray(JsonObject o, String string) { - JsonArray jsonArray = getAsJsonArray(o.getAsJsonArray(string)); + JsonArray jsonArray = getAsJsonArray(o.get(string)); if (jsonArray == null) { return null; } @@ -150,7 +150,7 @@ public static Long[] getLongArray(JsonObject o, String string) { } public static JsonArray getAsJsonArray(JsonElement element) { - return element == null ? null : element.getAsJsonArray(); + return (element == null || !element.isJsonArray()) ? null : element.getAsJsonArray(); } /** diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java index 061a3cb2ee..caa07d0eaf 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonParser.java @@ -10,17 +10,16 @@ * @author niefy */ public class GsonParser { - private static final JsonParser JSON_PARSER = new JsonParser(); public static JsonObject parse(String json) { - return JSON_PARSER.parse(json).getAsJsonObject(); + return new JsonParser().parse(json).getAsJsonObject(); } public static JsonObject parse(Reader json) { - return JSON_PARSER.parse(json).getAsJsonObject(); + return new JsonParser().parse(json).getAsJsonObject(); } public static JsonObject parse(JsonReader json) { - return JSON_PARSER.parse(json).getAsJsonObject(); + return new JsonParser().parse(json).getAsJsonObject(); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java index ff260c16fb..8f3dafe48a 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java @@ -1,5 +1,7 @@ package me.chanjar.weixin.common.util.json; +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import me.chanjar.weixin.common.bean.WxAccessToken; @@ -7,6 +9,9 @@ import me.chanjar.weixin.common.bean.menu.WxMenu; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; + +import java.io.File; import java.util.Objects; /** @@ -25,8 +30,24 @@ public class WxGsonBuilder { INSTANCE.registerTypeAdapter(WxMediaUploadResult.class, new WxMediaUploadResultAdapter()); INSTANCE.registerTypeAdapter(WxNetCheckResult.class, new WxNetCheckResultGsonAdapter()); + INSTANCE.setExclusionStrategies(new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes fieldAttributes) { + return false; + } + + @Override + public boolean shouldSkipClass(Class aClass) { + return aClass == File.class || aClass == ApacheHttpClientBuilder.class; + } + }); } + /** + * 创建Gson实例 + * + * @return Gson实例 + */ public static Gson create() { if (Objects.isNull(GSON_INSTANCE)) { synchronized (INSTANCE) { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java index 50d3b0d630..5e7f9b41d9 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java @@ -1,11 +1,3 @@ -/* - * KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved. - * - * This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended - * only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction - * arose from modification of the original source, or other redistribution of this source - * is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. - */ package me.chanjar.weixin.common.util.json; import com.google.gson.*; @@ -14,6 +6,7 @@ import me.chanjar.weixin.common.bean.menu.WxMenuRule; import java.lang.reflect.Type; +import java.util.Optional; /** @@ -21,96 +14,111 @@ */ public class WxMenuGsonAdapter implements JsonSerializer, JsonDeserializer { + // JSON字段常量定义 + private static final String FIELD_BUTTON = "button"; + private static final String FIELD_MATCH_RULE = "matchrule"; + private static final String FIELD_SUB_BUTTON = "sub_button"; + private static final String FIELD_MENU = "menu"; + + // 菜单按钮字段常量 + private static final String FIELD_TYPE = "type"; + private static final String FIELD_NAME = "name"; + private static final String FIELD_KEY = "key"; + private static final String FIELD_URL = "url"; + private static final String FIELD_MEDIA_ID = "media_id"; + private static final String FIELD_ARTICLE_ID = "article_id"; + private static final String FIELD_APP_ID = "appid"; + private static final String FIELD_PAGE_PATH = "pagepath"; + + // 菜单规则字段常量 + private static final String FIELD_TAG_ID = "tag_id"; + private static final String FIELD_SEX = "sex"; + private static final String FIELD_COUNTRY = "country"; + private static final String FIELD_PROVINCE = "province"; + private static final String FIELD_CITY = "city"; + private static final String FIELD_CLIENT_PLATFORM_TYPE = "client_platform_type"; + private static final String FIELD_LANGUAGE = "language"; + @Override public JsonElement serialize(WxMenu menu, Type typeOfSrc, JsonSerializationContext context) { JsonObject json = new JsonObject(); - JsonArray buttonArray = new JsonArray(); - for (WxMenuButton button : menu.getButtons()) { - JsonObject buttonJson = convertToJson(button); - buttonArray.add(buttonJson); - } - json.add("button", buttonArray); - + Optional.ofNullable(menu.getButtons()) + .ifPresent(buttons -> buttons.stream() + .map(this::convertToJson) + .forEach(buttonArray::add)); + json.add(FIELD_BUTTON, buttonArray); if (menu.getMatchRule() != null) { - json.add("matchrule", convertToJson(menu.getMatchRule())); + json.add(FIELD_MATCH_RULE, convertToJson(menu.getMatchRule())); } - return json; } protected JsonObject convertToJson(WxMenuButton button) { JsonObject buttonJson = new JsonObject(); - buttonJson.addProperty("type", button.getType()); - buttonJson.addProperty("name", button.getName()); - buttonJson.addProperty("key", button.getKey()); - buttonJson.addProperty("url", button.getUrl()); - buttonJson.addProperty("media_id", button.getMediaId()); - buttonJson.addProperty("article_id", button.getArticleId()); - buttonJson.addProperty("appid", button.getAppId()); - buttonJson.addProperty("pagepath", button.getPagePath()); - if (button.getSubButtons() != null && button.getSubButtons().size() > 0) { + addPropertyIfNotNull(buttonJson, FIELD_TYPE, button.getType()); + addPropertyIfNotNull(buttonJson, FIELD_NAME, button.getName()); + addPropertyIfNotNull(buttonJson, FIELD_KEY, button.getKey()); + addPropertyIfNotNull(buttonJson, FIELD_URL, button.getUrl()); + addPropertyIfNotNull(buttonJson, FIELD_MEDIA_ID, button.getMediaId()); + addPropertyIfNotNull(buttonJson, FIELD_ARTICLE_ID, button.getArticleId()); + addPropertyIfNotNull(buttonJson, FIELD_APP_ID, button.getAppId()); + addPropertyIfNotNull(buttonJson, FIELD_PAGE_PATH, button.getPagePath()); + if (button.getSubButtons() != null && !button.getSubButtons().isEmpty()) { JsonArray buttonArray = new JsonArray(); - for (WxMenuButton sub_button : button.getSubButtons()) { - buttonArray.add(convertToJson(sub_button)); - } - buttonJson.add("sub_button", buttonArray); + button.getSubButtons().stream() + .map(this::convertToJson) + .forEach(buttonArray::add); + buttonJson.add(FIELD_SUB_BUTTON, buttonArray); } return buttonJson; } protected JsonObject convertToJson(WxMenuRule menuRule) { JsonObject matchRule = new JsonObject(); - matchRule.addProperty("tag_id", menuRule.getTagId()); - matchRule.addProperty("sex", menuRule.getSex()); - matchRule.addProperty("country", menuRule.getCountry()); - matchRule.addProperty("province", menuRule.getProvince()); - matchRule.addProperty("city", menuRule.getCity()); - matchRule.addProperty("client_platform_type", menuRule.getClientPlatformType()); - matchRule.addProperty("language", menuRule.getLanguage()); + addPropertyIfNotNull(matchRule, FIELD_TAG_ID, menuRule.getTagId()); + addPropertyIfNotNull(matchRule, FIELD_SEX, menuRule.getSex()); + addPropertyIfNotNull(matchRule, FIELD_COUNTRY, menuRule.getCountry()); + addPropertyIfNotNull(matchRule, FIELD_PROVINCE, menuRule.getProvince()); + addPropertyIfNotNull(matchRule, FIELD_CITY, menuRule.getCity()); + addPropertyIfNotNull(matchRule, FIELD_CLIENT_PLATFORM_TYPE, menuRule.getClientPlatformType()); + addPropertyIfNotNull(matchRule, FIELD_LANGUAGE, menuRule.getLanguage()); return matchRule; } - @Deprecated - private WxMenuRule convertToRule(JsonObject json) { - WxMenuRule menuRule = new WxMenuRule(); - //变态的微信接口,这里居然反人类的使用和序列化时不一样的名字 - //menuRule.setTagId(GsonHelper.getString(json,"tag_id")); - menuRule.setTagId(GsonHelper.getString(json, "group_id")); - menuRule.setSex(GsonHelper.getString(json, "sex")); - menuRule.setCountry(GsonHelper.getString(json, "country")); - menuRule.setProvince(GsonHelper.getString(json, "province")); - menuRule.setCity(GsonHelper.getString(json, "city")); - menuRule.setClientPlatformType(GsonHelper.getString(json, "client_platform_type")); - menuRule.setLanguage(GsonHelper.getString(json, "language")); - return menuRule; + private void addPropertyIfNotNull(JsonObject obj, String key, String value) { + if (value != null) { + obj.addProperty(key, value); + } } @Override public WxMenu deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - /* - * 操蛋的微信 - * 创建菜单时是 { button : ... } - * 查询菜单时是 { menu : { button : ... } } - * 现在企业号升级为企业微信后,没有此问题,因此需要单独处理 - */ - JsonArray buttonsJson = json.getAsJsonObject().get("menu").getAsJsonObject().get("button").getAsJsonArray(); - return this.buildMenuFromJson(buttonsJson); + JsonObject root = json.getAsJsonObject(); + JsonArray buttonsJson = null; + if (root.has(FIELD_MENU)) { + JsonObject menuObj = root.getAsJsonObject(FIELD_MENU); + buttonsJson = menuObj.getAsJsonArray(FIELD_BUTTON); + } else if (root.has(FIELD_BUTTON)) { + buttonsJson = root.getAsJsonArray(FIELD_BUTTON); + } + if (buttonsJson == null) { + throw new JsonParseException("No button array found in menu JSON"); + } + return buildMenuFromJson(buttonsJson); } protected WxMenu buildMenuFromJson(JsonArray buttonsJson) { WxMenu menu = new WxMenu(); - for (int i = 0; i < buttonsJson.size(); i++) { - JsonObject buttonJson = buttonsJson.get(i).getAsJsonObject(); + for (JsonElement btnElem : buttonsJson) { + JsonObject buttonJson = btnElem.getAsJsonObject(); WxMenuButton button = convertFromJson(buttonJson); menu.getButtons().add(button); - if (buttonJson.get("sub_button") == null || buttonJson.get("sub_button").isJsonNull()) { - continue; - } - JsonArray sub_buttonsJson = buttonJson.get("sub_button").getAsJsonArray(); - for (int j = 0; j < sub_buttonsJson.size(); j++) { - JsonObject sub_buttonJson = sub_buttonsJson.get(j).getAsJsonObject(); - button.getSubButtons().add(convertFromJson(sub_buttonJson)); + if (buttonJson.has(FIELD_SUB_BUTTON) && buttonJson.get(FIELD_SUB_BUTTON).isJsonArray()) { + JsonArray sub_buttonsJson = buttonJson.getAsJsonArray(FIELD_SUB_BUTTON); + for (JsonElement subBtnElem : sub_buttonsJson) { + button.getSubButtons().add(convertFromJson(subBtnElem.getAsJsonObject())); + } } } return menu; @@ -118,14 +126,14 @@ protected WxMenu buildMenuFromJson(JsonArray buttonsJson) { protected WxMenuButton convertFromJson(JsonObject json) { WxMenuButton button = new WxMenuButton(); - button.setName(GsonHelper.getString(json, "name")); - button.setKey(GsonHelper.getString(json, "key")); - button.setUrl(GsonHelper.getString(json, "url")); - button.setType(GsonHelper.getString(json, "type")); - button.setMediaId(GsonHelper.getString(json, "media_id")); - button.setArticleId(GsonHelper.getString(json, "article_id")); - button.setAppId(GsonHelper.getString(json, "appid")); - button.setPagePath(GsonHelper.getString(json, "pagepath")); + button.setName(GsonHelper.getString(json, FIELD_NAME)); + button.setKey(GsonHelper.getString(json, FIELD_KEY)); + button.setUrl(GsonHelper.getString(json, FIELD_URL)); + button.setType(GsonHelper.getString(json, FIELD_TYPE)); + button.setMediaId(GsonHelper.getString(json, FIELD_MEDIA_ID)); + button.setArticleId(GsonHelper.getString(json, FIELD_ARTICLE_ID)); + button.setAppId(GsonHelper.getString(json, FIELD_APP_ID)); + button.setPagePath(GsonHelper.getString(json, FIELD_PAGE_PATH)); return button; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxNetCheckResultGsonAdapter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxNetCheckResultGsonAdapter.java index 65c15fbc38..61492cbc7a 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxNetCheckResultGsonAdapter.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxNetCheckResultGsonAdapter.java @@ -20,7 +20,7 @@ public WxNetCheckResult deserialize(JsonElement json, Type typeOfT, JsonDeserial JsonArray dnssJson = json.getAsJsonObject().get("dns").getAsJsonArray(); List dnsInfoList = new ArrayList<>(); - if (dnssJson != null && dnssJson.size() > 0) { + if (dnssJson != null && !dnssJson.isEmpty()) { for (int i = 0; i < dnssJson.size(); i++) { JsonObject buttonJson = dnssJson.get(i).getAsJsonObject(); WxNetCheckResult.WxNetCheckDnsInfo dnsInfo = new WxNetCheckResult.WxNetCheckDnsInfo(); @@ -32,7 +32,7 @@ public WxNetCheckResult deserialize(JsonElement json, Type typeOfT, JsonDeserial JsonArray pingsJson = json.getAsJsonObject().get("ping").getAsJsonArray(); List pingInfoList = new ArrayList<>(); - if (pingsJson != null && pingsJson.size() > 0) { + if (pingsJson != null && !pingsJson.isEmpty()) { for (int i = 0; i < pingsJson.size(); i++) { JsonObject pingJson = pingsJson.get(i).getAsJsonObject(); WxNetCheckResult.WxNetCheckPingInfo pingInfo = new WxNetCheckResult.WxNetCheckPingInfo(); diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java index 214b4547b0..364b04b574 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java @@ -1,16 +1,11 @@ package me.chanjar.weixin.common.util.locks; import lombok.Getter; -import org.springframework.data.redis.connection.RedisStringCommands; -import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.RedisScript; -import org.springframework.data.redis.core.types.Expiration; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; +import java.util.Collections; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; @@ -47,13 +42,17 @@ public RedisTemplateSimpleDistributedLock( StringRedisTemplate redisTemplate, S @Override public void lock() { + boolean interrupted = false; while (!tryLock()) { try { Thread.sleep(1000); } catch (InterruptedException e) { - // Ignore + interrupted = true; } } + if (interrupted) { + Thread.currentThread().interrupt(); + } } @Override @@ -66,19 +65,20 @@ public void lockInterruptibly() throws InterruptedException { @Override public boolean tryLock() { String value = valueThreadLocal.get(); - if (value == null || value.length() == 0) { + if (value == null || value.isEmpty()) { value = UUID.randomUUID().toString(); valueThreadLocal.set(value); } - final byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); - final byte[] valueBytes = value.getBytes(StandardCharsets.UTF_8); - List redisResults = redisTemplate.executePipelined((RedisCallback) connection -> { - connection.set(keyBytes, valueBytes, Expiration.milliseconds(leaseMilliseconds), RedisStringCommands.SetOption.SET_IF_ABSENT); - connection.get(keyBytes); - return null; - }); - Object currentLockSecret = redisResults.size() > 1 ? redisResults.get(1) : redisResults.get(0); - return currentLockSecret != null && currentLockSecret.toString().equals(value); + + // Use high-level StringRedisTemplate API to ensure consistent key serialization + Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(key, value, leaseMilliseconds, TimeUnit.MILLISECONDS); + if (Boolean.TRUE.equals(lockAcquired)) { + return true; + } + + // Check if we already hold the lock (reentrant behavior) + String currentValue = redisTemplate.opsForValue().get(key); + return value.equals(currentValue); } @Override @@ -98,8 +98,8 @@ public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { public void unlock() { if (valueThreadLocal.get() != null) { // 提示: 必须指定returnType, 类型: 此处必须为Long, 不能是Integer - RedisScript script = new DefaultRedisScript("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", Long.class); - redisTemplate.execute(script, Arrays.asList(key), valueThreadLocal.get()); + RedisScript script = new DefaultRedisScript<>("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", Long.class); + redisTemplate.execute(script, Collections.singletonList(key), valueThreadLocal.get()); valueThreadLocal.remove(); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java index e5bdb38804..fd2f13a553 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java @@ -102,7 +102,7 @@ private StringManager(String packageName, Locale locale) { * * @param packageName The package name */ - public static final synchronized StringManager getManager( + public static synchronized StringManager getManager( String packageName) { return getManager(packageName, Locale.getDefault()); } @@ -115,7 +115,7 @@ public static final synchronized StringManager getManager( * @param packageName The package name * @param locale The Locale */ - public static final synchronized StringManager getManager( + public static synchronized StringManager getManager( String packageName, Locale locale) { Map map = MANAGERS.get(packageName); diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/IntegerArrayConverter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/IntegerArrayConverter.java index 3532fcab08..710547c746 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/IntegerArrayConverter.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/IntegerArrayConverter.java @@ -25,7 +25,7 @@ public String toString(Object obj) { @Override public Object fromString(String str) { - if (str == null || str.length() == 0) { + if (str == null || str.isEmpty()) { return null; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamCDataListConverter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamCDataListConverter.java new file mode 100644 index 0000000000..0b55a9c037 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamCDataListConverter.java @@ -0,0 +1,54 @@ +package me.chanjar.weixin.common.util.xml; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +/** + * 兼容两种格式的字符串列表转换器: + *
    + *
  • 旧格式(4.8.0之前):<MemChangeList><![CDATA[id1,id2]]></MemChangeList>
  • + *
  • 新格式(4.8.0起):<MemChangeList><Item><![CDATA[id1]]></Item></MemChangeList>
  • + *
+ * 解析结果统一为逗号分隔的字符串。 + */ +public class XStreamCDataListConverter implements Converter { + + @Override + public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { + if (source != null) { + writer.setValue(""); + } + } + + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + if (reader.hasMoreChildren()) { + // 新格式:含有 子元素 + StringBuilder sb = new StringBuilder(); + while (reader.hasMoreChildren()) { + reader.moveDown(); + String value = reader.getValue(); + if (value != null && !value.isEmpty()) { + if (sb.length() > 0) { + sb.append(","); + } + sb.append(value); + } + reader.moveUp(); + } + return sb.length() > 0 ? sb.toString() : null; + } else { + // 旧格式:直接 CDATA 文本 + String value = reader.getValue(); + return (value != null && !value.isEmpty()) ? value : null; + } + } + + @Override + public boolean canConvert(Class type) { + return type == String.class; + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java index 3fa91fa70e..51cd1e980c 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java @@ -22,6 +22,11 @@ public class XStreamInitializer { public static ClassLoader classLoader; + /** + * 设置类加载器 + * + * @param classLoaderInfo 类加载器 + */ public static void setClassLoader(ClassLoader classLoaderInfo) { classLoader = classLoaderInfo; } diff --git a/weixin-java-common/src/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/bean/CommonUploadParamTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/CommonUploadParamTest.java new file mode 100644 index 0000000000..05c8b379d3 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/CommonUploadParamTest.java @@ -0,0 +1,119 @@ +package me.chanjar.weixin.common.bean; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +/** + * CommonUploadParam 单元测试 + * + * @author Binary Wang + */ +@Test +public class CommonUploadParamTest { + + @Test + public void testFromFile() { + File file = new File("test.txt"); + CommonUploadParam param = CommonUploadParam.fromFile("media", file); + + Assert.assertNotNull(param); + Assert.assertEquals(param.getName(), "media"); + Assert.assertNotNull(param.getData()); + Assert.assertNull(param.getFormFields()); + } + + @Test + public void testFromBytes() { + byte[] bytes = "test content".getBytes(); + CommonUploadParam param = CommonUploadParam.fromBytes("media", "test.txt", bytes); + + Assert.assertNotNull(param); + Assert.assertEquals(param.getName(), "media"); + Assert.assertNotNull(param.getData()); + Assert.assertEquals(param.getData().getFileName(), "test.txt"); + Assert.assertNull(param.getFormFields()); + } + + @Test + public void testAddFormField() { + File file = new File("test.txt"); + CommonUploadParam param = CommonUploadParam.fromFile("media", file); + + // 添加单个表单字段 + param.addFormField("title", "测试标题"); + + Assert.assertNotNull(param.getFormFields()); + Assert.assertEquals(param.getFormFields().size(), 1); + Assert.assertEquals(param.getFormFields().get("title"), "测试标题"); + + // 添加多个表单字段 + param.addFormField("introduction", "测试介绍"); + + Assert.assertEquals(param.getFormFields().size(), 2); + Assert.assertEquals(param.getFormFields().get("introduction"), "测试介绍"); + } + + @Test + public void testAddFormFieldChaining() { + File file = new File("test.txt"); + CommonUploadParam param = CommonUploadParam.fromFile("media", file) + .addFormField("title", "测试标题") + .addFormField("introduction", "测试介绍"); + + Assert.assertNotNull(param.getFormFields()); + Assert.assertEquals(param.getFormFields().size(), 2); + Assert.assertEquals(param.getFormFields().get("title"), "测试标题"); + Assert.assertEquals(param.getFormFields().get("introduction"), "测试介绍"); + } + + @Test + public void testConstructorWithFormFields() { + CommonUploadData data = new CommonUploadData("test.txt", null, 0); + Map formFields = new HashMap<>(); + formFields.put("title", "测试标题"); + formFields.put("introduction", "测试介绍"); + + CommonUploadParam param = new CommonUploadParam("media", data, formFields); + + Assert.assertNotNull(param.getFormFields()); + Assert.assertEquals(param.getFormFields().size(), 2); + Assert.assertEquals(param.getFormFields().get("title"), "测试标题"); + Assert.assertEquals(param.getFormFields().get("introduction"), "测试介绍"); + } + + @Test + public void testToString() { + File file = new File("test.txt"); + CommonUploadParam param = CommonUploadParam.fromFile("media", file) + .addFormField("title", "测试标题"); + + String str = param.toString(); + Assert.assertTrue(str.contains("name:media")); + Assert.assertTrue(str.contains("formFields:")); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testAddFormFieldWithNullFieldName() { + File file = new File("test.txt"); + CommonUploadParam param = CommonUploadParam.fromFile("media", file); + param.addFormField(null, "value"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testAddFormFieldWithEmptyFieldName() { + File file = new File("test.txt"); + CommonUploadParam param = CommonUploadParam.fromFile("media", file); + param.addFormField("", "value"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testAddFormFieldWithNullFieldValue() { + File file = new File("test.txt"); + CommonUploadParam param = CommonUploadParam.fromFile("media", file); + param.addFormField("fieldName", null); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnumTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnumTest.java new file mode 100644 index 0000000000..66147bb7ec --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnumTest.java @@ -0,0 +1,62 @@ +package me.chanjar.weixin.common.error; + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; + +/** + * 微信小程序错误码枚举测试 + * + * @author GitHub Copilot + */ +@Test +public class WxMaErrorMsgEnumTest { + + public void testFindMsgByCodeForExistingCode() { + String msg = WxMaErrorMsgEnum.findMsgByCode(40001); + assertNotNull(msg); + } + + public void testFindMsgByCodeForNonExistingCode() { + String msg = WxMaErrorMsgEnum.findMsgByCode(999999); + assertNull(msg); + } + + /** + * 验证微信小程序虚拟支付错误码 + */ + public void testVirtualPaymentErrorCodes() { + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490001), "openid错误"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490002), "请求参数字段错误,具体看errmsg"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490003), "签名错误"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490004), "重复操作(赠送和代币支付和充值广告金相关接口会返回,表示之前的操作已经成功)"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490005), "订单已经通过cancel_currency_pay接口退款,不支持再退款"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490006), "代币的退款/支付操作金额不足"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490007), "图片或文字存在敏感内容,禁止使用"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490008), "代币未发布,不允许进行代币操作"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490009), "用户session_key不存在或已过期,请重新登录"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490011), "数据生成中,请稍后调用本接口获取"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490012), "批量任务运行中,请等待完成后才能再次运行"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490013), "禁止对核销状态的单进行退款"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490014), "退款操作进行中,稍后可以使用相同参数重试"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490015), "频率限制"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490016), "退款的left_fee字段与实际不符,请通过query_order接口查询确认"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490018), "广告金充值账户行业id不匹配"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490019), "广告金充值账户id已绑定其他appid"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490020), "广告金充值账户主体名称错误"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490021), "账户未完成进件"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490022), "广告金充值账户无效"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490023), "广告金余额不足"); + assertEquals(WxMaErrorMsgEnum.findMsgByCode(268490024), "广告金充值金额必须大于0"); + } + + /** + * 验证虚拟支付错误码中不存在的编号(如268490010、268490017)返回null + */ + public void testVirtualPaymentMissingCodes() { + assertNull(WxMaErrorMsgEnum.findMsgByCode(268490010)); + assertNull(WxMaErrorMsgEnum.findMsgByCode(268490017)); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java index 96ba20ba2b..fb53c8c4b6 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java @@ -35,6 +35,17 @@ public void testGetExpire() { Assert.assertTrue(expireSeconds <= 4 && expireSeconds >= 0); } + @Test + public void testGetExpireForNonExistentKey() { + String nonExistentKey = "non_existent_key_" + System.currentTimeMillis(); + Long expire = wxRedisOps.getExpire(nonExistentKey); + // 对于不存在的 key,底层使用 getExpire(key, TimeUnit.SECONDS) 时应返回负值 + // Spring Data Redis 2.x 和 3.x 约定:-2 表示 key 不存在,-1 表示 key 没有过期时间 + // 因此这里不应返回 null,而应返回一个小于 0 的值 + Assert.assertNotNull(expire, "Non-existent key should not have null expiration"); + Assert.assertTrue(expire < 0, "Non-existent key should have negative expiration"); + } + @Test public void testExpire() { String key = "access_token", value = String.valueOf(System.currentTimeMillis()); diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/DataUtilsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/DataUtilsTest.java index f5732d9a0b..c5088e78c9 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/DataUtilsTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/DataUtilsTest.java @@ -19,4 +19,42 @@ public void testHandleDataWithSecret() { final String s = DataUtils.handleDataWithSecret(data); assertTrue(s.contains("&secret=******&")); } + + @Test + public void testHandleDataWithSecretAtEnd() { + // Secret is the last parameter in the query string, so there is no trailing & + String data = "appid=wx123&secret=abc123"; + final String s = DataUtils.handleDataWithSecret(data); + assertFalse(s.contains("abc123"), "Secret at the end of the string should be masked"); + assertTrue(s.contains("secret=******"), "Secret should be replaced with asterisks"); + } + + @Test + public void testHandleDataWithSecretAsFirstParam() { + // Secret is the first parameter, so there is no leading & + String data = "secret=abc123&appid=wx123"; + final String s = DataUtils.handleDataWithSecret(data); + assertFalse(s.contains("abc123"), "Secret as the first parameter should be masked"); + assertTrue(s.contains("secret=******"), "Secret should be replaced with asterisks"); + } + + @Test + public void testHandleDataWithSecretEncodedValue() { + // The secret value contains URL-encoded and non-word characters; the whole value must be masked + String data = "appid=wx123&secret=abc%2Fdef-.+ghi&grant_type=client_credential"; + final String s = DataUtils.handleDataWithSecret(data); + assertFalse(s.contains("def"), "The full secret value must be masked, including after non-word characters"); + assertFalse(s.contains("%2F"), "Encoded characters in the secret must be masked too"); + assertTrue(s.contains("&secret=******&"), "Secret should be replaced with asterisks"); + } + + @Test + public void testHandleDataWithOpenidInJson() { + String data = "{\"code\":\"phone-code\",\"openid\":\"user-openid\"}"; + + String result = DataUtils.handleDataWithSecret(data); + + assertFalse(result.contains("user-openid")); + assertTrue(result.contains("\"openid\":\"******\"")); + } } 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 index 5a25fb1493..3ab603e47c 100644 --- 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 @@ -6,8 +6,10 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.Charset; import java.nio.file.Files; +import java.util.Base64; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -30,5 +32,31 @@ public void testTestCreateTmpFile() { @Test public void testImageToBase64ByStream() { + byte[] original = new byte[5000]; + for (int i = 0; i < original.length; i++) { + original[i] = (byte) (i % 256); + } + // A stream that returns at most 16 bytes per read() call (like a network stream), + // so a single in.read(buffer) does not fill the buffer. + InputStream chunked = new InputStream() { + private final ByteArrayInputStream delegate = new ByteArrayInputStream(original); + + @Override + public int read() { + return delegate.read(); + } + + @Override + public int read(byte[] b, int off, int len) { + return delegate.read(b, off, Math.min(len, 16)); + } + + @Override + public int available() { + return delegate.available(); + } + }; + String result = FileUtils.imageToBase64ByStream(chunked); + assertThat(result).isEqualTo(Base64.getEncoder().encodeToString(original)); } } 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/json/GsonHelperTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/json/GsonHelperTest.java index bafe3c30d1..775ac2207b 100644 --- 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 @@ -136,4 +136,29 @@ public void testBuildSimpleJsonObject() { System.out.println(GsonHelper.buildJsonObject(1, true, "jsonElement", new JsonObject())); System.out.println(GsonHelper.buildJsonObject("num", 2, "string", "cde", "char", 'a', "bool", true)); } + + /** + * If the JSON property exists but is NOT a JSON array (e.g. a plain string), + * getIntArray should return null gracefully rather than throwing ClassCastException. + */ + @Test + public void testGetIntArrayNonArrayProperty() { + JsonObject json = new JsonObject(); + json.addProperty("items", "not-an-array"); + assertThat(GsonHelper.getIntArray(json, "items")).isNull(); + } + + @Test + public void testGetStringArrayNonArrayProperty() { + JsonObject json = new JsonObject(); + json.addProperty("items", 123); + assertThat(GsonHelper.getStringArray(json, "items")).isNull(); + } + + @Test + public void testGetLongArrayNonArrayProperty() { + JsonObject json = new JsonObject(); + json.addProperty("items", "not-an-array"); + assertThat(GsonHelper.getLongArray(json, "items")).isNull(); + } } 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/RedisTemplateSimpleDistributedLockInterruptTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockInterruptTest.java new file mode 100644 index 0000000000..2b8ebccb10 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockInterruptTest.java @@ -0,0 +1,98 @@ +package me.chanjar.weixin.common.util.locks; + +import org.mockito.Mockito; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * 测试 RedisTemplateSimpleDistributedLock 的线程中断处理行为 + * + * @author GitHub Copilot + */ +public class RedisTemplateSimpleDistributedLockInterruptTest { + + private StringRedisTemplate mockRedisTemplate; + private ValueOperations mockValueOps; + private RedisTemplateSimpleDistributedLock lock; + + @BeforeMethod + @SuppressWarnings("unchecked") + public void setUp() { + mockRedisTemplate = Mockito.mock(StringRedisTemplate.class); + mockValueOps = Mockito.mock(ValueOperations.class); + Mockito.when(mockRedisTemplate.opsForValue()).thenReturn(mockValueOps); + lock = new RedisTemplateSimpleDistributedLock(mockRedisTemplate, "test_interrupt_lock", 60000); + } + + /** + * 测试 lock() 在 Thread.sleep 被中断时应恢复线程中断标志 + *

+ * 修复前:InterruptedException 被忽略(// Ignore),线程中断标志丢失 + * 修复后:调用 Thread.currentThread().interrupt() 恢复中断标志 + *

+ */ + @Test(description = "lock() 方法在中断时应恢复线程中断标志") + public void testLockRestoresInterruptedFlagAfterSleepInterruption() throws InterruptedException { + AtomicBoolean interruptedFlagAfterLock = new AtomicBoolean(false); + + // 第一次 setIfAbsent 返回 false(模拟锁被占用),第二次返回 true(模拟锁释放) + Mockito.when(mockValueOps.setIfAbsent(Mockito.anyString(), Mockito.anyString(), + Mockito.anyLong(), Mockito.any(TimeUnit.class))) + .thenReturn(false) + .thenReturn(true); + // get() 返回不同的值,确保不走可重入路径 + Mockito.when(mockValueOps.get(Mockito.anyString())).thenReturn("other-value"); + + Thread testThread = new Thread(() -> { + // 设置中断标志 + Thread.currentThread().interrupt(); + // 调用 lock(),第一次 tryLock 失败,sleep 会因中断标志立即抛出 InterruptedException + lock.lock(); + interruptedFlagAfterLock.set(Thread.currentThread().isInterrupted()); + }); + + testThread.start(); + testThread.join(5000); + + // 线程应该已经完成(不会永远阻塞) + Assert.assertFalse(testThread.isAlive(), "线程应该已完成"); + // 关键验证:中断标志应被恢复(而非被忽略丢失) + Assert.assertTrue(interruptedFlagAfterLock.get(), "lock()执行后线程中断标志应被恢复"); + } + + /** + * 测试 tryLock() 在 Redis 正常响应时的基本行为 + */ + @Test(description = "tryLock() 成功获取锁时应返回 true") + public void testTryLockSuccessfully() { + Mockito.when(mockValueOps.setIfAbsent(Mockito.anyString(), Mockito.anyString(), + Mockito.anyLong(), Mockito.any(TimeUnit.class))) + .thenReturn(true); + + boolean result = lock.tryLock(); + + Assert.assertTrue(result, "应成功获取锁"); + Assert.assertNotNull(lock.getLockSecretValue(), "锁值不应为null"); + } + + /** + * 测试 tryLock() 在锁已被其他线程持有时应返回 false + */ + @Test(description = "锁被占用时 tryLock() 应返回 false") + public void testTryLockWhenLockHeld() { + Mockito.when(mockValueOps.setIfAbsent(Mockito.anyString(), Mockito.anyString(), + Mockito.anyLong(), Mockito.any(TimeUnit.class))) + .thenReturn(false); + Mockito.when(mockValueOps.get(Mockito.anyString())).thenReturn("other-lock-value"); + + boolean result = lock.tryLock(); + + Assert.assertFalse(result, "锁被占用时应返回false"); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockSerializationTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockSerializationTest.java new file mode 100644 index 0000000000..ea4a131d37 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockSerializationTest.java @@ -0,0 +1,100 @@ +package me.chanjar.weixin.common.util.locks; + +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +/** + * 测试 RedisTemplateSimpleDistributedLock 在自定义 Key 序列化时的兼容性 + * + * 这个测试验证修复后的实现确保 tryLock 和 unlock 使用一致的键序列化方式 + */ +@Test(enabled = false) // 默认禁用,需要Redis实例才能运行 +public class RedisTemplateSimpleDistributedLockSerializationTest { + + private RedisTemplateSimpleDistributedLock redisLock; + private StringRedisTemplate redisTemplate; + + @BeforeTest + public void init() { + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); + connectionFactory.setHostName("127.0.0.1"); + connectionFactory.setPort(6379); + connectionFactory.afterPropertiesSet(); + + // 创建一个带自定义键序列化的 StringRedisTemplate + StringRedisTemplate redisTemplate = new StringRedisTemplate(connectionFactory); + + // 使用自定义键序列化器,模拟在键前面添加前缀的场景 + redisTemplate.setKeySerializer(new StringRedisSerializer() { + @Override + public byte[] serialize(String string) { + if (string == null) return null; + // 添加 "System:" 前缀,模拟用户自定义的键序列化 + return super.serialize("System:" + string); + } + + @Override + public String deserialize(byte[] bytes) { + if (bytes == null) return null; + String result = super.deserialize(bytes); + // 移除前缀进行反序列化 + return result != null && result.startsWith("System:") ? result.substring(7) : result; + } + }); + + this.redisTemplate = redisTemplate; + this.redisLock = new RedisTemplateSimpleDistributedLock(redisTemplate, "test_lock_key", 60000); + } + + @Test(description = "测试自定义键序列化器下的锁操作一致性") + public void testLockConsistencyWithCustomKeySerializer() { + // 1. 获取锁应该成功 + assertTrue(redisLock.tryLock(), "第一次获取锁应该成功"); + assertNotNull(redisLock.getLockSecretValue(), "锁值应该存在"); + + // 2. 验证键已正确存储(通过 redisTemplate 直接查询) + String actualValue = redisTemplate.opsForValue().get("test_lock_key"); + assertEquals(actualValue, redisLock.getLockSecretValue(), "通过 redisTemplate 查询的值应该与锁值相同"); + + // 3. 再次尝试获取同一把锁应该成功(可重入) + assertTrue(redisLock.tryLock(), "可重入锁应该再次获取成功"); + + // 4. 释放锁应该成功 + redisLock.unlock(); + assertNull(redisLock.getLockSecretValue(), "释放锁后锁值应该为空"); + + // 5. 验证键已被删除 + actualValue = redisTemplate.opsForValue().get("test_lock_key"); + assertNull(actualValue, "释放锁后 Redis 中的键应该被删除"); + + // 6. 释放已释放的锁应该是安全的 + redisLock.unlock(); // 不应该抛出异常 + } + + @Test(description = "测试不同线程使用相同键的锁排他性") + public void testLockExclusivityWithCustomKeySerializer() throws InterruptedException { + // 第一个锁实例获取锁 + assertTrue(redisLock.tryLock(), "第一个锁实例应该成功获取锁"); + + // 创建第二个锁实例使用相同的键 + RedisTemplateSimpleDistributedLock anotherLock = new RedisTemplateSimpleDistributedLock( + redisTemplate, "test_lock_key", 60000); + + // 第二个锁实例不应该能获取锁 + assertFalse(anotherLock.tryLock(), "第二个锁实例不应该能获取已被占用的锁"); + + // 释放第一个锁 + redisLock.unlock(); + + // 现在第二个锁实例应该能获取锁 + assertTrue(anotherLock.tryLock(), "第一个锁释放后,第二个锁实例应该能获取锁"); + + // 清理 + anotherLock.unlock(); + } +} \ No newline at end of file diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockTest.java index 4b65e31f0b..b278eeafa0 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockTest.java @@ -1,8 +1,10 @@ package me.chanjar.weixin.common.util.locks; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -13,9 +15,10 @@ import static org.testng.Assert.*; @Slf4j -@Test(enabled = false) +@Test(enabled = true) public class RedisTemplateSimpleDistributedLockTest { + private static final String KEY_PREFIX = "System:"; RedisTemplateSimpleDistributedLock redisLock; StringRedisTemplate redisTemplate; @@ -29,6 +32,28 @@ public void init() { connectionFactory.setPort(6379); connectionFactory.afterPropertiesSet(); StringRedisTemplate redisTemplate = new StringRedisTemplate(connectionFactory); + // 自定义序列化器,为 key 自动加前缀 + redisTemplate.setKeySerializer(new StringRedisSerializer() { + @NotNull + @Override + public byte[] serialize(String string) { + if (string == null) { + return super.serialize(null); + } + // 添加前缀 + return super.serialize(KEY_PREFIX + string); + } + + @NotNull + @Override + public String deserialize(byte[] bytes) { + String key = super.deserialize(bytes); + if (key.startsWith(KEY_PREFIX)) { + return key.substring(KEY_PREFIX.length()); + } + return key; + } + }); this.redisTemplate = redisTemplate; this.redisLock = new RedisTemplateSimpleDistributedLock(redisTemplate, 60000); this.lockCurrentExecuteCounter = new AtomicInteger(0); diff --git a/weixin-java-cp/APPROVAL_WORKFLOW_GUIDE.md b/weixin-java-cp/APPROVAL_WORKFLOW_GUIDE.md new file mode 100644 index 0000000000..d2c533b5e5 --- /dev/null +++ b/weixin-java-cp/APPROVAL_WORKFLOW_GUIDE.md @@ -0,0 +1,141 @@ +# WeChat Enterprise Workflow Approval Guide +# 企业微信流程审批功能使用指南 + +## Overview / 概述 + +WxJava SDK provides comprehensive support for WeChat Enterprise workflow approval (企业微信流程审批), including both traditional OA approval and the approval process engine. + +WxJava SDK 提供全面的企业微信流程审批支持,包括传统OA审批和审批流程引擎。 + +## Current Implementation Status / 当前实现状态 + +### ✅ Fully Implemented APIs / 已完整实现的API + +1. **Submit Approval Application / 提交审批申请** + - Endpoint: `/cgi-bin/oa/applyevent` + - Documentation: [91853](https://work.weixin.qq.com/api/doc/90000/90135/91853) + - Implementation: `WxCpOaService.apply(WxCpOaApplyEventRequest)` + +2. **Get Approval Details / 获取审批申请详情** + - Endpoint: `/cgi-bin/oa/getapprovaldetail` + - Implementation: `WxCpOaService.getApprovalDetail(String spNo)` + +3. **Batch Get Approval Numbers / 批量获取审批单号** + - Endpoint: `/cgi-bin/oa/getapprovalinfo` + - Implementation: `WxCpOaService.getApprovalInfo(...)` + +4. **Approval Process Engine / 审批流程引擎** + - Endpoint: `/cgi-bin/corp/getopenapprovaldata` + - Implementation: `WxCpOaAgentService.getOpenApprovalData(String thirdNo)` + +5. **Template Management / 模板管理** + - Create: `WxCpOaService.createOaApprovalTemplate(...)` + - Update: `WxCpOaService.updateOaApprovalTemplate(...)` + - Get Details: `WxCpOaService.getTemplateDetail(...)` + +## Usage Examples / 使用示例 + +### 1. Submit Approval Application / 提交审批申请 + +```java +// Create approval request +WxCpOaApplyEventRequest request = new WxCpOaApplyEventRequest() + .setCreatorUserId("userId") + .setTemplateId("templateId") + .setUseTemplateApprover(0) + .setApprovers(Arrays.asList( + new WxCpOaApplyEventRequest.Approver() + .setAttr(2) + .setUserIds(new String[]{"approver1", "approver2"}) + )) + .setNotifiers(new String[]{"notifier1", "notifier2"}) + .setNotifyType(1) + .setApplyData(new WxCpOaApplyEventRequest.ApplyData() + .setContents(Arrays.asList( + new ApplyDataContent() + .setControl("Text") + .setId("Text-1234567890") + .setValue(new ContentValue().setText("Approval content")) + )) + ); + +// Submit approval +String spNo = wxCpService.getOaService().apply(request); +``` + +### 2. Get Approval Details / 获取审批详情 + +```java +// Get approval details by approval number +WxCpApprovalDetailResult result = wxCpService.getOaService() + .getApprovalDetail("approval_number"); + +WxCpApprovalDetailResult.WxCpApprovalDetail detail = result.getInfo(); +System.out.println("Approval Status: " + detail.getSpStatus()); +System.out.println("Approval Name: " + detail.getSpName()); +``` + +### 3. Batch Get Approval Information / 批量获取审批信息 + +```java +// Get approval info with filters +Date startTime = new Date(System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000); +Date endTime = new Date(); + +WxCpApprovalInfo approvalInfo = wxCpService.getOaService() + .getApprovalInfo(startTime, endTime, "0", 100, null); + +List spNumbers = approvalInfo.getSpNoList(); +``` + +### 4. Third-Party Application Support / 第三方应用支持 + +```java +// For third-party applications +WxCpTpOAService tpOaService = wxCpTpService.getOaService(); + +// Submit approval for specific corp +String spNo = tpOaService.apply(request, "corpId"); + +// Get approval details for specific corp +WxCpApprovalDetailResult detail = tpOaService.getApprovalDetail("spNo", "corpId"); +``` + +## Multi-Account Configuration / 多账号配置支持 + +WxJava supports multi-account configurations for enterprise scenarios: + +```java +// Spring Boot configuration example +@Autowired +private WxCpMultiServices wxCpMultiServices; + +// Get service for specific corp +WxCpService wxCpService = wxCpMultiServices.getWxCpService("corpId"); +WxCpOaService oaService = wxCpService.getOaService(); +``` + +## Available Data Models / 可用数据模型 + +- `WxCpOaApplyEventRequest` - Approval application request +- `WxCpApprovalDetailResult` - Approval details response +- `WxCpApprovalInfo` - Batch approval information +- `WxCpXmlApprovalInfo` - XML approval message handling +- `WxCpOaApprovalTemplate` - Approval template management + +## Documentation References / 文档参考 + +- [Submit Approval Application (91853)](https://work.weixin.qq.com/api/doc/90000/90135/91853) +- [Get Approval Details (91983)](https://work.weixin.qq.com/api/doc/90000/90135/91983) +- [Batch Get Approval Numbers (91816)](https://work.weixin.qq.com/api/doc/90000/90135/91816) +- [Approval Process Engine (90269)](https://developer.work.weixin.qq.com/document/path/90269) + +## Conclusion / 结论 + +WxJava already provides comprehensive support for WeChat Enterprise workflow approval. The "new version" (新版) approval functionality referenced in issue requests is **already fully implemented** and available for use. + +WxJava 已经提供了企业微信流程审批的全面支持。问题中提到的"新版"流程审批功能**已经完全实现**并可使用。 + +For questions about specific usage, please refer to the test cases in `WxCpOaServiceImplTest` and the comprehensive API documentation. + +有关具体使用问题,请参考 `WxCpOaServiceImplTest` 中的测试用例和全面的API文档。 \ No newline at end of file diff --git a/weixin-java-cp/INTELLIGENT_ROBOT.md b/weixin-java-cp/INTELLIGENT_ROBOT.md new file mode 100644 index 0000000000..0fbe205f1a --- /dev/null +++ b/weixin-java-cp/INTELLIGENT_ROBOT.md @@ -0,0 +1,157 @@ +# 企业微信智能机器人接口 + +本模块提供企业微信智能机器人相关的 API 接口实现。 + +> `createRobot`、`chat`、`sendMessage` 等既有方法走企业应用 `access_token` 接口, +> 需要在 `WxCpConfigStorage` 中配置应用 `agentId` 和 `secret`。它们不适用于机器人后台创建的新版 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); +``` + +### 旧版 access_token 主动发送消息 + +智能机器人可以主动向用户发送消息,用于推送通知或提醒。 + +```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(); +``` + +### 新版 API 模式:接收回调与回复消息 + +在机器人后台开启 API 模式后,配置 URL、Token、EncodingAESKey。企业微信会推送加密 JSON 回调; +它不是 XML,也不需要企业应用 `secret`。从请求参数取得 `msg_signature`、`timestamp`、`nonce`, +从请求体取得 `encrypt` 字段后,可以直接解密和解析: + +```java +WxCpIntelligentRobotMessage callbackMessage = + robotService.parseEncryptedCallbackMessage( + msgSignature, timestamp, nonce, encryptedJson, + token, encodingAesKey, aiBotId); + +String responseUrl = callbackMessage.getResponseUrl(); +String content = callbackMessage.getText().getContent(); +``` + +回复时使用回调中的短期 `response_url`,不调用基于 `access_token` 的 `sendMessage`: + +```java +String replyJson = "{\"msgtype\":\"text\",\"text\":{\"content\":\"您好\"}}"; +robotService.replyMessage( + responseUrl, replyJson, token, encodingAesKey, aiBotId, + String.valueOf(System.currentTimeMillis() / 1000), java.util.UUID.randomUUID().toString()); +``` + +### 删除智能机器人 + +```java +String robotId = "robot_id_here"; +robotService.deleteRobot(robotId); +``` + +## 主要类说明 + +### 请求类 + +- `WxCpIntelligentRobotCreateRequest`: 创建机器人请求 +- `WxCpIntelligentRobotUpdateRequest`: 更新机器人请求 +- `WxCpIntelligentRobotChatRequest`: 智能对话请求 +- `WxCpIntelligentRobotSendMessageRequest`: 主动发送消息请求 + +### 响应类 + +- `WxCpIntelligentRobotCreateResponse`: 创建机器人响应 +- `WxCpIntelligentRobotChatResponse`: 智能对话响应 +- `WxCpIntelligentRobotSendMessageResponse`: 主动发送消息响应 +- `WxCpIntelligentRobot`: 机器人信息实体 + +### 消息接收 + +- `WxCpIntelligentRobotMessage`: 智能机器人 API 模式的已解密 JSON 回调消息 +- `WxCpIntelligentRobotCryptUtil`: 智能机器人 API 模式的消息加解密工具 + +### 服务接口 + +- `WxCpIntelligentRobotService`: 智能机器人服务接口 +- `WxCpIntelligentRobotServiceImpl`: 智能机器人服务实现 + +## 注意事项 + +1. 新版 API 模式的 Token、EncodingAESKey 和机器人 ID 由机器人后台配置,不要填写企业应用 secret。 +2. `response_url` 是回调附带的临时地址,应及时使用,且不应持久化。 +3. `parseCallbackMessage` 仅用于已解密的 JSON;HTTP 回调入口应使用 `parseEncryptedCallbackMessage`。 diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml index 8a418ae646..4fe6759f52 100644 --- a/weixin-java-cp/pom.xml +++ b/weixin-java-cp/pom.xml @@ -7,7 +7,7 @@ com.github.binarywang wx-java - 4.5.3.B + 4.8.6.B weixin-java-cp @@ -30,6 +30,15 @@ okhttp provided + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents.client5 + httpclient5 + provided + redis.clients jedis @@ -46,7 +55,11 @@ org.springframework.data spring-data-redis + ${spring-data-redis.version} + true + provided + org.testng testng @@ -54,9 +67,10 @@ org.mockito - mockito-all + mockito-core test + com.google.inject guice @@ -81,6 +95,10 @@ org.projectlombok lombok + + org.bouncycastle + bcprov-jdk18on + org.assertj @@ -96,7 +114,6 @@ com.fasterxml.jackson.core jackson-core - 2.13.4 test @@ -110,6 +127,15 @@ src/test/resources/testng.xml + + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.lang.reflect=ALL-UNNAMED + --add-opens java.base/java.io=ALL-UNNAMED + --add-opens java.base/java.security=ALL-UNNAMED + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.management/javax.management=ALL-UNNAMED + --add-opens java.naming/javax.naming=ALL-UNNAMED + 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 0c5ccb3b0c..7302a88f0c 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,6 +2,7 @@ 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; @@ -18,7 +19,7 @@ public interface WxCpAgentService { /** *
    * 获取企业号应用信息
-   * 该API用于获取企业号某个应用的基本信息,包括头像、昵称、帐号类型、认证类型、可见范围等信息
+   * 该API用于获取企业号某个应用的基本信息,包括头像、昵称、账号类型、认证类型、可见范围等信息
    * 详情请见: ...
    * 
* @@ -26,7 +27,7 @@ public interface WxCpAgentService { * @return wx cp agent * @throws WxErrorException the wx error exception */ - WxCpAgent get(Integer agentId) throws WxErrorException; + WxCpAgent get(Long agentId) throws WxErrorException; /** *
@@ -52,4 +53,18 @@ public interface WxCpAgentService {
    */
   List list() throws WxErrorException;
 
+  /**
+   * 
+   * 获取应用管理员列表
+   * 第三方服务商可以用此接口获取授权企业中某个第三方应用或者代开发应用的管理员列表(不包括外部管理员),
+   * 以便服务商在用户进入应用主页之后根据是否管理员身份做权限的区分。
+   * 详情请见: 文档
+   * 
+ * + * @param agentId 应用id + * @return admin list + * @throws WxErrorException the wx error exception + */ + WxCpTpAdmin getAdminList(Long 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 index c50aa2f5fc..67c57a8a88 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentWorkBenchService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentWorkBenchService.java @@ -36,4 +36,12 @@ public interface WxCpAgentWorkBenchService { * @throws WxErrorException the wx error exception */ void setWorkBenchData(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErrorException; + + /** + * Batch sets work bench data. + * + * @param wxCpAgentWorkBench the wx cp agent work bench + * @throws WxErrorException the wx error exception + */ + void batchSetWorkBenchData(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java index 4da13d3fde..16882669b7 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpCorpGroupService.java @@ -9,7 +9,7 @@ * 企业互联相关接口 * * @author libo <422423229@qq.com> - * Created on 27/2/2023 9:57 PM + * @since 2023-02-27 9:57 PM */ public interface WxCpCorpGroupService { /** @@ -23,5 +23,5 @@ public interface WxCpCorpGroupService { * @return the list * @throws WxErrorException the wx error exception */ - List listAppShareInfo(Integer agentId, Integer businessType, String corpId, Integer limit, String cursor) throws WxErrorException; + List listAppShareInfo(Long 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/WxCpExportService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java index 24c6ea9dc1..a2c7adabea 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExportService.java @@ -85,7 +85,7 @@ public interface WxCpExportService { * 获取导出结果 * * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/export/get_result?access_token=ACCESS_TOKEN&jobid=jobid_xxxxxxxxxxxxxxx + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/export/get_result?access_token=ACCESS_TOKEN&jobid=jobid_xxxxxxxxxxxxxxx} * * 文档地址:https://developer.work.weixin.qq.com/document/path/94854 * 返回的url文件下载解密可参考 CSDN diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java index 15c9fbeb1c..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,5 +1,10 @@ package me.chanjar.weixin.cp.api; +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; @@ -8,12 +13,8 @@ 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 java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.Date; -import java.util.List; +import me.chanjar.weixin.cp.bean.external.interceptrule.WxCpInterceptRuleInfo; +import me.chanjar.weixin.cp.bean.external.interceptrule.WxCpInterceptRuleList; /** *
@@ -37,6 +38,7 @@ public interface WxCpExternalContactService {
    * 用户需要妥善存储返回的config_id,config_id丢失可能导致用户无法编辑或删除「联系我」。
    * 临时会话模式不占用「联系我」数量,但每日最多添加10万个,并且仅支持单人。
    * 临时会话模式的二维码,添加好友完成后该二维码即刻失效。
+   * 文档地址
    * 
* * @param info 客户联系「联系我」方式 @@ -58,6 +60,25 @@ public interface WxCpExternalContactService { */ WxCpContactWayInfo getContactWay(String configId) throws WxErrorException; + /** + * 获取企业已配置的「联系我」列表 + * + *
+   * 获取企业配置的「联系我」二维码和「联系我」小程序插件列表。不包含临时会话。
+   * 注意,该接口仅可获取2021年7月10日以后创建的「联系我」
+   * 
+ * + * 文档地址: 获取企业已配置的「联系我」列表 + * + * @param startTime 「联系我」创建起始时间戳, 默认为90天前 + * @param endTime 「联系我」创建结束时间戳, 默认为当前时间 + * @param cursor 分页查询使用的游标,为上次请求返回的 next_cursor + * @param limit 每次查询的分页大小,默认为100条,最多支持1000条 + * @return contact way configId + * @throws WxErrorException the wx error exception + */ + WxCpContactWayList listContactWay(Long startTime, Long endTime, String cursor, Long limit) throws WxErrorException; + /** * 更新企业已配置的「联系我」方式 * @@ -110,13 +131,13 @@ 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 userId) throws WxErrorException; + WxCpExternalContactInfo getExternalContact(String externalUserId) throws WxErrorException; /** * 获取客户详情. @@ -125,7 +146,7 @@ public interface WxCpExternalContactService { * 企业可通过此接口,根据外部联系人的userid(如何获取?),拉取客户详情。 * * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid=EXTERNAL_USERID + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid=EXTERNAL_USERID} * * 权限说明: * @@ -133,12 +154,12 @@ public interface WxCpExternalContactService { * 第三方/自建应用调用时,返回的跟进人follow_user仅包含应用可见范围之内的成员。 * * - * @param userId 外部联系人的userid,注意不是企业成员的帐号 + * @param externalUserId 外部联系人的userid,注意不是企业成员的帐号 * @param cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 * @return . contact detail * @throws WxErrorException . */ - WxCpExternalContactInfo getContactDetail(String userId, String cursor) throws WxErrorException; + WxCpExternalContactInfo getContactDetail(String externalUserId, String cursor) throws WxErrorException; /** * 企业和服务商可通过此接口,将微信外部联系人的userid转为微信openid,用于调用支付相关接口。暂不支持企业微信外部联系人(ExternalUserid为wo开头)的userid转openid。 @@ -231,9 +252,9 @@ public interface WxCpExternalContactService { * * 权限说明: * - * 该企业授权了该服务商第三方应用,且授权的第三方应用具备“企业客户权限->客户基础信息”权限 + * {@code 该企业授权了该服务商第三方应用,且授权的第三方应用具备“企业客户权限->客户基础信息”权限} * 该客户的跟进人必须在应用的可见范围之内 - * 应用需具备“企业客户权限->客户基础信息”权限 + * {@code 应用需具备“企业客户权限->客户基础信息”权限} * * * @param externalUserid 代开发自建应用获取到的外部联系人ID @@ -255,8 +276,8 @@ public interface WxCpExternalContactService { * * @param externalUserid 服务商主体的external_userid,必须是source_agentid对应的应用所获取 * @param sourceAgentId 企业授权的代开发自建应用或第三方应用的agentid - * @return - * @throws WxErrorException + * @return 企业的external_userid + * @throws WxErrorException 微信错误异常 */ String fromServiceExternalUserid(String externalUserid, String sourceAgentId) throws WxErrorException; @@ -341,7 +362,7 @@ public interface WxCpExternalContactService { * 权限说明: * * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?) - * 第三方应用需具有“企业客户权限->客户基础信息”权限 + * {@code 第三方应用需具有“企业客户权限->客户基础信息”权限} * 对于第三方/自建应用,群主必须在应用的可见范围 * 仅支持企业服务人员创建的客户群 * 仅可转换出自己企业下的客户群chat_id @@ -379,6 +400,25 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c 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; + /** * 修改客户备注信息. *
@@ -399,7 +439,7 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c
    *   企业可通过此接口获取指定成员添加的客户列表。客户是指配置了客户联系功能的成员所添加的外部联系人。没有配置客户联系功能的成员,所添加的外部联系人将不会作为客户返回。
    *
    * 请求方式:GET(HTTPS)
-   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list?access_token=ACCESS_TOKEN&userid=USERID
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list?access_token=ACCESS_TOKEN&userid=USERID}
    *
    * 权限说明:
    *
@@ -430,7 +470,8 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c
   /**
    * 获取待分配的离职成员列表
    * 企业和第三方可通过此接口,获取所有离职成员的客户列表,并可进一步调用分配离职成员的客户接口将这些客户重新分配给其他企业成员。
-   * 

+ + * * 请求方式:POST(HTTPS) * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_unassigned_list?access_token=ACCESS_TOKEN * @@ -457,17 +498,17 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c /** * 企业可通过此接口,转接在职成员的客户给其他成员。 - * + *

    * external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
    * 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
-   * 

+ * * 权限说明: - * * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。 - * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限 + * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。 + * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限} * 接替成员必须在此第三方应用或自建应用的可见范围内。 * 接替成员需要配置了客户联系功能。 * 接替成员需要在企业微信激活且已经过实名认证。 - * + *

* * @param req 转接在职成员的客户给其他成员请求实体 * @return wx cp base resp @@ -477,13 +518,13 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, String c /** * 企业和第三方可通过此接口查询在职成员的客户转接情况。 - * + *
    * 权限说明:
-   * 

+ * * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。 - * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限 + * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限} * 接替成员必须在此第三方应用或自建应用的可见范围内。 - * + *

* * @param handOverUserid 原添加成员的userid * @param takeOverUserid 接替成员的userid @@ -496,19 +537,21 @@ WxCpUserTransferResultResp transferResult(String handOverUserid, String takeOver /** * 企业可通过此接口,分配离职成员的客户给其他成员。 - * + *
    * handover_userid必须是已离职用户。
    * external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
    * 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
-   * 

+ + * * 权限说明: - *

+ + * * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。 - * 第三方应用需拥有“企业客户权限->客户联系->离职分配”权限 + * {@code 第三方应用需拥有“企业客户权限->客户联系->离职分配”权限} * 接替成员必须在此第三方应用或自建应用的可见范围内。 * 接替成员需要配置了客户联系功能。 * 接替成员需要在企业微信激活且已经过实名认证。 - * + *

* * @param req 转接在职成员的客户给其他成员请求实体 * @return wx cp base resp @@ -518,13 +561,14 @@ WxCpUserTransferResultResp transferResult(String handOverUserid, String takeOver /** * 企业和第三方可通过此接口查询离职成员的客户分配情况。 - * + *
    * 权限说明:
-   * 

+ + * * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。 - * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限 + * {@code 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限} * 接替成员必须在此第三方应用或自建应用的可见范围内。 - * + *

* * @param handOverUserid 原添加成员的userid * @param takeOverUserid 接替成员的userid @@ -591,21 +635,24 @@ WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex, Integer pageSize, /** * 企业可通过此接口,将已离职成员为群主的群,分配给另一个客服成员。 * - * + *
    * 注意::
-   * 

+ + * * 群主离职了的客户群,才可继承 * 继承给的新群主,必须是配置了客户联系功能的成员 * 继承给的新群主,必须有设置实名 * 继承给的新群主,必须有激活企业微信 * 同一个人的群,限制每天最多分配300个给新群主 - *

+ + * * 权限说明: - *

+ + * * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。 - * 第三方应用需拥有“企业客户权限->客户联系->分配离职成员的客户群”权限 + * {@code 第三方应用需拥有“企业客户权限->客户联系->分配离职成员的客户群”权限} * 对于第三方/自建应用,群主必须在应用的可见范围。 - * + *

* * @param chatIds 需要转群主的客户群ID列表。取值范围: 1 ~ 100 * @param newOwner 新群主ID @@ -617,7 +664,7 @@ WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex, Integer pageSize, /** * 企业可通过此接口,将在职成员为群主的群,分配给另一个客服成员。 - * + *
    * 注意:
    * 继承给的新群主,必须是配置了客户联系功能的成员
    * 继承给的新群主,必须有设置实名
@@ -677,11 +724,14 @@ WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer
    * 企业可通过此接口添加企业群发消息的任务并通知客服人员发送给相关客户或客户群。(注:企业微信终端需升级到2.7.5版本及以上)
    * 注意:调用该接口并不会直接发送消息给客户/客户群,需要相关的客服人员操作以后才会实际发送(客服人员的企业微信需要升级到2.7.5及以上版本)
    * 同一个企业每个自然月内仅可针对一个客户/客户群发送4条消息,超过限制的用户将会被忽略。
-   * 

+ + * * 请求方式: POST(HTTP) - *

+ + * * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_msg_template?access_token=ACCESS_TOKEN - *

+ + * * 文档地址 * * @param wxCpMsgTemplate the wx cp msg template @@ -694,15 +744,18 @@ WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer /** * 提醒成员群发 * 企业和第三方应用可调用此接口,重新触发群发通知,提醒成员完成群发任务,24小时内每个群发最多触发三次提醒。 - *

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

+ + * * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/remind_groupmsg_send?access_token=ACCESS_TOKEN - *

+ * * 文档地址 * * @param msgId 群发消息的id,通过获取群发记录列表接口返回 * @return the wx cp msg template add result + * @throws WxErrorException 微信错误异常 */ WxCpBaseResp remindGroupMsgSend(String msgId) throws WxErrorException; @@ -714,11 +767,12 @@ WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer * 请求方式: POST(HTTPS) *

* 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/cancel_groupmsg_send?access_token=ACCESS_TOKEN - *

+ * * 文档地址 * * @param msgId 群发消息的id,通过获取群发记录列表接口返回 * @return the wx cp msg template add result + * @throws WxErrorException 微信错误异常 */ WxCpBaseResp cancelGroupMsgSend(String msgId) throws WxErrorException; @@ -845,6 +899,20 @@ WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer */ 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; + + /** *
    * 获取客户朋友圈全部的发表记录 获取企业全部的发表列表
@@ -949,7 +1017,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e
   /**
    * 
    * 企业和第三方应用可通过此接口获取企业与成员的群发记录。
-   * 获取企业群发成员执行结果
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/93338
    * 
* * @param msgid 群发消息的id,通过获取群发记录列表接口返回 @@ -978,7 +1046,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e /** *
    * 获取群发成员发送任务列表。
-   * 获取群发成员发送任务列表
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/93338
    * 
* * @param msgid 群发消息的id,通过获取群发记录列表接口返回 @@ -992,7 +1060,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e /** *
    * 添加入群欢迎语素材。
-   * 添加入群欢迎语素材
+   * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
    * 
* * @param template 素材内容 @@ -1004,7 +1072,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e /** *
    * 编辑入群欢迎语素材。
-   * 编辑入群欢迎语素材
+   * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
    * 
* * @param template the template @@ -1016,7 +1084,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e /** *
    * 获取入群欢迎语素材。
-   * 获取入群欢迎语素材
+   * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
    * 
* * @param templateId 群欢迎语的素材id @@ -1029,7 +1097,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e *
    * 删除入群欢迎语素材。
    * 企业可通过此API删除入群欢迎语素材,且仅能删除调用方自己创建的入群欢迎语素材。
-   * 删除入群欢迎语素材
+   * 文档地址:https://open.work.weixin.qq.com/api/doc/90000/90135/92366
    * 
* * @param templateId 群欢迎语的素材id @@ -1041,8 +1109,8 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e /** *
-   * 获取商品图册
-   * 获取商品图册列表
+   * 获取商品图册列表
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/95096
    * 
* * @param limit 返回的最大记录数,整型,最大值100,默认值50,超过最大值时取默认值 @@ -1055,7 +1123,7 @@ WxCpGroupMsgListResult getGroupMsgListV2(String chatType, Date startTime, Date e /** *
    * 获取商品图册
-   * 获取商品图册
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/95096
    * 
* * @param productId 商品id @@ -1102,7 +1170,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F * 企业和第三方应用可以通过此接口新建敏感词规则 * 请求方式:POST(HTTPS) * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_intercept_rule?access_token=ACCESS_TOKEN - *
+   * 
* @param ruleAddRequest the rule add request * @return 规则id * @throws WxErrorException the wx error exception @@ -1116,7 +1184,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F * 企业和第三方应用可以通过此接口修改敏感词规则 * 请求方式:POST(HTTPS) * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_intercept_rule?access_token=ACCESS_TOKEN - *
+   * 
* @param interceptRule the rule * @throws WxErrorException the wx error exception */ @@ -1128,12 +1196,37 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F * 企业和第三方应用可以通过此接口修改敏感词规则 * 请求方式: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; + /** *
    * 创建商品图册
@@ -1142,7 +1235,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F
    * 请求地址:
    * https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_product_album?access_token=ACCESS_TOKEN
    * 文档地址
-   * 
+   * 
* @param wxCpProductAlbumInfo 商品图册信息 * @return 商品id string * @throws WxErrorException the wx error exception @@ -1157,7 +1250,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F * 请求地址: * https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_product_album?access_token=ACCESS_TOKEN * 文档地址 - *
+   * 
* @param wxCpProductAlbumInfo 商品图册信息 * @throws WxErrorException the wx error exception */ @@ -1172,7 +1265,7 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F * https://qyapi.weixin.qq.com/cgi-bin/externalcontact/delete_product_album?access_token=ACCESS_TOKEN * * 文档地址 - *
+   * 
* @param productId 商品id * @throws WxErrorException the wx error exception */ @@ -1291,4 +1384,22 @@ WxMediaUploadResult uploadAttachment(String mediaType, Integer attachmentType, F * @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 index bc6e130548..b8ccea5e50 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java @@ -70,6 +70,23 @@ public interface WxCpGroupRobotService { */ void sendMarkdown(String webhookUrl, String content) throws WxErrorException; + /** + * 发送markdown_v2类型的消息 + * + * @param content markdown内容,最长不超过4096个字节,必须是utf8编码 + * @throws WxErrorException 异常 + */ + void sendMarkdownV2(String content) throws WxErrorException; + + /** + * 发送markdown_v2类型的消息 + * + * @param webhookUrl webhook地址 + * @param content markdown内容,最长不超过4096个字节,必须是utf8编码 + * @throws WxErrorException 异常 + */ + void sendMarkdownV2(String webhookUrl, String content) throws WxErrorException; + /** * 发送image类型的消息 * @@ -98,11 +115,21 @@ public interface WxCpGroupRobotService { */ 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 - * @param wxCpGroupRobotMessage - * @throws WxErrorException + * + * @param webhookUrl webhook地址 + * @param wxCpGroupRobotMessage 群机器人消息 + * @throws WxErrorException 异常 */ void sendTemplateCardMessage(String webhookUrl, WxCpGroupRobotMessage wxCpGroupRobotMessage) throws WxErrorException; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpHrService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpHrService.java new file mode 100644 index 0000000000..d9d6ed0129 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpHrService.java @@ -0,0 +1,68 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldData; +import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldDataResp; +import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldInfoResp; + +import java.util.List; + +/** + * 人事助手相关接口. + * 官方文档:... + * + * @author copilot + */ +public interface WxCpHrService { + + /** + * 获取员工档案字段信息. + *

+ * 请求方式:POST(HTTPS) + * 请求地址:... + * 权限说明: + * 需要配置人事助手的secret,调用接口前需给对应成员赋予人事小助手应用的权限。 + * + * @param fields 指定字段key列表,不填则返回全部字段 + * @return 字段信息响应 wx cp hr employee field info resp + * @throws WxErrorException the wx error exception + */ + WxCpHrEmployeeFieldInfoResp getFieldInfo(List fields) throws WxErrorException; + + /** + * 获取员工档案数据. + *

+ * 请求方式:POST(HTTPS) + * 请求地址:... + * 权限说明: + * 需要配置人事助手的secret,调用接口前需给对应成员赋予人事小助手应用的权限。 + * + * @param userid 员工userid + * @param fields 指定字段key列表 + * @return 员工档案数据响应 wx cp hr employee field data resp + * @throws WxErrorException the wx error exception + */ + WxCpHrEmployeeFieldDataResp getEmployeeFieldInfo(String userid, List fields) throws WxErrorException; + + /** + * 获取员工档案数据. + *

+ * 请求方式:POST(HTTPS) + * 请求地址:... + */ + WxCpHrEmployeeFieldDataResp getEmployeeFieldInfo(String userid, boolean getAll, List fields) throws WxErrorException; + + /** + * 更新员工档案数据. + *

+ * 请求方式:POST(HTTPS) + * 请求地址:... + * 权限说明: + * 需要配置人事助手的secret,调用接口前需给对应成员赋予人事小助手应用的权限。 + * + * @param userid 员工userid + * @param fieldList 字段数据列表 + * @throws WxErrorException the wx error exception + */ + void updateEmployeeFieldInfo(String userid, List fieldList) 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..1d71bac99a --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpIntelligentRobotService.java @@ -0,0 +1,121 @@ +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; + + /** + * 解析智能机器人 API 模式回调消息. + * + * @param callbackMessageJson 回调消息JSON + * @return 解析后的回调消息对象 + */ + WxCpIntelligentRobotMessage parseCallbackMessage(String callbackMessageJson); + + /** + * 解密并解析智能机器人 API 模式回调消息. + * + * @param msgSignature 回调 URL 参数中的签名 + * @param timestamp 回调 URL 参数中的时间戳 + * @param nonce 回调 URL 参数中的随机串 + * @param encryptedJson 回调 JSON 信封中的 encrypt 字段 + * @param token 机器人后台配置的 Token + * @param encodingAesKey 机器人后台配置的 EncodingAESKey + * @param aiBotId 机器人 ID + * @return 解密并解析后的回调消息 + */ + default WxCpIntelligentRobotMessage parseEncryptedCallbackMessage(String msgSignature, String timestamp, String nonce, + String encryptedJson, String token, String encodingAesKey, + String aiBotId) { + throw new UnsupportedOperationException("当前智能机器人服务不支持 API 模式回调解析"); + } + + /** + * 加密并向智能机器人 API 模式的临时 response_url 回复消息. + * + * @param responseUrl 回调消息中的 response_url + * @param plainJson 回复的明文 JSON + * @param token 机器人后台配置的 Token + * @param encodingAesKey 机器人后台配置的 EncodingAESKey + * @param aiBotId 机器人 ID + * @param timestamp 回复时间戳 + * @param nonce 回复随机串 + * @return 企业微信响应内容 + * @throws WxErrorException 微信接口异常 + */ + default String replyMessage(String responseUrl, String plainJson, String token, String encodingAesKey, String aiBotId, + String timestamp, String nonce) throws WxErrorException { + throw new UnsupportedOperationException("当前智能机器人服务不支持 API 模式消息回复"); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java index 86b342f2fc..046cfbc5bb 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpKfService.java @@ -75,6 +75,19 @@ public interface WxCpKfService { */ WxCpKfServicerOpResp addServicer(String openKfid, List userIdList) throws WxErrorException; + /** + * 接待人员管理 + * 添加指定客服账号的接待人员,每个客服账号目前最多可添加2000个接待人员,20个部门。 + * userid_list和department_id_list至少需要填其中一个 + * + * @param openKfid 客服帐号ID + * @param userIdList 接待人员userid列表。第三方应用填密文userid,即open_userid 可填充个数:1 ~ 100。超过100个需分批调用。 + * @param departmentIdList 接待人员部门id列表 可填充个数:0 ~ 20。 + * @return 添加客服账号结果 wx cp kf servicer op resp + * @throws WxErrorException 异常 + */ + WxCpKfServicerOpResp addServicer(String openKfid, List userIdList,List departmentIdList) throws WxErrorException; + /** * 接待人员管理 * 从客服帐号删除接待人员 @@ -86,6 +99,19 @@ public interface WxCpKfService { */ WxCpKfServicerOpResp delServicer(String openKfid, List userIdList) throws WxErrorException; + /** + * 接待人员管理 + * 从客服帐号删除接待人员 + * userid_list和department_id_list至少需要填其中一个 + * + * @param openKfid 客服帐号ID + * @param userIdList 接待人员userid列表。第三方应用填密文userid,即open_userid 可填充个数:1 ~ 100。超过100个需分批调用。 + * @param departmentIdList 接待人员部门id列表 可填充个数:0 ~ 100。超过100个需分批调用。 + * @return 删除客服账号结果 wx cp kf servicer op resp + * @throws WxErrorException 异常 + */ + WxCpKfServicerOpResp delServicer(String openKfid, List userIdList, List departmentIdList) throws WxErrorException; + /** * 接待人员管理 * 获取某个客服帐号的接待人员列表 @@ -196,7 +222,7 @@ WxCpKfCustomerBatchGetResp customerBatchGet(List externalUserIdList) * https://qyapi.weixin.qq.com/cgi-bin/kf/get_corp_statistic?access_token=ACCESS_TOKEN * 文档地址: * https://developer.work.weixin.qq.com/document/path/95489 - *

+   * 
* @param request 查询参数 * @return 客户数据统计 -企业汇总数据 * @throws WxErrorException the wx error exception @@ -212,7 +238,7 @@ WxCpKfCustomerBatchGetResp customerBatchGet(List externalUserIdList) * https://qyapi.weixin.qq.com/cgi-bin/kf/get_servicer_statistic?access_token=ACCESS_TOKEN * 文档地址: * https://developer.work.weixin.qq.com/document/path/95490 - *
+   * 
* @param request 查询参数 * @return 客户数据统计 -企业汇总数据 * @throws WxErrorException the wx error exception diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java index a2e2344190..63fabad7a1 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpLivingService.java @@ -27,7 +27,7 @@ public interface WxCpLivingService { /** * 获取直播详情 * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/living/get_living_info?access_token=ACCESS_TOKEN&livingid=LIVINGID + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/living/get_living_info?access_token=ACCESS_TOKEN&livingid=LIVINGID} * * @param livingId 直播id * @return 获取的直播详情 living info diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java index d0d4b661b0..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; @@ -53,6 +55,32 @@ WxMediaUploadResult upload(String mediaType, String fileType, InputStream inputS 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; + /** * 上传多媒体文件. * @@ -82,9 +110,9 @@ WxMediaUploadResult upload(String mediaType, String filename, String url) * 获取高清语音素材. * 可以使用本接口获取从JSSDK的uploadVoice接口上传的临时语音素材,格式为speex,16K采样率。该音频比上文的临时素材获取接口(格式为amr,8K采样率)更加清晰,适合用作语音识别等对音质要求较高的业务。 * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get/jssdk?access_token=ACCESS_TOKEN&media_id=MEDIA_ID + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get/jssdk?access_token=ACCESS_TOKEN&media_id=MEDIA_ID} * 仅企业微信2.4及以上版本支持。 - * 文档地址:https://work.weixin.qq.com/api/doc#90000/90135/90255 + * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/90255 *
* * @param mediaId 媒体id @@ -107,4 +135,21 @@ WxMediaUploadResult upload(String mediaType, String filename, String url) * @throws WxErrorException the wx error exception */ String uploadImg(File file) throws WxErrorException; + + /** + * 生成异步上传任务 + * 跟上传临时素材拿到的media_id使用场景是不通用的,目前适配的接口如下:https://developer.work.weixin.qq.com/document/path/96488#%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E8%AF%B4%E6%98%8E + * @param req 请求参数 + * @return 返回异步任务id + * @throws WxErrorException the wx error exception + */ + String uploadByUrl(MediaUploadByUrlReq req) throws WxErrorException; + + /** + * 查询异步任务结果 + * @param jobId 任务id。最长为128字节,60分钟内有效 + * @return 返回异步任务结果 + * @throws WxErrorException the wx error exception + */ + MediaUploadByUrlResult uploadByUrl(String jobId) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMeetingService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMeetingService.java index e3ee866118..d761f99d0b 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMeetingService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMeetingService.java @@ -5,8 +5,6 @@ import me.chanjar.weixin.cp.bean.oa.meeting.WxCpMeetingUpdateResult; import me.chanjar.weixin.cp.bean.oa.meeting.WxCpUserMeetingIdResult; -import java.util.List; - /** * 企业微信日程接口. * 企业和开发者通过会议接口可以便捷地预定及管理会议,用于小组周会、部门例会等场景。 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMenuService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMenuService.java index 07f300dd14..bfa2c1c938 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 @@ -40,7 +40,7 @@ public interface WxCpMenuService { * @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; + void create(Long agentId, WxMenu menu) throws WxErrorException; /** *
@@ -67,7 +67,7 @@ public interface WxCpMenuService {
    * @throws WxErrorException the wx error exception
    * @see #delete() #delete()
    */
-  void delete(Integer agentId) throws WxErrorException;
+  void delete(Long agentId) throws WxErrorException;
 
   /**
    * 
@@ -96,5 +96,5 @@ public interface WxCpMenuService {
    * @throws WxErrorException the wx error exception
    * @see #get() #get()
    */
-  WxMenu get(Integer agentId) throws WxErrorException;
+  WxMenu get(Long 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
index e49a36ba50..534cc89b36 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java
@@ -72,8 +72,9 @@ public interface WxCpMessageService {
    * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_token=ACCESS_TOKEN
    * 文档地址: https://developer.work.weixin.qq.com/document/path/94867
    * 
+ * * @param msgId 消息id - * @throws WxErrorException + * @throws WxErrorException 异常 */ void recall(String msgId) throws WxErrorException; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java index 221caf2e70..5e8811953f 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java @@ -28,9 +28,26 @@ public interface WxCpMsgAuditService { * @param timeout 超时时间,根据实际需要填写 * @return 返回是否调用成功 chat datas * @throws Exception the exception + * @deprecated 请使用 {@link #getChatRecords(long, long, String, String, long)} 代替, + * 该方法会将SDK暴露给调用方,容易导致SDK生命周期管理混乱,引发JVM崩溃 */ + @Deprecated WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, String passwd, @NonNull long timeout) throws Exception; + /** + * 拉取聊天记录函数(推荐使用) + * 该方法不会将SDK暴露给调用方,SDK生命周期由框架自动管理,更加安全 + * + * @param seq 从指定的seq开始拉取消息,注意的是返回的消息从seq+1开始返回,seq为之前接口返回的最大seq值。首次使用请使用seq:0 + * @param limit 一次拉取的消息条数,最大值1000条,超过1000条会返回错误 + * @param proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081,如果没有传null + * @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123,如果没有传null + * @param timeout 超时时间,根据实际需要填写 + * @return 返回聊天记录列表,不包含SDK信息 + * @throws Exception the exception + */ + List getChatRecords(long seq, @NonNull long limit, String proxy, String passwd, @NonNull long timeout) throws Exception; + /** * 获取解密的聊天数据Model * @@ -39,10 +56,24 @@ public interface WxCpMsgAuditService { * @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ... * @return 解密后的聊天数据 decrypt data * @throws Exception the exception + * @deprecated 请使用 {@link #getDecryptChatData(WxCpChatDatas.WxCpChatData, Integer)} 代替, + * 该方法需要传入SDK,容易导致SDK生命周期管理混乱,引发JVM崩溃 */ + @Deprecated WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception; + /** + * 获取解密的聊天数据Model(推荐使用) + * 该方法不需要传入SDK,SDK由框架自动管理,更加安全 + * + * @param chatData 聊天数据 + * @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ... + * @return 解密后的聊天数据 + * @throws Exception the exception + */ + WxCpChatModel getDecryptChatData(@NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception; + /** * 获取解密的聊天数据明文 * @@ -51,9 +82,23 @@ WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatD * @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ... * @return 解密后的明文 chat plain text * @throws Exception the exception + * @deprecated 请使用 {@link #getChatRecordPlainText(WxCpChatDatas.WxCpChatData, Integer)} 代替, + * 该方法需要传入SDK,容易导致SDK生命周期管理混乱,引发JVM崩溃 */ + @Deprecated String getChatPlainText(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception; + /** + * 获取解密的聊天数据明文(推荐使用) + * 该方法不需要传入SDK,SDK由框架自动管理,更加安全 + * + * @param chatData 聊天数据 + * @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ... + * @return 解密后的明文 + * @throws Exception the exception + */ + String getChatRecordPlainText(@NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception; + /** * 获取媒体文件 * 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。 @@ -69,10 +114,32 @@ WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatD * @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000 * @param targetFilePath 目标文件绝对路径+实际文件名,比如:/usr/local/file/20220114/474f866b39d10718810d55262af82662.gif * @throws WxErrorException the wx error exception + * @deprecated 请使用 {@link #downloadMediaFile(String, String, String, long, String)} 代替, + * 该方法需要传入SDK,容易导致SDK生命周期管理混乱,引发JVM崩溃 */ + @Deprecated void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, @NonNull String targetFilePath) throws WxErrorException; + /** + * 获取媒体文件(推荐使用) + * 该方法不需要传入SDK,SDK由框架自动管理,更加安全 + * 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。 + *

+ * 注意: + * 根据上面返回的文件类型,拼接好存放文件的绝对路径即可。此时绝对路径写入文件流,来达到获取媒体文件的目的。 + * 详情可以看官方文档,亦可阅读此接口源码。 + * + * @param sdkfileid 消息体内容中的sdkfileid信息 + * @param proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081,如果没有传null + * @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123,如果没有传null + * @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000 + * @param targetFilePath 目标文件绝对路径+实际文件名,比如:/usr/local/file/20220114/474f866b39d10718810d55262af82662.gif + * @throws WxErrorException the wx error exception + */ + void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, + @NonNull String targetFilePath) throws WxErrorException; + /** * 获取媒体文件 传入一个lambda,each所有的数据分片byte[],更加灵活 * 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。 @@ -85,10 +152,29 @@ void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, St * @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000 * @param action 传入一个lambda,each所有的数据分片 * @throws WxErrorException the wx error exception + * @deprecated 请使用 {@link #downloadMediaFile(String, String, String, long, Consumer)} 代替, + * 该方法需要传入SDK,容易导致SDK生命周期管理混乱,引发JVM崩溃 */ + @Deprecated void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, @NonNull Consumer action) throws WxErrorException; + /** + * 获取媒体文件 传入一个lambda,each所有的数据分片byte[],更加灵活(推荐使用) + * 该方法不需要传入SDK,SDK由框架自动管理,更加安全 + * 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。 + * 详情可以看官方文档,亦可阅读此接口源码。 + * + * @param sdkfileid 消息体内容中的sdkfileid信息 + * @param proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081,如果没有传null + * @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123,如果没有传null + * @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000 + * @param action 传入一个lambda,each所有的数据分片 + * @throws WxErrorException the wx error exception + */ + void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, + @NonNull Consumer action) throws WxErrorException; + /** * 获取会话内容存档开启成员列表 * 企业可通过此接口,获取企业开启会话内容存档的成员列表 @@ -129,4 +215,20 @@ void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, St */ WxCpAgreeInfo checkSingleAgree(@NonNull WxCpCheckAgreeRequest checkAgreeRequest) throws WxErrorException; + /** + * 关闭当前线程持有的SDK,释放本地资源。 + *

+ * 在线程池场景下,任务结束后必须在 finally 块中调用此方法,防止SDK实例随线程复用而泄漏。 + * 独立线程或一次性任务也建议调用,以主动释放原生资源。 + */ + void closeThreadLocalSdk(); + + /** + * 关闭所有会话存档SDK实例,释放全部原生资源。 + *

+ * 适用于应用关闭阶段(如 Spring Bean 销毁阶段 {@code @PreDestroy} 或 Shutdown Hook)。 + * 调用后,所有线程的SDK均不可再使用。 + */ + void closeAllSdks(); + } 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 a2c47437b2..695534ba73 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,6 +3,7 @@ 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; /** *

@@ -84,14 +85,15 @@ public interface WxCpOAuth2Service {
    * @throws WxErrorException 异常
    * @see #getUserInfo(String) #getUserInfo(String)
    */
-  WxCpOauth2UserInfo getUserInfo(Integer agentId, String code) throws WxErrorException;
+  WxCpOauth2UserInfo getUserInfo(Long agentId, String code) throws WxErrorException;
 
   /**
    * 获取家校访问用户身份
    * 该接口用于根据code获取家长或者学生信息
-   * 

+ *

    * 请求方式:GET(HTTPS)
-   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
+   * 
* * @param code the code * @return school user info @@ -101,14 +103,16 @@ public interface WxCpOAuth2Service { /** *
-   * 使用user_ticket获取成员详情.
+   * 使用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 成员票据 @@ -116,4 +120,36 @@ public interface WxCpOAuth2Service { * @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/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 index 94535fe1da..cc039fd9f5 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMeetingRoomService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaMeetingRoomService.java @@ -1,7 +1,7 @@ package me.chanjar.weixin.cp.api; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.cp.bean.oa.meetingroom.WxCpOaMeetingRoom; +import me.chanjar.weixin.cp.bean.oa.meetingroom.*; import java.util.List; @@ -59,17 +59,115 @@ public interface WxCpOaMeetingRoomService { 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 会议室对象 + * @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/WxCpOaService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java index fec2c08e55..3494dcfa4e 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java @@ -11,7 +11,8 @@ /** * 企业微信OA相关接口. * - * @author Element & Wang_Wong created on 2019-04-06 10:52 + * @author Element, Wang_Wong + * @since 2019-04-06 10:52 */ public interface WxCpOaService { @@ -22,7 +23,7 @@ public interface WxCpOaService { * * 请求方式:POST(HTTPS) * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent?access_token=ACCESS_TOKEN - * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/91853 + * 文档地址 *
* * @param request 请求 @@ -34,7 +35,7 @@ public interface WxCpOaService { /** *
    *  获取打卡数据
-   *  API doc : https://work.weixin.qq.com/api/doc#90000/90135/90262
+   *  文档地址
    * 
* * @param openCheckinDataType 打卡类型。1:上下班打卡;2:外出打卡;3:全部打卡 @@ -50,7 +51,7 @@ List getCheckinData(Integer openCheckinDataType, Date startTime /** *
    *   获取打卡规则
-   *   API doc : https://work.weixin.qq.com/api/doc#90000/90135/90263
+   *  文档地址
    * 
* * @param datetime 需要获取规则的当天日期 @@ -64,7 +65,7 @@ List getCheckinData(Integer openCheckinDataType, Date startTime /** *
    *   获取企业所有打卡规则
-   *   API doc : https://work.weixin.qq.com/api/doc/90000/90135/93384
+   * 文档地址
    * 
* * @return 打卡规则列表 crop checkin option @@ -82,7 +83,7 @@ List getCheckinData(Integer openCheckinDataType, Date startTime * * 一次拉取调用最多拉取100个审批记录,可以通过多次拉取的方式来满足需求,但调用频率不可超过600次/分。 * - * API doc : https://work.weixin.qq.com/api/doc/90000/90135/91816 + * 文档地址 *
* * @param startTime 开始时间 @@ -93,6 +94,7 @@ List getCheckinData(Integer openCheckinDataType, Date startTime * @return WxCpApprovalInfo approval info * @throws WxErrorException . */ + @Deprecated WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime, Integer cursor, Integer size, List filters) throws WxErrorException; @@ -106,16 +108,46 @@ WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime, * @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调用本接口,根据审批单号查询企业微信“审批应用”的审批申请详情。
    *
-   *   API Doc : https://work.weixin.qq.com/api/doc/90000/90135/91983
+   *  文档地址
    * 
* * @param spNo 审批单编号。 @@ -300,7 +332,7 @@ List getDialRecord(Date startTime, Date endTime, Integer offset, * https://qyapi.weixin.qq.com/cgi-bin/checkin/addcheckinuserface?access_token=ACCESS_TOKEN * 文档地址: * https://developer.work.weixin.qq.com/document/path/93378 - *
+   * 
* @param userId 需要录入的用户id * @param userFace 需要录入的人脸图片数据,需要将图片数据base64处理后填入,对已录入的人脸会进行更新处理 * @throws WxErrorException the wx error exception diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java new file mode 100644 index 0000000000..712bc2a89c --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDocService.java @@ -0,0 +1,543 @@ +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.*; + +import java.io.File; +import java.util.Collections; +import java.util.List; + +/** + * 企业微信文档相关接口. + * 文档 + * + * @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; + + /** + * 分享文档/收集表 + * 该接口用于获取文档或收集表的分享链接。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/doc_share?access_token=ACCESS_TOKEN + * + * @param request 分享请求,docid/formid 二选一 + * @return url 文档分享链接 + * @throws WxErrorException the wx error exception + */ + WxCpDocShare docShare(@NonNull WxCpDocShareRequest request) throws WxErrorException; + + /** + * 获取文档权限信息 + * 该接口用于获取文档、表格、智能表格的查看规则、文档通知范围及权限、安全设置信息。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/doc_get_auth?access_token=ACCESS_TOKEN + * + * @param docId 文档docid + * @return 文档权限信息 + * @throws WxErrorException the wx error exception + */ + WxCpDocAuthInfo docGetAuth(@NonNull String docId) throws WxErrorException; + + /** + * 修改文档查看规则 + * 该接口用于修改文档、表格、智能表格查看规则。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/mod_doc_join_rule?access_token=ACCESS_TOKEN + * + * @param request 修改文档查看规则请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp docModifyJoinRule(@NonNull WxCpDocModifyJoinRuleRequest request) throws WxErrorException; + + /** + * 修改文档通知范围及权限 + * 该接口用于修改文档、表格、智能表格通知范围列表。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/mod_doc_member?access_token=ACCESS_TOKEN + * + * @param request 修改文档通知范围及权限请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp docModifyMember(@NonNull WxCpDocModifyMemberRequest request) throws WxErrorException; + + /** + * 修改文档安全设置 + * 该接口用于修改文档、表格、智能表格的安全设置。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/mod_doc_safty_setting?access_token=ACCESS_TOKEN + * + * @param request 修改文档安全设置请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp docModifySafetySetting( + @NonNull WxCpDocModifySafetySettingRequest request + ) throws WxErrorException; + + /** + * @deprecated Use {@link #docModifySafetySetting(WxCpDocModifySafetySettingRequest)} instead. + */ + @Deprecated + default WxCpBaseResp docModifySaftySetting( + @NonNull WxCpDocModifySaftySettingRequest request + ) throws WxErrorException { + WxCpDocModifySafetySettingRequest newReq = + WxCpDocModifySafetySettingRequest.builder() + .docId(request.getDocId()) + .enableReadonlyCopy(request.getEnableReadonlyCopy()) + .watermark(request.getWatermark()) + .build(); + return docModifySafetySetting(newReq); + } + + /** + * 编辑表格内容 + * 该接口可以对一个在线表格批量执行多个更新操作 + *

+ * 注意: + * 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; + + /** + * 获取文档数据 + * 该接口用于获取在线文档内容数据。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/get_doc_data?access_token=ACCESS_TOKEN + * + * @param request 获取文档数据请求参数 + * @return 文档内容数据 + * @throws WxErrorException the wx error exception + */ + WxCpDocData docGetData(@NonNull WxCpDocGetDataRequest request) throws WxErrorException; + + /** + * 编辑文档内容 + * 该接口用于编辑在线文档内容。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/mod_doc?access_token=ACCESS_TOKEN + * + * @param request 编辑文档内容请求参数 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp docModify(@NonNull WxCpDocModifyRequest request) throws WxErrorException; + + /** + * 上传文档图片 + * 该接口用于上传在线文档编辑时使用的图片资源。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/upload_doc_image?access_token=ACCESS_TOKEN + * + * @param file 图片文件 + * @return 上传结果 + * @throws WxErrorException the wx error exception + */ + WxCpDocImageUploadResult docUploadImage(@NonNull File file) throws WxErrorException; + + /** + * 添加文档高级功能账号 + * 该接口用于为在线文档添加高级功能账号。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/add_admin?access_token=ACCESS_TOKEN + * + * @param request 文档高级功能账号请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp docAddAdmin(@NonNull WxCpDocAdminRequest request) throws WxErrorException; + + /** + * 删除文档高级功能账号 + * 该接口用于删除在线文档的高级功能账号。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/del_admin?access_token=ACCESS_TOKEN + * + * @param request 文档高级功能账号请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp docDeleteAdmin(@NonNull WxCpDocAdminRequest request) throws WxErrorException; + + /** + * 获取文档高级功能账号列表 + * 该接口用于获取在线文档的高级功能账号列表。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/get_admin_list?access_token=ACCESS_TOKEN + * + * @param docId 文档 docid + * @return 文档高级功能账号列表 + * @throws WxErrorException the wx error exception + */ + WxCpDocAdminListResult docGetAdminList(@NonNull String docId) throws WxErrorException; + + /** + * 获取智能表格内容权限 + * 该接口用于获取智能表格字段/记录等内容权限信息。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/smartsheet/get_sheet_auth?access_token=ACCESS_TOKEN + * + * @param request 智能表格内容权限请求 + * @return 智能表格内容权限 + * @throws WxErrorException the wx error exception + */ + WxCpDocSmartSheetAuth smartSheetGetAuth(@NonNull WxCpDocSmartSheetAuthRequest request) throws WxErrorException; + + /** + * 修改智能表格内容权限 + * 该接口用于修改智能表格字段/记录等内容权限信息。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/smartsheet/mod_sheet_auth?access_token=ACCESS_TOKEN + * + * @param request 修改智能表格内容权限请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp smartSheetModifyAuth(@NonNull WxCpDocSmartSheetModifyAuthRequest request) throws WxErrorException; + + /** + * 获取智能表格工作表信息. + * + * @param request 智能表格请求 + * @return 智能表格工作表信息 + * @throws WxErrorException the wx error exception + */ + WxCpDocSmartSheetResult smartSheetGetSheet(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 新增智能表格工作表. + * + * @param request 智能表格请求 + * @return 智能表格工作表信息 + * @throws WxErrorException the wx error exception + */ + WxCpDocSmartSheetResult smartSheetAddSheet(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 删除智能表格工作表. + * + * @param request 智能表格请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp smartSheetDeleteSheet(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 更新智能表格工作表. + * + * @param request 智能表格请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp smartSheetUpdateSheet(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 获取智能表格视图. + * + * @param request 智能表格请求 + * @return 智能表格视图 + * @throws WxErrorException the wx error exception + */ + WxCpDocSmartSheetResult smartSheetGetViews(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 新增智能表格视图. + * + * @param request 智能表格请求 + * @return 智能表格视图 + * @throws WxErrorException the wx error exception + */ + WxCpDocSmartSheetResult smartSheetAddView(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 删除智能表格视图. + * + * @param request 智能表格请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp smartSheetDeleteViews(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 更新智能表格视图. + * + * @param request 智能表格请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp smartSheetUpdateView(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 获取智能表格字段. + * + * @param request 智能表格请求 + * @return 智能表格字段 + * @throws WxErrorException the wx error exception + */ + WxCpDocSmartSheetResult smartSheetGetFields(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 新增智能表格字段. + * + * @param request 智能表格请求 + * @return 智能表格字段 + * @throws WxErrorException the wx error exception + */ + WxCpDocSmartSheetResult smartSheetAddFields(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 删除智能表格字段. + * + * @param request 智能表格请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp smartSheetDeleteFields(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 更新智能表格字段. + * + * @param request 智能表格请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp smartSheetUpdateFields(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 获取智能表格记录. + * + * @param request 智能表格请求 + * @return 智能表格记录 + * @throws WxErrorException the wx error exception + */ + WxCpDocSmartSheetResult smartSheetGetRecords(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 新增智能表格记录. + * + * @param request 智能表格请求 + * @return 智能表格记录 + * @throws WxErrorException the wx error exception + */ + WxCpDocSmartSheetResult smartSheetAddRecords(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 删除智能表格记录. + * + * @param request 智能表格请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp smartSheetDeleteRecords(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 更新智能表格记录. + * + * @param request 智能表格请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp smartSheetUpdateRecords(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException; + + /** + * 创建收集表 + * 该接口用于创建收集表。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/create_collect?access_token=ACCESS_TOKEN + * + * @param request 创建收集表请求 + * @return 创建收集表结果 + * @throws WxErrorException the wx error exception + */ + WxCpFormCreateResult formCreate(@NonNull WxCpFormCreateRequest request) throws WxErrorException; + + /** + * 编辑收集表 + * 该接口用于编辑收集表。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/modify_collect?access_token=ACCESS_TOKEN + * + * @param request 编辑收集表请求 + * @return wx cp base resp + * @throws WxErrorException the wx error exception + */ + WxCpBaseResp formModify(@NonNull WxCpFormModifyRequest request) throws WxErrorException; + + /** + * 获取收集表信息 + * 该接口用于读取收集表的信息。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/get_form_info?access_token=ACCESS_TOKEN + * + * @param formId 收集表id + * @return 收集表信息 + * @throws WxErrorException the wx error exception + */ + WxCpFormInfoResult formInfo(@NonNull String formId) throws WxErrorException; + + /** + * 获取收集表统计信息 + * 该接口用于获取收集表的统计信息、已回答成员列表和未回答成员列表。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/get_form_statistic?access_token=ACCESS_TOKEN + * + * @param requests 收集表统计请求数组 + * @return 收集表统计结果(包含 statistic_list) + * @throws WxErrorException the wx error exception + */ + WxCpFormStatisticResult formStatistic(@NonNull List requests) throws WxErrorException; + + /** + * 单个收集表统计查询的兼容封装,底层仍按官方数组请求发送。 + * + * @param request 收集表统计请求 + * @return 收集表统计信息 + * @throws WxErrorException the wx error exception + */ + default WxCpFormStatistic formStatistic(@NonNull WxCpFormStatisticRequest request) throws WxErrorException { + WxCpFormStatisticResult result = formStatistic(Collections.singletonList(request)); + List list = result == null ? null : result.getStatisticList(); + return list == null || list.isEmpty() ? null : list.get(0); + } + + /** + * 获取收集表答案 + * 该接口用于读取收集表的答案。 + *

+ * 请求方式:POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedoc/get_form_answer?access_token=ACCESS_TOKEN + * + * @param request 收集表答案请求 + * @return 收集表答案 + * @throws WxErrorException the wx error exception + */ + WxCpFormAnswer formAnswer(@NonNull WxCpFormAnswerRequest request) throws WxErrorException; + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java index ad2dc635fc..e7217616b8 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaWeDriveService.java @@ -9,7 +9,7 @@ /** * 企业微信微盘相关接口. - * https://developer.work.weixin.qq.com/document/path/93654 + * ... * * @author Wang_Wong created on 2022-04-22 */ @@ -20,7 +20,7 @@ public interface WxCpOaWeDriveService { * 该接口用于在微盘内新建空间,可以指定人创建空间。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/space_create?access_token=ACCESS_TOKEN + * 请求地址: ... * * @param request 新建空间对应请求参数 * @return spaceid (空间id) @@ -33,7 +33,7 @@ public interface WxCpOaWeDriveService { * 该接口用于重命名已有空间,接收userid参数,以空间管理员身份来重命名。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/space_rename?access_token=ACCESS_TOKEN + * 请求地址: ... * * @param request 重命名空间的请求参数 * @return wx cp base resp @@ -46,35 +46,33 @@ public interface WxCpOaWeDriveService { * 该接口用于解散已有空间,需要以空间管理员身份来解散。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/space_dismiss?access_token=ACCESS_TOKEN + * 请求地址: ... * - * @param userId the user id * @param spaceId the space id * @return wx cp base resp * @throws WxErrorException the wx error exception */ - WxCpBaseResp spaceDismiss(@NonNull String userId, @NonNull String spaceId) throws WxErrorException; + WxCpBaseResp spaceDismiss(@NonNull String spaceId) throws WxErrorException; /** * 获取空间信息 * 该接口用于获取空间成员列表、信息、权限等信息。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/space_info?access_token=ACCESS_TOKEN + * 请求地址: ... * - * @param userId the user id * @param spaceId the space id * @return wx cp space info * @throws WxErrorException the wx error exception */ - WxCpSpaceInfo spaceInfo(@NonNull String userId, @NonNull String spaceId) throws WxErrorException; + WxCpSpaceInfo spaceInfo(@NonNull String spaceId) throws WxErrorException; /** * 添加成员/部门 * 该接口用于对指定空间添加成员/部门,可一次性添加多个。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/space_acl_add?access_token=ACCESS_TOKEN + * 请求地址: ... * * @param request 添加成员/部门请求参数 * @return wx cp base resp @@ -87,7 +85,7 @@ public interface WxCpOaWeDriveService { * 该接口用于对指定空间移除成员/部门,操作者需要有移除权限。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/space_acl_del?access_token=ACCESS_TOKEN + * 请求地址: ... * * @param request 移除成员/部门请求参数 * @return wx cp base resp @@ -100,7 +98,7 @@ public interface WxCpOaWeDriveService { * 该接口用于修改空间权限,需要传入userid,修改权限范围继承传入用户的权限范围。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/space_setting?access_token=ACCESS_TOKEN + * 请求地址: ... * * @param request 权限管理请求参数 * @return wx cp base resp @@ -113,21 +111,20 @@ public interface WxCpOaWeDriveService { * 该接口用于获取空间邀请分享链接。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/space_share?access_token=ACCESS_TOKEN + * 请求地址: ... * - * @param userId the user id * @param spaceId the space id * @return wx cp space share * @throws WxErrorException the wx error exception */ - WxCpSpaceShare spaceShare(@NonNull String userId, @NonNull String spaceId) throws WxErrorException; + WxCpSpaceShare spaceShare(@NonNull String spaceId) throws WxErrorException; /** * 获取文件列表 * 该接口用于获取指定地址下的文件列表。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/file_list?access_token=ACCESS_TOKEN + * 请求地址: ... * * @param request 获取文件列表请求参数 * @return wx cp file list @@ -140,7 +137,7 @@ public interface WxCpOaWeDriveService { * 该接口用于向微盘中的指定位置上传文件。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/file_upload?access_token=ACCESS_TOKEN + * 请求地址: ... * * @param request 上传文件请求参数 * @return wx cp file upload @@ -153,54 +150,58 @@ public interface WxCpOaWeDriveService { * 该接口用于下载文件,请求的userid需有下载权限。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/file_download?access_token=ACCESS_TOKEN + * 请求地址: ... * - * @param userId the user id - * @param fileId the file id - * @return wx cp file download + * @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(@NonNull String userId, @NonNull String fileId) throws WxErrorException; + WxCpFileDownload fileDownload(String fileId, String selectedTicket) throws WxErrorException; /** * 重命名文件 * 该接口用于对指定文件进行重命名。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/file_rename?access_token=ACCESS_TOKEN + * 请求地址: ... * - * @param userId the user id * @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 userId, @NonNull String fileId, @NonNull String newName) throws WxErrorException; + WxCpFileRename fileRename(@NonNull String fileId, @NonNull String newName) throws WxErrorException; /** - * 新建文件/微文档 - * 该接口用于在微盘指定位置新建文件、微文档。 + * 新建文件夹/文档 + * 该接口用于在微盘指定位置新建文件夹、文档(更多文档接口能力可见文档API接口说明)。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/file_create?access_token=ACCESS_TOKEN + * 请求地址: ... * - * @param userId 操作者userid * @param spaceId 空间spaceid * @param fatherId 父目录fileid, 在根目录时为空间spaceid - * @param fileType 文件类型, 1:文件夹 3:微文档(文档) 4:微文档(表格) - * @param fileName 文件名字 + * @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 userId, @NonNull String spaceId, - @NonNull String fatherId, @NonNull Integer fileType, @NonNull String fileName) throws WxErrorException; + WxCpFileCreate fileCreate(@NonNull String spaceId, @NonNull String fatherId, @NonNull Integer fileType, + @NonNull String fileName) throws WxErrorException; /** * 移动文件 * 该接口用于将文件移动到指定位置。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/file_move?access_token=ACCESS_TOKEN + * 请求地址: ... * * @param request 移动文件的请求参数 * @return wx cp file move @@ -213,21 +214,33 @@ WxCpFileCreate fileCreate(@NonNull String userId, @NonNull String spaceId, * 该接口用于删除指定文件。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/file_delete?access_token=ACCESS_TOKEN + * 请求地址: ... * - * @param userId 操作者userid - * @param fileId 文件fileid列表 + * @param fileIds 文件fileid列表 * @return wx cp base resp * @throws WxErrorException the wx error exception */ - WxCpBaseResp fileDelete(@NonNull String userId, @NonNull List fileId) throws WxErrorException; + 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) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/file_acl_add?access_token=ACCESS_TOKEN + * 请求地址: ... * * @param request 新增指定人请求参数 * @return wx cp base resp @@ -240,7 +253,7 @@ WxCpFileCreate fileCreate(@NonNull String userId, @NonNull String spaceId, * 该接口用于删除指定文件的指定人/部门。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/file_acl_del?access_token=ACCESS_TOKEN + * 请求地址: ... * * @param request 请求参数 * @return wx cp base resp @@ -253,43 +266,27 @@ WxCpFileCreate fileCreate(@NonNull String userId, @NonNull String spaceId, * 该接口用于文件的分享设置。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/file_setting?access_token=ACCESS_TOKEN + * 请求地址: ... * - * @param userId the user id * @param fileId the file id * @param authScope the auth scope * @param auth the auth * @return wx cp base resp * @throws WxErrorException the wx error exception */ - WxCpBaseResp fileSetting(@NonNull String userId, @NonNull String fileId, @NonNull Integer authScope, Integer auth) throws WxErrorException; + WxCpBaseResp fileSetting(@NonNull String fileId, @NonNull Integer authScope, Integer auth) throws WxErrorException; /** * 获取分享链接 * 该接口用于获取文件的分享链接。 *

* 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/file_share?access_token=ACCESS_TOKEN + * 请求地址: ... * - * @param userId the user id * @param fileId the file id * @return wx cp file share * @throws WxErrorException the wx error exception */ - WxCpFileShare fileShare(@NonNull String userId, @NonNull String fileId) throws WxErrorException; - - /** - * 文件信息 - * 该接口用于获取指定文件的信息。 - *

- * 请求方式:POST(HTTPS) - * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/wedrive/file_info?access_token=ACCESS_TOKEN - * - * @param userId the user id - * @param fileId the file id - * @return wx cp file info - * @throws WxErrorException the wx error exception - */ - WxCpFileInfo fileInfo(@NonNull String userId, @NonNull String fileId) throws WxErrorException; + WxCpFileShare fileShare(@NonNull String fileId) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java index 56687c9cb1..5f1d41c197 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolService.java @@ -80,9 +80,10 @@ public interface WxCpSchoolService { /** * 获取直播详情 + *

    * 请求方式:GET(HTTPS)
-   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/living/get_living_info?access_token=ACCESS_TOKEN&livingid
-   * =LIVINGID
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/living/get_living_info?access_token=ACCESS_TOKEN&livingid=LIVINGID}
+   * 
* * @param livingId the living id * @return living info diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java index 26cfe3a019..d004ca8aa5 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpSchoolUserService.java @@ -19,9 +19,10 @@ public interface WxCpSchoolUserService { /** * 获取访问用户身份 * 该接口用于根据code获取成员信息 - *

+ *

    * 请求方式:GET(HTTPS)
-   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
+   * 
* * @param code the code * @return user info @@ -32,9 +33,10 @@ public interface WxCpSchoolUserService { /** * 获取家校访问用户身份 * 该接口用于根据code获取家长或者学生信息 - *

+ *

    * 请求方式:GET(HTTPS)
-   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/getuserinfo?access_token=ACCESS_TOKEN&code=CODE}
+   * 
* * @param code the code * @return school user info @@ -90,8 +92,10 @@ public interface WxCpSchoolUserService { /** * 删除学生 + *
    * 请求方式:GET(HTTPS)
-   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_student?access_token=ACCESS_TOKEN&userid=USERID
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_student?access_token=ACCESS_TOKEN&userid=USERID}
+   * 
* * @param studentUserId the student user id * @return wx cp base resp @@ -160,8 +164,10 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI /** * 读取学生或家长 + *
    * 请求方式:GET(HTTPS)
-   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/get?access_token=ACCESS_TOKEN&userid=USERID
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/get?access_token=ACCESS_TOKEN&userid=USERID}
+   * 
* * @param userId the user id * @return user @@ -171,9 +177,10 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI /** * 获取部门成员详情 + *
    * 请求方式:GET(HTTPS)
-   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID
-   * &fetch_child=FETCH_CHILD
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD}
+   * 
* * @param departmentId 获取的部门id * @param fetchChild 1/0:是否递归获取子部门下面的成员 @@ -184,9 +191,10 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI /** * 获取部门家长详情 + *
    * 请求方式:GET(HTTPS)
-   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list_parent?access_token=ACCESS_TOKEN&department_id
-   * =DEPARTMENT_ID
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/list_parent?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID}
+   * 
* * @param departmentId 获取的部门id * @return user list parent @@ -207,8 +215,10 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI /** * 删除家长 + *
    * 请求方式:GET(HTTPS)
-   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_parent?access_token=ACCESS_TOKEN&userid=USERID
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/user/delete_parent?access_token=ACCESS_TOKEN&userid=USERID}
+   * 
* * @param userId the user id * @return wx cp base resp @@ -256,7 +266,7 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI /** * 删除部门 * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/delete?access_token=ACCESS_TOKEN&id=ID + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/delete?access_token=ACCESS_TOKEN&id=ID} * * @param id the id * @return wx cp base resp @@ -292,10 +302,9 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI /** * 获取外部联系人详情 * 学校可通过此接口,根据外部联系人的userid(如何获取?),拉取外部联系人详情。 - *

+ * * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid - * =EXTERNAL_USERID + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid=EXTERNAL_USERID} * * @param externalUserId 外部联系人的userid,注意不是学校成员的帐号 * @return external contact @@ -306,9 +315,9 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI /** * 获取可使用的家长范围 * 获取可在微信「学校通知-学校应用」使用该应用的家长范围,以学生或部门列表的形式返回。应用只能给该列表下的家长发送「学校通知」。注意该范围只能由学校的系统管理员在「管理端-家校沟通-配置」配置。 - *

+ * * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/agent/get_allow_scope?access_token=ACCESS_TOKEN&agentid=AGENTID + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/agent/get_allow_scope?access_token=ACCESS_TOKEN&agentid=AGENTID} * * @param agentId the agent id * @return allow scope @@ -332,9 +341,9 @@ WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStudentUserI /** * 获取部门列表 * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/list?access_token=ACCESS_TOKEN&id=ID + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/school/department/list?access_token=ACCESS_TOKEN&id=ID} * - * @param id the id + * @param id 部门id。获取指定部门及其下的子部门。 如果不填,默认获取全量组织架构 * @return wx cp department list * @throws WxErrorException the wx error exception */ diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java index 07400af53c..9a715c1e91 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 @@ -12,7 +12,6 @@ import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; import me.chanjar.weixin.cp.bean.WxCpProviderToken; import me.chanjar.weixin.cp.config.WxCpConfigStorage; -import me.chanjar.weixin.cp.corpgroup.service.WxCpLinkedCorpService; /** * 微信API的Service. @@ -58,6 +57,32 @@ public interface WxCpService extends WxService { */ String getAccessToken(boolean forceRefresh) throws WxErrorException; + /** + *

+   * 获取通讯录同步access_token,本方法线程安全
+   * 通讯录同步相关接口仅支持通过"通讯录同步secret"调用,需要使用独立的access_token
+   * 详情请见: https://developer.work.weixin.qq.com/document/path/91579
+   * 
+ * + * @param forceRefresh 强制刷新 + * @return 通讯录同步专用的access token + * @throws WxErrorException the wx error exception + */ + String getContactAccessToken(boolean forceRefresh) throws WxErrorException; + + /** + *
+   * 获取会话存档access_token,本方法线程安全
+   * 会话存档相关接口需要使用会话存档secret获取单独的access_token
+   * 详情请见: https://developer.work.weixin.qq.com/document/path/91782
+   * 
+ * + * @param forceRefresh 强制刷新 + * @return 会话存档专用的access token + * @throws WxErrorException the wx error exception + */ + String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException; + /** * 获得jsapi_ticket,不强制刷新jsapi_ticket * @@ -150,7 +175,7 @@ public interface WxCpService extends WxService { /** *
-   * 获取微信服务器的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
    * 
* @@ -159,6 +184,17 @@ public interface WxCpService extends WxService { */ 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; + /** *
    * 获取服务商凭证
@@ -184,6 +220,45 @@ public interface WxCpService extends WxService {
    */
   String postWithoutToken(String url, String postData) throws WxErrorException;
 
+  /**
+   * 
+   * 使用会话存档access token发起post请求
+   * 会话存档相关API需要使用会话存档专用的secret获取独立的access token
+   * 
+ * + * @param url 接口地址 + * @param postData 请求body字符串 + * @return the string + * @throws WxErrorException the wx error exception + */ + String postForMsgAudit(String url, String postData) throws WxErrorException; + + /** + *
+   * 使用通讯录同步access token发起get请求
+   * 通讯录同步相关API需要使用通讯录同步专用的secret获取独立的access token
+   * 
+ * + * @param url 接口地址 + * @param queryParam 请求参数 + * @return the string + * @throws WxErrorException the wx error exception + */ + String getForContact(String url, String queryParam) throws WxErrorException; + + /** + *
+   * 使用通讯录同步access token发起post请求
+   * 通讯录同步相关API需要使用通讯录同步专用的secret获取独立的access token
+   * 
+ * + * @param url 接口地址 + * @param postData 请求body字符串 + * @return the string + * @throws WxErrorException the wx error exception + */ + String postForContact(String url, String postData) throws WxErrorException; + /** *
    * Service没有实现某个API的时候,可以用这个,
@@ -445,6 +520,13 @@ public interface WxCpService extends WxService {
    */
   WxCpOaWeDriveService getOaWeDriveService();
 
+  /**
+   * 获取OA效率工具 文档的服务类对象
+   *
+   * @return oa we doc service
+   */
+  WxCpOaWeDocService getOaWeDocService();
+
   /**
    * 获取会话存档相关接口的服务类对象
    *
@@ -567,14 +649,35 @@ public interface WxCpService extends WxService {
   /**
    * 相关接口的服务类对象
    *
-   * @return  the meeting service
+   * @return the meeting service
    */
   WxCpMeetingService getMeetingService();
 
   /**
    * 企业互联的服务类对象
    *
-   * @return
+   * @return 企业互联服务对象
    */
   WxCpCorpGroupService getCorpGroupService();
+
+  /**
+   * 获取智能机器人服务
+   *
+   * @return 智能机器人服务 intelligent robot service
+   */
+  WxCpIntelligentRobotService getIntelligentRobotService();
+
+  /**
+   * 获取人事助手服务
+   *
+   * @return 人事助手服务 hr service
+   */
+  WxCpHrService getHrService();
+
+  /**
+   * 获取待办服务
+   *
+   * @return 待办服务 todo service
+   */
+  WxCpTodoService getTodoService();
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTodoService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTodoService.java
new file mode 100644
index 0000000000..82f17c2303
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTodoService.java
@@ -0,0 +1,47 @@
+package me.chanjar.weixin.cp.api;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.todo.WxCpTodo;
+
+import java.util.List;
+
+/**
+ * 企业微信待办接口.
+ * 

+ * 官方文档: + * 获取待办详情, + * 更新待办状态 + * + * @author Binary Wang created on 2026-08-11 + */ +public interface WxCpTodoService { + /** + * 获取待办详情. + *

+ * 该接口用于获取指定的待办详情,请求参数仅包含必填的 todo_id,响应直接返回单个待办对象。 + *

+ * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/todo/get?access_token=ACCESS_TOKEN + * + * @param todoId 待办ID + * @return 待办详情 wx cp todo + * @throws WxErrorException the wx error exception + */ + WxCpTodo get(String todoId) throws WxErrorException; + + /** + * 更新待办状态. + *

+ * 该接口用于修改指定的待办信息,支持修改待办整体状态(status 字段)、待办参与人及其状态(attendees[].userid / status 字段)。 + * 仅允许修改当前应用创建的待办,不允许修改已删除的待办。 + *

+ * 请求方式: POST(HTTPS) + * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/todo/update?access_token=ACCESS_TOKEN + * + * @param todoId 待办ID + * @param status 待办整体状态,可不传:0 - 完成;1 - 进行中。为 null 时不修改整体状态 + * @param attendees 待办参与人列表,最多支持20个参与人。为 null 或空时不修改参与人列表 + * @throws WxErrorException the wx error exception + */ + void update(String todoId, Integer status, List attendees) throws WxErrorException; +} 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 b397a121a4..7a7b5f40a8 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java @@ -38,7 +38,7 @@ public interface WxCpUserService { *

    * 获取部门成员详情
    * 请求方式:GET(HTTPS)
-   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD
+   * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD}
    *
    * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/90201
    * 
@@ -185,7 +185,8 @@ public interface WxCpUserService { * 文档地址:https://developer.work.weixin.qq.com/document/path/95895 *
* - * @param email 手机号码。长度为5~32个字节 + * @param email 邮箱 + * @param emailType 邮箱类型:1-企业邮箱;2-个人邮箱 * @return userid email对应的成员userid * @throws WxErrorException . */ @@ -212,7 +213,7 @@ public interface WxCpUserService { * 获取加入企业二维码。 * * 请求方式:GET(HTTPS) - * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/corp/get_join_qrcode?access_token=ACCESS_TOKEN&size_type=SIZE_TYPE + * {@code 请求地址:https://qyapi.weixin.qq.com/cgi-bin/corp/get_join_qrcode?access_token=ACCESS_TOKEN&size_type=SIZE_TYPE} * * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/91714 *
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java index 0d43145488..eb8abd773f 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 @@ -5,12 +5,14 @@ import com.google.gson.JsonObject; import lombok.extern.slf4j.Slf4j; 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; @@ -24,8 +26,6 @@ import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; import me.chanjar.weixin.cp.bean.WxCpProviderToken; import me.chanjar.weixin.cp.config.WxCpConfigStorage; -import me.chanjar.weixin.cp.corpgroup.service.WxCpLinkedCorpService; -import me.chanjar.weixin.cp.corpgroup.service.impl.WxCpLinkedCorpServiceImpl; import org.apache.commons.lang3.StringUtils; import java.io.File; @@ -59,6 +59,7 @@ public abstract class BaseWxCpServiceImpl implements WxCpService, RequestH private final WxCpLivingService livingService = new WxCpLivingServiceImpl(this); private final WxCpOaAgentService oaAgentService = new WxCpOaAgentServiceImpl(this); private final WxCpOaWeDriveService oaWeDriveService = new WxCpOaWeDriveServiceImpl(this); + private final WxCpOaWeDocService oaWeDocService = new WxCpOaWeDocServiceImpl(this); private final WxCpMsgAuditService msgAuditService = new WxCpMsgAuditServiceImpl(this); private final WxCpTaskCardService taskCardService = new WxCpTaskCardServiceImpl(this); private final WxCpExternalContactService externalContactService = new WxCpExternalContactServiceImpl(this); @@ -74,6 +75,9 @@ public abstract class BaseWxCpServiceImpl implements WxCpService, RequestH private final WxCpMeetingService meetingService = new WxCpMeetingServiceImpl(this); private final WxCpCorpGroupService corpGroupService = new WxCpCorpGroupServiceImpl(this); + private final WxCpIntelligentRobotService intelligentRobotService = new WxCpIntelligentRobotServiceImpl(this); + private final WxCpHrService hrService = new WxCpHrServiceImpl(this); + private final WxCpTodoService todoService = new WxCpTodoServiceImpl(this); /** * 全局的是否正在刷新access token的锁. @@ -110,7 +114,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; } } @@ -229,7 +233,23 @@ public WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorEx @Override public String[] getCallbackIp() throws WxErrorException { - String responseContent = get(this.configStorage.getApiUrl(GET_CALLBACK_IP), null); + 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()]; @@ -268,6 +288,12 @@ 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()); @@ -278,6 +304,39 @@ public String postWithoutToken(String url, String postData) throws WxErrorExcept return this.executeNormal(SimplePostRequestExecutor.create(this), url, postData); } + @Override + public String postForMsgAudit(String url, String postData) throws WxErrorException { + // 获取会话存档专用的access token + String msgAuditAccessToken = getMsgAuditAccessToken(false); + // 拼接access_token参数 + String urlWithToken = url + (url.contains("?") ? "&" : "?") + "access_token=" + msgAuditAccessToken; + // 使用executeNormal方法,不自动添加token + return this.executeNormal(SimplePostRequestExecutor.create(this), urlWithToken, postData); + } + + @Override + public String getForContact(String url, String queryParam) throws WxErrorException { + // 获取通讯录同步专用的access token + String contactAccessToken = getContactAccessToken(false); + // 拼接access_token参数 + String urlWithToken = url + (url.contains("?") ? "&" : "?") + "access_token=" + contactAccessToken; + if (queryParam != null && !queryParam.isEmpty()) { + urlWithToken = urlWithToken + "&" + queryParam; + } + // 使用executeNormal方法,不自动添加token + return this.executeNormal(SimpleGetRequestExecutor.create(this), urlWithToken, null); + } + + @Override + public String postForContact(String url, String postData) throws WxErrorException { + // 获取通讯录同步专用的access token + String contactAccessToken = getContactAccessToken(false); + // 拼接access_token参数 + String urlWithToken = url + (url.contains("?") ? "&" : "?") + "access_token=" + contactAccessToken; + // 使用executeNormal方法,不自动添加token + return this.executeNormal(SimplePostRequestExecutor.create(this), urlWithToken, postData); + } + /** * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求. */ @@ -371,23 +430,29 @@ protected T executeInternal(RequestExecutor executor, String uri, E * 普通请求,不自动带accessToken */ private T executeNormal(RequestExecutor executor, String uri, E data) throws WxErrorException { + String uriForLog = redactQueryString(uri); try { T result = executor.execute(uri, data, WxType.CP); - log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uri, data, result); + log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriForLog, data, result); return result; } catch (WxErrorException e) { WxError error = e.getError(); if (error.getErrorCode() != 0) { - log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uri, data, error); + log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriForLog, data, error); throw new WxErrorException(error, e); } return null; } catch (IOException e) { - log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uri, data, e.getMessage()); + log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriForLog, data, e.getMessage()); throw new WxErrorException(e); } } + static String redactQueryString(String uri) { + int queryStart = uri.indexOf('?'); + return queryStart < 0 ? uri : uri.substring(0, queryStart) + "?******"; + } + @Override public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) { this.configStorage = wxConfigProvider; @@ -561,6 +626,11 @@ public WxCpOaWeDriveService getOaWeDriveService() { return oaWeDriveService; } + @Override + public WxCpOaWeDocService getOaWeDocService() { + return oaWeDocService; + } + @Override public WxCpMsgAuditService getMsgAuditService() { return msgAuditService; @@ -680,4 +750,19 @@ public WxCpMeetingService getMeetingService() { public WxCpCorpGroupService getCorpGroupService() { return corpGroupService; } + + @Override + public WxCpIntelligentRobotService getIntelligentRobotService() { + return this.intelligentRobotService; + } + + @Override + public WxCpHrService getHrService() { + return this.hrService; + } + + @Override + public WxCpTodoService getTodoService() { + return this.todoService; + } } 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 81628fed82..6f2ade2e49 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 @@ -11,6 +11,7 @@ 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; @@ -33,7 +34,7 @@ public class WxCpAgentServiceImpl implements WxCpAgentService { private final WxCpService mainService; @Override - public WxCpAgent get(Integer agentId) throws WxErrorException { + public WxCpAgent get(Long agentId) throws WxErrorException { if (agentId == null) { throw new IllegalArgumentException("缺少agentid参数"); } @@ -65,4 +66,21 @@ public List list() throws WxErrorException { }.getType()); } + @Override + public WxCpTpAdmin getAdminList(Long 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 index bb5c191e96..b0bbb38642 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentWorkBenchServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentWorkBenchServiceImpl.java @@ -38,4 +38,10 @@ public void setWorkBenchData(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErr final String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(WORKBENCH_DATA_SET)); this.mainService.post(url, wxCpAgentWorkBench.toUserDataString()); } + + @Override + public void batchSetWorkBenchData(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErrorException { + final String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(WORKBENCH_BATCH_DATA_SET)); + this.mainService.post(url, wxCpAgentWorkBench.toBatchUserDataString()); + } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java index ae4db4582a..73d11cc15d 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpCorpGroupServiceImpl.java @@ -8,7 +8,6 @@ import me.chanjar.weixin.cp.api.WxCpCorpGroupService; import me.chanjar.weixin.cp.api.WxCpService; import me.chanjar.weixin.cp.bean.corpgroup.WxCpCorpGroupCorp; -import me.chanjar.weixin.cp.bean.corpgroup.WxCpCorpGroupCorpListAppShareInfoResp; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; import java.util.List; @@ -19,14 +18,14 @@ * 企业互联相关接口实现类 * * @author libo <422423229@qq.com> - * Created on 27/2/2023 9:57 PM + * @since 2023-02-27 9:57 PM */ @RequiredArgsConstructor public class WxCpCorpGroupServiceImpl implements WxCpCorpGroupService { private final WxCpService cpService; @Override - public List listAppShareInfo(Integer agentId, Integer businessType, String corpId, + public List listAppShareInfo(Long 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(); @@ -39,7 +38,7 @@ public List listAppShareInfo(Integer agentId, Integer busines JsonObject tmpJson = GsonParser.parse(responseContent); return WxCpGsonBuilder.create().fromJson(tmpJson.get("corp_list"), - new TypeToken>() { + new TypeToken>() { }.getType() ); } 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 43f44421c9..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,7 +1,17 @@ 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; @@ -20,23 +30,15 @@ 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.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 static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.ExternalContact.*; - /** * The type Wx cp external contact service. * - * @author 曹祖鹏 & yuanqixun & Mr.Pan & Wang_Wong + * @author 曹祖鹏, yuanqixun, Mr.Pan, Wang_Wong */ @RequiredArgsConstructor public class WxCpExternalContactServiceImpl implements WxCpExternalContactService { @@ -63,6 +65,17 @@ public WxCpContactWayInfo getContactWay(String configId) throws WxErrorException return WxCpContactWayInfo.fromJson(this.mainService.post(url, json.toString())); } + @Override + public WxCpContactWayList listContactWay(Long startTime, Long endTime, String cursor, Long limit) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("start_time", startTime); + json.addProperty("end_time", endTime); + json.addProperty("cursor", cursor); + json.addProperty("limit", limit); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_CONTACT_WAY); + return WxCpContactWayList.fromJson(this.mainService.post(url, json.toString())); + } + @Override public WxCpBaseResp updateContactWay(WxCpContactWayInfo info) throws WxErrorException { if (StringUtils.isBlank(info.getContactWay().getConfigId())) { @@ -101,14 +114,14 @@ public WxCpBaseResp closeTempChat(String userId, String externalUserId) throws W } @Override - public WxCpExternalContactInfo getExternalContact(String userId) throws WxErrorException { - final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_EXTERNAL_CONTACT + userId); + 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 userId, String cursor) throws WxErrorException { - String params = userId; + public WxCpExternalContactInfo getContactDetail(String externalUserId, String cursor) throws WxErrorException { + String params = externalUserId; if (StringUtils.isNotEmpty(cursor)) { params = params + "&cursor=" + cursor; } @@ -212,6 +225,20 @@ public WxCpExternalContactBatchInfo getContactDetailBatch(String[] userIdList, S 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); @@ -527,6 +554,14 @@ public WxCpGetMomentTaskResult getMomentTaskResult(String jobId) throws WxErrorE 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 { @@ -739,6 +774,19 @@ public void delInterceptRule(String ruleId) throws WxErrorException { 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); @@ -817,6 +865,22 @@ public WxCpCustomerAcquisitionQuota customerAcquisitionQuota() throws WxErrorExc 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) { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java index 30e4474992..8373c6c8ee 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java @@ -42,6 +42,11 @@ public void sendMarkdown(String content) throws WxErrorException { this.sendMarkdown(this.getWebhookUrl(), content); } + @Override + public void sendMarkdownV2(String content) throws WxErrorException { + this.sendMarkdownV2(this.getWebhookUrl(), content); + } + @Override public void sendImage(String base64, String md5) throws WxErrorException { this.sendImage(this.getWebhookUrl(), base64, md5); @@ -70,6 +75,14 @@ public void sendMarkdown(String webhookUrl, String content) throws WxErrorExcept .toJson()); } + @Override + public void sendMarkdownV2(String webhookUrl, String content) throws WxErrorException { + this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage() + .setMsgType(GroupRobotMsgType.MARKDOWN_V2) + .setContent(content) + .toJson()); + } + @Override public void sendImage(String webhookUrl, String base64, String md5) throws WxErrorException { this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage() @@ -92,6 +105,14 @@ public void sendFile(String webhookUrl, String mediaId) throws WxErrorException .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/WxCpHrServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpHrServiceImpl.java new file mode 100644 index 0000000000..df71643d4c --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpHrServiceImpl.java @@ -0,0 +1,80 @@ +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.WxCpHrService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldData; +import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldDataResp; +import me.chanjar.weixin.cp.bean.hr.WxCpHrEmployeeFieldInfoResp; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Hr.*; + +/** + * 人事助手相关接口实现类. + * 官方文档:https://developer.work.weixin.qq.com/document/path/99132 + * + * @author leejoker created on 2024-01-01 + */ +@RequiredArgsConstructor +public class WxCpHrServiceImpl implements WxCpHrService { + + private final WxCpService cpService; + + @Override + public WxCpHrEmployeeFieldInfoResp getFieldInfo(List fields) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + if (fields != null && !fields.isEmpty()) { + jsonObject.add("fields", WxCpGsonBuilder.create().toJsonTree(fields)); + } + String response = this.cpService.post( + this.cpService.getWxCpConfigStorage().getApiUrl(GET_FIELD_INFO), + jsonObject.toString() + ); + return WxCpHrEmployeeFieldInfoResp.fromJson(response); + } + + @Override + public WxCpHrEmployeeFieldDataResp getEmployeeFieldInfo(String userid, List fields) throws WxErrorException { + return getEmployeeFieldInfo(userid, false, fields); + } + + @Override + public WxCpHrEmployeeFieldDataResp getEmployeeFieldInfo(String userid, boolean getAll, List fields) throws WxErrorException { + if (userid == null || userid.trim().isEmpty()) { + throw new IllegalArgumentException("userid 不能为空"); + } + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("userid", userid); + jsonObject.addProperty("get_all", getAll); + if (fields != null && !fields.isEmpty()) { + jsonObject.add("fields", WxCpGsonBuilder.create().toJsonTree(fields)); + } + String response = this.cpService.post( + this.cpService.getWxCpConfigStorage().getApiUrl(GET_EMPLOYEE_FIELD_INFO), + jsonObject.toString() + ); + return WxCpHrEmployeeFieldDataResp.fromJson(response); + } + + @Override + public void updateEmployeeFieldInfo(String userid, List fieldList) throws WxErrorException { + if (userid == null || userid.trim().isEmpty()) { + throw new IllegalArgumentException("userid 不能为空"); + } + if (fieldList == null || fieldList.isEmpty()) { + throw new IllegalArgumentException("fieldList 不能为空"); + } + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("userid", userid); + jsonObject.add("field_list", WxCpGsonBuilder.create().toJsonTree(fieldList)); + this.cpService.post( + this.cpService.getWxCpConfigStorage().getApiUrl(UPDATE_EMPLOYEE_FIELD_INFO), + jsonObject.toString() + ); + } +} 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..a5ccdca5e1 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpIntelligentRobotServiceImpl.java @@ -0,0 +1,91 @@ +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.crypto.WxCpIntelligentRobotCryptUtil; +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); + } + + @Override + public WxCpIntelligentRobotMessage parseCallbackMessage(String callbackMessageJson) { + return WxCpIntelligentRobotMessage.fromJson(callbackMessageJson); + } + + @Override + public WxCpIntelligentRobotMessage parseEncryptedCallbackMessage(String msgSignature, String timestamp, String nonce, + String encryptedJson, String token, + String encodingAesKey, String aiBotId) { + WxCpIntelligentRobotCryptUtil cryptUtil = new WxCpIntelligentRobotCryptUtil(token, encodingAesKey, aiBotId); + return parseCallbackMessage(cryptUtil.decrypt(msgSignature, timestamp, nonce, encryptedJson)); + } + + @Override + public String replyMessage(String responseUrl, String plainJson, String token, String encodingAesKey, + String aiBotId, String timestamp, String nonce) throws WxErrorException { + WxCpIntelligentRobotCryptUtil cryptUtil = new WxCpIntelligentRobotCryptUtil(token, encodingAesKey, aiBotId); + return this.cpService.postWithoutToken(responseUrl, cryptUtil.encrypt(plainJson, timestamp, nonce)); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpKfServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpKfServiceImpl.java index 29e84c516f..be4f2a5850 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpKfServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpKfServiceImpl.java @@ -70,23 +70,62 @@ public WxCpKfAccountLinkResp getAccountLink(WxCpKfAccountLink link) throws WxErr @Override public WxCpKfServicerOpResp addServicer(String openKfid, List userIdList) throws WxErrorException { - return servicerOp(openKfid, userIdList, SERVICER_ADD); + return servicerOp(openKfid, userIdList, null, SERVICER_ADD); + } + + @Override + public WxCpKfServicerOpResp addServicer(String openKfId, List userIdList, List departmentIdList) throws WxErrorException { + validateParameters(SERVICER_ADD, userIdList, departmentIdList); + return servicerOp(openKfId, userIdList, departmentIdList, SERVICER_ADD); } @Override public WxCpKfServicerOpResp delServicer(String openKfid, List userIdList) throws WxErrorException { - return servicerOp(openKfid, userIdList, SERVICER_DEL); + return servicerOp(openKfid, userIdList, null, SERVICER_DEL); } - private WxCpKfServicerOpResp servicerOp(String openKfid, List userIdList, String uri) throws WxErrorException { + @Override + public WxCpKfServicerOpResp delServicer(String openKfid, List userIdList, List departmentIdList) throws WxErrorException { + validateParameters(SERVICER_DEL, userIdList, departmentIdList); + return servicerOp(openKfid, userIdList, departmentIdList, SERVICER_DEL); + } + + private void validateParameters(String uri, List userIdList, List departmentIdList) { + if ((userIdList == null || userIdList.isEmpty()) && (departmentIdList == null || departmentIdList.isEmpty())) { + throw new IllegalArgumentException("userid_list和department_id_list至少需要填其中一个"); + } + if (SERVICER_DEL.equals(uri)) { + if (userIdList != null && userIdList.size() > 100) { + throw new IllegalArgumentException("可填充个数:0 ~ 100。超过100个需分批调用。"); + } + if (departmentIdList != null && departmentIdList.size() > 100) { + throw new IllegalArgumentException("可填充个数:0 ~ 100。超过100个需分批调用。"); + } + } else { + if (userIdList != null && userIdList.size() > 100) { + throw new IllegalArgumentException("可填充个数:0 ~ 100。超过100个需分批调用。"); + } + if (departmentIdList != null && departmentIdList.size() > 20) { + throw new IllegalArgumentException("可填充个数:0 ~ 20。"); + } + } + } + + private WxCpKfServicerOpResp servicerOp(String openKfid, List userIdList, List departmentIdList, String uri) throws WxErrorException { String url = cpService.getWxCpConfigStorage().getApiUrl(uri); JsonObject json = new JsonObject(); json.addProperty("open_kfid", openKfid); - JsonArray userIdArray = new JsonArray(); - userIdList.forEach(userIdArray::add); - json.add("userid_list", userIdArray); - + if (userIdList != null && !userIdList.isEmpty()) { + JsonArray userIdArray = new JsonArray(); + userIdList.forEach(userIdArray::add); + json.add("userid_list", userIdArray); + } + if (departmentIdList != null && !departmentIdList.isEmpty()) { + JsonArray departmentIdArray = new JsonArray(); + departmentIdList.forEach(departmentIdArray::add); + json.add("department_id_list", departmentIdArray); + } String responseContent = cpService.post(url, json.toString()); return WxCpKfServicerOpResp.fromJson(responseContent); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java index 7953d69e37..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,24 +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.json.GsonHelper; +import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.cp.api.WxCpMediaService; import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlReq; +import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlResult; +import org.apache.commons.io.IOUtils; import java.io.File; import java.io.IOException; 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; /** *
@@ -55,18 +68,35 @@ public WxMediaUploadResult upload(String mediaType, String filename, String url)
         , this.mainService.getWxCpConfigStorage().getApiUrl(MEDIA_UPLOAD + mediaType),
         new InputStreamData(inputStream, filename));
     } finally {
-      if (inputStream != null) {
-        try {
-          inputStream.close();
-        } catch (IOException e) {
-        }
-      }
+      IOUtils.closeQuietly(inputStream);
       if (conn != null) {
         conn.disconnect();
       }
     }
   }
 
+  @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()),
@@ -95,4 +125,20 @@ public String uploadImg(File file) throws WxErrorException {
     return this.mainService.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()), url, file)
       .getUrl();
   }
+
+  @Override
+  public String uploadByUrl(MediaUploadByUrlReq req) throws WxErrorException {
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPLOAD_BY_URL);
+    String responseContent = this.mainService.post(url, req.toJson());
+    return GsonHelper.getString(GsonParser.parse(responseContent), "jobid");
+  }
+
+  @Override
+  public MediaUploadByUrlResult uploadByUrl(String jobId) throws WxErrorException {
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_UPLOAD_BY_URL_RESULT);
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("jobid", jobId);
+    String post = this.mainService.post(url, jsonObject.toString());
+    return MediaUploadByUrlResult.fromJson(post);
+  }
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMeetingServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMeetingServiceImpl.java
index 3fc9d8218f..341bc97eab 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMeetingServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMeetingServiceImpl.java
@@ -11,7 +11,6 @@
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMenuServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMenuServiceImpl.java
index d008e77083..0920c2b3e0 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
@@ -27,7 +27,7 @@ public void create(WxMenu menu) throws WxErrorException {
   }
 
   @Override
-  public void create(Integer agentId, WxMenu menu) throws WxErrorException {
+  public void create(Long agentId, WxMenu menu) throws WxErrorException {
     String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(MENU_CREATE), agentId);
     this.mainService.post(url, menu.toJson());
   }
@@ -38,7 +38,7 @@ public void delete() throws WxErrorException {
   }
 
   @Override
-  public void delete(Integer agentId) throws WxErrorException {
+  public void delete(Long agentId) throws WxErrorException {
     String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(MENU_DELETE), agentId);
     this.mainService.get(url, null);
   }
@@ -49,7 +49,7 @@ public WxMenu get() throws WxErrorException {
   }
 
   @Override
-  public WxMenu get(Integer agentId) throws WxErrorException {
+  public WxMenu get(Long agentId) throws WxErrorException {
     String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(MENU_GET), agentId);
     try {
       String resultContent = this.mainService.get(url, null);
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
index 6daea8ef2f..def73e4152 100644
--- 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
@@ -21,7 +21,7 @@ public class WxCpMessageServiceImpl implements WxCpMessageService {
 
   @Override
   public WxCpMessageSendResult send(WxCpMessage message) throws WxErrorException {
-    Integer agentId = message.getAgentId();
+    Long agentId = message.getAgentId();
     if (null == agentId) {
       message.setAgentId(this.cpService.getWxCpConfigStorage().getAgentId());
     }
@@ -38,7 +38,7 @@ public WxCpMessageSendStatistics getStatistics(int timeType) throws WxErrorExcep
 
   @Override
   public WxCpLinkedCorpMessageSendResult sendLinkedCorpMessage(WxCpLinkedCorpMessage message) throws WxErrorException {
-    Integer agentId = message.getAgentId();
+    Long agentId = message.getAgentId();
     if (null == agentId) {
       message.setAgentId(this.cpService.getWxCpConfigStorage().getAgentId());
     }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java
index fc4f7cef64..be6588bc7b 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java
@@ -11,6 +11,7 @@
 import me.chanjar.weixin.cp.api.WxCpMsgAuditService;
 import me.chanjar.weixin.cp.api.WxCpService;
 import me.chanjar.weixin.cp.bean.msgaudit.*;
+import me.chanjar.weixin.cp.config.WxCpConfigStorage;
 import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 import org.apache.commons.lang3.StringUtils;
@@ -19,8 +20,11 @@
 import java.io.FileOutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
 import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.MsgAudit.*;
@@ -35,20 +39,76 @@
 public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService {
   private final WxCpService cpService;
 
+  /** 每个线程持有独立 SDK 实例,懒初始化,线程内跨调用复用 */
+  private final ThreadLocal threadLocalSdk = new ThreadLocal<>();
+
+  /** 跟踪所有已创建的 SDK,用于 closeAllSdks() 统一清理 */
+  private final Set managedSdks = ConcurrentHashMap.newKeySet();
+
   @Override
   public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, String passwd,
                                     @NonNull long timeout) throws Exception {
-    String configPath = cpService.getWxCpConfigStorage().getMsgAuditLibPath();
+    // 旧版 API:每次调用创建新 SDK,由调用方负责通过 Finance.DestroySdk(chatDatas.getSdk()) 释放
+    long sdk = this.createSdk();
+
+    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 long getOrInitThreadLocalSdk() throws WxErrorException {
+    Long sdk = threadLocalSdk.get();
+    if (sdk != null && sdk > 0) {
+      // 校验句柄是否仍受管理:closeAllSdks() 后其他线程 ThreadLocal 可能保留已销毁的 id
+      if (managedSdks.contains(sdk)) {
+        return sdk;
+      }
+      log.warn("线程 [{}] 发现已失效的会话存档SDK句柄 sdk={},请检查调用逻辑", Thread.currentThread().getName(), sdk);
+      threadLocalSdk.remove();
+      throw new WxErrorException("线程 [" + Thread.currentThread().getName() + "] 获取会话存档SDK失败,请检查是否已调用 closeAllSdks()");
+    }
+    long newSdk = createSdk();
+    threadLocalSdk.set(newSdk);
+    managedSdks.add(newSdk);
+    log.info("线程 [{}] 初始化会话存档SDK成功,sdk={}", Thread.currentThread().getName(), newSdk);
+    return newSdk;
+  }
+
+  /**
+   * 创建并初始化一个新的会话存档 SDK 实例。
+   * 

通常通过 {@link #getOrInitThreadLocalSdk()} 间接调用以复用 ThreadLocal 中的实例; + * 旧版直接暴露 sdk 的 API(如 {@link #getChatDatas})也会直接调用本方法,此时 SDK 由调用方自行管理。

+ *

Finance.loadingLibraries() 底层依赖 System.load(),JVM 保证同一库不重复加载,多线程并发调用安全。

+ */ + private long createSdk() throws WxErrorException { + WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage(); + + String configPath = configStorage.getMsgAuditLibPath(); if (StringUtils.isEmpty(configPath)) { throw new WxErrorException("请配置会话存档sdk文件的路径,不要配错了!!"); } - /** - * 完整的文件库路径: - * - * /www/osfile/libcrypto-1_1-x64.dll,libssl-1_1-x64.dll,libcurl-x64.dll,WeWorkFinanceSdk.dll, - * libWeWorkFinanceSdk_Java.so - */ // 替换斜杠 String replacePath = configPath.replace("\\", "/"); // 获取最后一个斜杠的下标,用作分割路径 @@ -66,9 +126,9 @@ public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, S List libList = Arrays.asList(libFiles); // 判断windows系统会话存档sdk中dll的加载,因为会互相依赖所以是有顺序的,否则会导致无法加载sdk #2598 - List osLib = new LinkedList(); - List fileLib = new ArrayList(); - libList.stream().forEach(s -> { + List osLib = new LinkedList<>(); + List fileLib = new ArrayList<>(); + libList.forEach(s -> { if (s.contains("lib")) { osLib.add(s); } else { @@ -79,36 +139,41 @@ public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, S Finance.loadingLibraries(osLib, prefixPath); long sdk = Finance.NewSdk(); - //因为会话存档单独有个secret,优先使用会话存档的secret - String msgAuditSecret = cpService.getWxCpConfigStorage().getMsgAuditSecret(); + // 因为会话存档单独有个secret,优先使用会话存档的secret + String msgAuditSecret = configStorage.getMsgAuditSecret(); if (StringUtils.isEmpty(msgAuditSecret)) { - msgAuditSecret = cpService.getWxCpConfigStorage().getCorpSecret(); + msgAuditSecret = configStorage.getCorpSecret(); } - long ret = Finance.Init(sdk, cpService.getWxCpConfigStorage().getCorpId(), msgAuditSecret); + long ret = Finance.Init(sdk, configStorage.getCorpId(), msgAuditSecret); if (ret != 0) { Finance.DestroySdk(sdk); throw new WxErrorException("init sdk err ret " + ret); } + return sdk; + } - long slice = Finance.NewSlice(); - ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice); - if (ret != 0) { - Finance.FreeSlice(slice); + @Override + public void closeThreadLocalSdk() { + Long sdk = threadLocalSdk.get(); + // 先从 managedSdks 摘除,摘除成功才调 DestroySdk,防止与 closeAllSdks() 并发时 double-free + if (sdk != null && managedSdks.remove(sdk)) { Finance.DestroySdk(sdk); - throw new WxErrorException("getchatdata err ret " + ret); + log.info("线程 [{}] 关闭会话存档SDK,sdk={}", Thread.currentThread().getName(), sdk); } + threadLocalSdk.remove(); + } - // 拉取会话存档 - String content = Finance.GetContentFromSlice(slice); - Finance.FreeSlice(slice); - WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content); - if (chatDatas.getErrCode().intValue() != 0) { - Finance.DestroySdk(sdk); - throw new WxErrorException(chatDatas.toJson()); + @Override + public void closeAllSdks() { + // 逐一 remove 后再 Destroy,防止与 closeThreadLocalSdk() 并发时 double-free + Long[] sdks = managedSdks.toArray(new Long[0]); + for (Long sdk : sdks) { + if (managedSdks.remove(sdk)) { + Finance.DestroySdk(sdk); + log.info("关闭会话存档SDK,sdk={}", sdk); + } } - - chatDatas.setSdk(sdk); - return chatDatas; + threadLocalSdk.remove(); } @Override @@ -128,36 +193,27 @@ public WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.Wx * @throws Exception the exception */ public String decryptChatData(long sdk, WxCpChatDatas.WxCpChatData chatData, Integer pkcs1) throws Exception { - /** - * 企业获取的会话内容,使用企业自行配置的消息加密公钥进行加密,企业可用自行保存的私钥解开会话内容数据。 - * msgAuditPriKey 会话存档私钥不能为空 - */ + // 企业获取的会话内容,使用企业自行配置的消息加密公钥进行加密,企业可用自行保存的私钥解开会话内容数据。 + // msgAuditPriKey 会话存档私钥不能为空 String priKey = cpService.getWxCpConfigStorage().getMsgAuditPriKey(); if (StringUtils.isEmpty(priKey)) { throw new WxErrorException("请配置会话存档私钥【msgAuditPriKey】"); } String decryptByPriKey = WxCpCryptUtil.decryptPriKey(chatData.getEncryptRandomKey(), priKey, pkcs1); - /** - * 每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。 - */ + // 每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。 long msg = Finance.NewSlice(); - /** - * 解密会话存档内容 - * sdk不会要求用户传入rsa私钥,保证用户会话存档数据只有自己能够解密。 - * 此处需要用户先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk来解密encrypt_chat_msg获取会话存档明文。 - */ + // 解密会话存档内容 + // sdk不会要求用户传入rsa私钥,保证用户会话存档数据只有自己能够解密。 + // 此处需要用户先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk来解密encrypt_chat_msg获取会话存档明文。 int ret = Finance.DecryptData(sdk, decryptByPriKey, chatData.getEncryptChatMsg(), msg); if (ret != 0) { Finance.FreeSlice(msg); - Finance.DestroySdk(sdk); throw new WxErrorException("msg err ret " + ret); } - /** - * 明文 - */ + // 明文 String plainText = Finance.GetContentFromSlice(msg); Finance.FreeSlice(msg); return plainText; @@ -179,29 +235,29 @@ public void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String pr * 为空字符串,拉取后续分片时直接填入上次返回的indexbuf即可。 */ File targetFile = new File(targetFilePath); - if (!targetFile.getParentFile().exists()) { - targetFile.getParentFile().mkdirs(); + File parentDir = targetFile.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + parentDir.mkdirs(); } this.getMediaFile(sdk, sdkfileid, proxy, passwd, timeout, i -> { try { // 大于512k的文件会分片拉取,此处需要使用追加写,避免后面的分片覆盖之前的数据。 - FileOutputStream outputStream = new FileOutputStream(targetFile, true); - outputStream.write(i); - outputStream.close(); + try (FileOutputStream outputStream = new FileOutputStream(targetFile, true)) { + outputStream.write(i); + } } catch (Exception e) { - e.printStackTrace(); + log.error("写入媒体文件分片失败,targetFilePath={}", targetFilePath, e); } }); } @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即可。 - */ + /** + * 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); @@ -210,19 +266,17 @@ public void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String pr ret = Finance.GetMediaData(sdk, indexbuf, sdkfileid, proxy, passwd, timeout, mediaData); if (ret != 0) { Finance.FreeMediaData(mediaData); - Finance.DestroySdk(sdk); throw new WxErrorException("getmediadata err ret " + ret); } data_len += Finance.GetDataLen(mediaData); - log.info("正在分片拉取媒体文件 len:{}, data_len:{}, is_finis:{} \n", Finance.GetIndexLen(mediaData), data_len, - Finance.IsMediaDataFinish(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(); + log.error("处理媒体文件分片失败,sdkfileid={}", sdkfileid, e); } if (Finance.IsMediaDataFinish(mediaData) == 1) { @@ -244,7 +298,7 @@ public List getPermitUserList(Integer type) throws WxErrorException { if (type != null) { jsonObject.addProperty("type", type); } - String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + String responseContent = this.cpService.postForMsgAudit(apiUrl, jsonObject.toString()); return WxCpGsonBuilder.create().fromJson(GsonParser.parse(responseContent).getAsJsonArray("ids"), new TypeToken>() { }.getType()); @@ -255,15 +309,106 @@ public WxCpGroupChat getGroupChat(@NonNull String roomid) throws WxErrorExceptio 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()); + String responseContent = this.cpService.postForMsgAudit(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()); + String responseContent = this.cpService.postForMsgAudit(apiUrl, checkAgreeRequest.toJson()); return WxCpAgreeInfo.fromJson(responseContent); } + @Override + public List getChatRecords(long seq, @NonNull long limit, String proxy, String passwd, + @NonNull long timeout) throws Exception { + long sdk = this.getOrInitThreadLocalSdk(); + + long slice = Finance.NewSlice(); + long ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice); + if (ret != 0) { + Finance.FreeSlice(slice); + throw new WxErrorException("getchatdata err ret " + ret); + } + + // 拉取会话存档 + String content = Finance.GetContentFromSlice(slice); + Finance.FreeSlice(slice); + WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content); + if (chatDatas.getErrCode().intValue() != 0) { + throw new WxErrorException(chatDatas.toJson()); + } + + List chatDataList = chatDatas.getChatData(); + return chatDataList != null ? chatDataList : Collections.emptyList(); + } + + @Override + public WxCpChatModel getDecryptChatData(@NonNull WxCpChatDatas.WxCpChatData chatData, + @NonNull Integer pkcs1) throws Exception { + long sdk = this.getOrInitThreadLocalSdk(); + String plainText = this.decryptChatData(sdk, chatData, pkcs1); + return WxCpChatModel.fromJson(plainText); + } + + @Override + public String getChatRecordPlainText(@NonNull WxCpChatDatas.WxCpChatData chatData, + @NonNull Integer pkcs1) throws Exception { + long sdk = this.getOrInitThreadLocalSdk(); + return this.decryptChatData(sdk, chatData, pkcs1); + } + + @Override + public void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, + @NonNull String targetFilePath) throws WxErrorException { + long sdk; + try { + sdk = this.getOrInitThreadLocalSdk(); + } catch (Exception e) { + throw new WxErrorException(e); + } + + // 使用AtomicReference捕获Lambda中的异常,以便在执行完后抛出 + final java.util.concurrent.atomic.AtomicReference exceptionHolder = new java.util.concurrent.atomic.AtomicReference<>(); + + File targetFile = new File(targetFilePath); + File parentDir = targetFile.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + parentDir.mkdirs(); + } + this.getMediaFile(sdk, sdkfileid, proxy, passwd, timeout, i -> { + // 如果之前已经发生异常,不再继续处理 + if (exceptionHolder.get() != null) { + return; + } + try { + // 大于512k的文件会分片拉取,此处需要使用追加写,避免后面的分片覆盖之前的数据。 + try (FileOutputStream outputStream = new FileOutputStream(targetFile, true)) { + outputStream.write(i); + } + } catch (Exception e) { + exceptionHolder.set(e); + } + }); + + // 检查是否发生异常,如果有则抛出 + Exception caughtException = exceptionHolder.get(); + if (caughtException != null) { + throw new WxErrorException(caughtException); + } + } + + @Override + public void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, + @NonNull Consumer action) throws WxErrorException { + long sdk; + try { + sdk = this.getOrInitThreadLocalSdk(); + } catch (Exception e) { + throw new WxErrorException(e); + } + this.getMediaFile(sdk, sdkfileid, proxy, passwd, timeout, action); + } + } 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 f5bd61ba97..9b390b5ec8 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 @@ -10,8 +10,11 @@ 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.bean.workbench.WxCpSecondVerificationInfo; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; +import java.util.Optional; + import static me.chanjar.weixin.common.api.WxConsts.OAuth2Scope.*; import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.OAuth2.*; @@ -66,16 +69,16 @@ public WxCpOauth2UserInfo getUserInfo(String code) throws WxErrorException { } @Override - public WxCpOauth2UserInfo getUserInfo(Integer agentId, String code) throws WxErrorException { + public WxCpOauth2UserInfo getUserInfo(Long agentId, String code) throws WxErrorException { 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")) @@ -106,4 +109,25 @@ public WxCpUserDetail getUserDetail(String userTicket) throws WxErrorException { 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/WxCpOaMeetingRoomServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaMeetingRoomServiceImpl.java index f486028a0a..9c32a45235 100644 --- 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 @@ -7,7 +7,7 @@ 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.WxCpOaMeetingRoom; +import me.chanjar.weixin.cp.bean.oa.meetingroom.*; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; import java.util.List; @@ -48,4 +48,40 @@ 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/WxCpOaServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImpl.java index 43df95f300..59cde79a93 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImpl.java @@ -140,7 +140,7 @@ public WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date e if (filters != null && !filters.isEmpty()) { JsonArray filterJsonArray = new JsonArray(); for (WxCpApprovalInfoQueryFilter filter : filters) { - filterJsonArray.add(new JsonParser().parse(filter.toJson())); + filterJsonArray.add(JsonParser.parseString(filter.toJson())); } jsonObject.add("filters", filterJsonArray); } @@ -153,7 +153,43 @@ public WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date e @Override public WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime) throws WxErrorException { - return this.getApprovalInfo(startTime, endTime, null, null, null); + 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); + 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 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..7f5bf004db --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDocServiceImpl.java @@ -0,0 +1,336 @@ +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.bean.CommonUploadParam; +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 java.io.File; +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 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 { + return docShare(WxCpDocShareRequest.builder().docId(docId).build()); + } + + @Override + public WxCpDocShare docShare(@NonNull WxCpDocShareRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_DOC_SHARE); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocShare.fromJson(responseContent); + } + + @Override + public WxCpDocAuthInfo docGetAuth(@NonNull String docId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_DOC_GET_AUTH); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("docid", docId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpDocAuthInfo.fromJson(responseContent); + } + + @Override + public WxCpBaseResp docModifyJoinRule(@NonNull WxCpDocModifyJoinRuleRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_MOD_DOC_JOIN_RULE); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp docModifyMember(@NonNull WxCpDocModifyMemberRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_MOD_DOC_MEMBER); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp docModifySafetySetting( + @NonNull WxCpDocModifySafetySettingRequest request + ) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage() + .getApiUrl(WEDOC_MOD_DOC_SAFETY_SETTING); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.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); + } + + @Override + public WxCpDocData docGetData(@NonNull WxCpDocGetDataRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_GET_DOC_DATA); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocData.fromJson(responseContent); + } + + @Override + public WxCpBaseResp docModify(@NonNull WxCpDocModifyRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_MOD_DOC); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpDocImageUploadResult docUploadImage(@NonNull File file) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_UPLOAD_DOC_IMAGE); + String responseContent = this.cpService.upload(apiUrl, CommonUploadParam.fromFile("media", file)); + return WxCpDocImageUploadResult.fromJson(responseContent); + } + + @Override + public WxCpBaseResp docAddAdmin(@NonNull WxCpDocAdminRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_ADD_ADMIN); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp docDeleteAdmin(@NonNull WxCpDocAdminRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_DEL_ADMIN); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpDocAdminListResult docGetAdminList(@NonNull String docId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_GET_ADMIN_LIST); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("docid", docId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpDocAdminListResult.fromJson(responseContent); + } + + @Override + public WxCpDocSmartSheetAuth smartSheetGetAuth(@NonNull WxCpDocSmartSheetAuthRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_GET_SHEET_AUTH); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocSmartSheetAuth.fromJson(responseContent); + } + + @Override + public WxCpBaseResp smartSheetModifyAuth(@NonNull WxCpDocSmartSheetModifyAuthRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_MOD_SHEET_AUTH); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpDocSmartSheetResult smartSheetGetSheet(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_GET_SHEET); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocSmartSheetResult.fromJson(responseContent); + } + + @Override + public WxCpDocSmartSheetResult smartSheetAddSheet(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_ADD_SHEET); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocSmartSheetResult.fromJson(responseContent); + } + + @Override + public WxCpBaseResp smartSheetDeleteSheet(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_DELETE_SHEET); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp smartSheetUpdateSheet(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_UPDATE_SHEET); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpDocSmartSheetResult smartSheetGetViews(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_GET_VIEWS); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocSmartSheetResult.fromJson(responseContent); + } + + @Override + public WxCpDocSmartSheetResult smartSheetAddView(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_ADD_VIEW); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocSmartSheetResult.fromJson(responseContent); + } + + @Override + public WxCpBaseResp smartSheetDeleteViews(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_DELETE_VIEWS); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp smartSheetUpdateView(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_UPDATE_VIEW); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpDocSmartSheetResult smartSheetGetFields(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_GET_FIELDS); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocSmartSheetResult.fromJson(responseContent); + } + + @Override + public WxCpDocSmartSheetResult smartSheetAddFields(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_ADD_FIELDS); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocSmartSheetResult.fromJson(responseContent); + } + + @Override + public WxCpBaseResp smartSheetDeleteFields(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_DELETE_FIELDS); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp smartSheetUpdateFields(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_UPDATE_FIELDS); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpDocSmartSheetResult smartSheetGetRecords(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_GET_RECORDS); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocSmartSheetResult.fromJson(responseContent); + } + + @Override + public WxCpDocSmartSheetResult smartSheetAddRecords(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_ADD_RECORDS); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpDocSmartSheetResult.fromJson(responseContent); + } + + @Override + public WxCpBaseResp smartSheetDeleteRecords(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_DELETE_RECORDS); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpBaseResp smartSheetUpdateRecords(@NonNull WxCpDocSmartSheetRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_SMARTSHEET_UPDATE_RECORDS); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpFormCreateResult formCreate(@NonNull WxCpFormCreateRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_CREATE_FORM); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpFormCreateResult.fromJson(responseContent); + } + + @Override + public WxCpBaseResp formModify(@NonNull WxCpFormModifyRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_MODIFY_FORM); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpBaseResp.fromJson(responseContent); + } + + @Override + public WxCpFormInfoResult formInfo(@NonNull String formId) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_GET_FORM_INFO); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("formid", formId); + String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); + return WxCpFormInfoResult.fromJson(responseContent); + } + + @Override + public WxCpFormStatisticResult formStatistic(@NonNull List requests) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_GET_FORM_STATISTIC); + String responseContent = this.cpService.post(apiUrl, WxCpFormStatisticRequest.toJson(requests)); + return WxCpFormStatisticResult.fromJson(responseContent); + } + + @Override + public WxCpFormAnswer formAnswer(@NonNull WxCpFormAnswerRequest request) throws WxErrorException { + String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(WEDOC_GET_FORM_ANSWER); + String responseContent = this.cpService.post(apiUrl, request.toJson()); + return WxCpFormAnswer.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 index 979e86e55f..a41195ae84 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDriveServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaWeDriveServiceImpl.java @@ -39,20 +39,18 @@ public WxCpBaseResp spaceRename(@NonNull WxCpSpaceRenameRequest request) throws } @Override - public WxCpBaseResp spaceDismiss(@NonNull String userId, @NonNull String spaceId) throws WxErrorException { + public WxCpBaseResp spaceDismiss(@NonNull String spaceId) throws WxErrorException { String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_DISMISS); JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("userid", userId); jsonObject.addProperty("spaceid", spaceId); String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); return WxCpBaseResp.fromJson(responseContent); } @Override - public WxCpSpaceInfo spaceInfo(@NonNull String userId, @NonNull String spaceId) throws WxErrorException { + public WxCpSpaceInfo spaceInfo(@NonNull String spaceId) throws WxErrorException { String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_INFO); JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("userid", userId); jsonObject.addProperty("spaceid", spaceId); String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); return WxCpSpaceInfo.fromJson(responseContent); @@ -80,10 +78,9 @@ public WxCpBaseResp spaceSetting(@NonNull WxCpSpaceSettingRequest request) throw } @Override - public WxCpSpaceShare spaceShare(@NonNull String userId, @NonNull String spaceId) throws WxErrorException { + public WxCpSpaceShare spaceShare(@NonNull String spaceId) throws WxErrorException { String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(SPACE_SHARE); JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("userid", userId); jsonObject.addProperty("spaceid", spaceId); String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); return WxCpSpaceShare.fromJson(responseContent); @@ -114,10 +111,9 @@ public WxCpFileDownload fileDownload(@NonNull String userId, @NonNull String fil } @Override - public WxCpFileRename fileRename(@NonNull String userId, @NonNull String fileId, @NonNull String newName) throws WxErrorException { + 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("userid", userId); jsonObject.addProperty("fileid", fileId); jsonObject.addProperty("new_name", newName); String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); @@ -125,11 +121,10 @@ public WxCpFileRename fileRename(@NonNull String userId, @NonNull String fileId, } @Override - public WxCpFileCreate fileCreate(@NonNull String userId, @NonNull String spaceId, @NonNull String fatherId, + 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("userid", userId); jsonObject.addProperty("spaceid", spaceId); jsonObject.addProperty("fatherid", fatherId); jsonObject.addProperty("file_type", fileType); @@ -146,9 +141,9 @@ public WxCpFileMove fileMove(@NonNull WxCpFileMoveRequest request) throws WxErro } @Override - public WxCpBaseResp fileDelete(@NonNull String userId, @NonNull List fileId) throws WxErrorException { + public WxCpBaseResp fileDelete(@NonNull List fileIds) throws WxErrorException { String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_DELETE); - WxCpFileDeleteRequest request = new WxCpFileDeleteRequest(userId, fileId); + WxCpFileDeleteRequest request = new WxCpFileDeleteRequest(fileIds); String responseContent = this.cpService.post(apiUrl, request.toJson()); return WxCpBaseResp.fromJson(responseContent); } @@ -168,11 +163,9 @@ public WxCpBaseResp fileAclDel(@NonNull WxCpFileAclDelRequest request) throws Wx } @Override - public WxCpBaseResp fileSetting(@NonNull String userId, @NonNull String fileId, @NonNull Integer authScope, - Integer auth) throws WxErrorException { + public WxCpBaseResp fileSetting(@NonNull String fileId, @NonNull Integer authScope, Integer auth) throws WxErrorException { String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_SETTING); JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("userid", userId); jsonObject.addProperty("fileid", fileId); jsonObject.addProperty("auth_scope", authScope); if (auth != null) { @@ -183,20 +176,18 @@ public WxCpBaseResp fileSetting(@NonNull String userId, @NonNull String fileId, } @Override - public WxCpFileShare fileShare(@NonNull String userId, @NonNull String fileId) throws WxErrorException { + public WxCpFileShare fileShare(@NonNull String fileId) throws WxErrorException { String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_SHARE); JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("userid", userId); jsonObject.addProperty("fileid", fileId); String responseContent = this.cpService.post(apiUrl, jsonObject.toString()); return WxCpFileShare.fromJson(responseContent); } @Override - public WxCpFileInfo fileInfo(@NonNull String userId, @NonNull String fileId) throws WxErrorException { + public WxCpFileInfo fileInfo(@NonNull String fileId) throws WxErrorException { String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(FILE_INFO); JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("userid", userId); 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/WxCpSchoolUserServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolUserServiceImpl.java index fac1689e08..bdb067f923 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolUserServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpSchoolUserServiceImpl.java @@ -16,6 +16,7 @@ import org.apache.commons.lang3.StringUtils; import java.util.List; +import java.util.Objects; import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.ExternalContact.*; import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.School.*; @@ -98,7 +99,7 @@ public WxCpBaseResp updateStudent(@NonNull String studentUserId, String newStude if (StringUtils.isNotEmpty(name)) { jsonObject.addProperty("name", name); } - if (departments != null && departments.size() > 0) { + if (departments != null && !departments.isEmpty()) { JsonArray jsonArray = new JsonArray(); for (Integer depart : departments) { jsonArray.add(new JsonPrimitive(depart)); @@ -246,7 +247,7 @@ public String convertToOpenId(@NonNull String externalUserId) throws WxErrorExce @Override public WxCpDepartmentList listDepartment(Integer id) throws WxErrorException { - String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST) + id; + String apiUrl = Objects.isNull(id) ? this.cpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST) : String.format("%s?id=%s", this.cpService.getWxCpConfigStorage().getApiUrl(DEPARTMENT_LIST), id); String responseContent = this.cpService.get(apiUrl, null); return WxCpDepartmentList.fromJson(responseContent); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java index 7e69152a17..8285e59df2 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,24 +1,23 @@ package me.chanjar.weixin.cp.api.impl; - import me.chanjar.weixin.common.bean.WxAccessToken; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.error.WxRuntimeException; -import me.chanjar.weixin.common.util.http.HttpType; +import me.chanjar.weixin.common.util.http.HttpClientType; +import me.chanjar.weixin.common.util.http.apache.ApacheBasicResponseHandler; import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; import me.chanjar.weixin.cp.config.WxCpConfigStorage; import me.chanjar.weixin.cp.constant.WxCpApiPathConsts; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.CloseableHttpClient; import java.io.IOException; +import java.util.concurrent.locks.Lock; /** * The type Wx cp service apache http client. @@ -40,8 +39,8 @@ public HttpHost getRequestHttpProxy() { } @Override - public HttpType getRequestType() { - return HttpType.APACHE_HTTP; + public HttpClientType getRequestType() { + return HttpClientType.APACHE_HTTP; } @Override @@ -61,13 +60,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); @@ -82,6 +75,96 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { return this.configStorage.getAccessToken(); } + @Override + public String getContactAccessToken(boolean forceRefresh) throws WxErrorException { + if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getContactAccessToken(); + } + + Lock lock = this.configStorage.getContactAccessTokenLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getContactAccessToken(); + } + // 使用通讯录同步secret获取access_token + String contactSecret = this.configStorage.getContactSecret(); + if (contactSecret == null || contactSecret.trim().isEmpty()) { + throw new WxErrorException("通讯录同步secret未配置"); + } + String url = String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN), + this.configStorage.getCorpId(), contactSecret); + + 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, ApacheBasicResponseHandler.INSTANCE); + WxError error = WxError.fromJson(resultContent, WxType.CP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + + WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); + this.configStorage.updateContactAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } catch (IOException e) { + throw new WxRuntimeException(e); + } + } finally { + lock.unlock(); + } + return this.configStorage.getContactAccessToken(); + } + + @Override + public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException { + if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getMsgAuditAccessToken(); + } + + Lock lock = this.configStorage.getMsgAuditAccessTokenLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getMsgAuditAccessToken(); + } + // 使用会话存档secret获取access_token + String msgAuditSecret = this.configStorage.getMsgAuditSecret(); + if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) { + throw new WxErrorException("会话存档secret未配置"); + } + String url = String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN), + this.configStorage.getCorpId(), msgAuditSecret); + + 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, ApacheBasicResponseHandler.INSTANCE); + WxError error = WxError.fromJson(resultContent, WxType.CP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + + WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); + this.configStorage.updateMsgAuditAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } catch (IOException e) { + throw new WxRuntimeException(e); + } + } finally { + lock.unlock(); + } + return this.configStorage.getMsgAuditAccessToken(); + } + @Override public void initHttp() { ApacheHttpClientBuilder apacheHttpClientBuilder = this.configStorage 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..129823df5a --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java @@ -0,0 +1,191 @@ +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; +import java.util.concurrent.locks.Lock; + +/** + * 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 String getContactAccessToken(boolean forceRefresh) throws WxErrorException { + if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getContactAccessToken(); + } + + Lock lock = this.configStorage.getContactAccessTokenLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getContactAccessToken(); + } + // 使用通讯录同步secret获取access_token + String contactSecret = this.configStorage.getContactSecret(); + if (contactSecret == null || contactSecret.trim().isEmpty()) { + throw new WxErrorException("通讯录同步secret未配置"); + } + String url = String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN), + this.configStorage.getCorpId(), contactSecret); + + 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.updateContactAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } catch (IOException e) { + throw new WxRuntimeException(e); + } + } finally { + lock.unlock(); + } + return this.configStorage.getContactAccessToken(); + } + + @Override + public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException { + if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getMsgAuditAccessToken(); + } + + Lock lock = this.configStorage.getMsgAuditAccessTokenLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getMsgAuditAccessToken(); + } + // 使用会话存档secret获取access_token + String msgAuditSecret = this.configStorage.getMsgAuditSecret(); + if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) { + throw new WxErrorException("会话存档secret未配置"); + } + String url = String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN), + this.configStorage.getCorpId(), msgAuditSecret); + + 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.updateMsgAuditAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } catch (IOException e) { + throw new WxRuntimeException(e); + } + } finally { + lock.unlock(); + } + return this.configStorage.getMsgAuditAccessToken(); + } + + @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 661a0ed79f..69cc074be9 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java @@ -6,14 +6,12 @@ import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.error.WxRuntimeException; +import me.chanjar.weixin.common.util.http.apache.ApacheBasicResponseHandler; import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.cp.config.WxCpConfigStorage; import me.chanjar.weixin.cp.constant.WxCpApiPathConsts; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.BasicResponseHandler; -import org.apache.http.impl.client.CloseableHttpClient; import java.io.IOException; import java.util.concurrent.locks.Lock; @@ -55,13 +53,7 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { RequestConfig config = RequestConfig.custom().setProxy(getRequestHttpProxy()).build(); httpGet.setConfig(config); } - String resultContent; - try (CloseableHttpClient httpClient = getRequestHttpClient(); - CloseableHttpResponse response = httpClient.execute(httpGet)) { - resultContent = new BasicResponseHandler().handleResponse(response); - } finally { - httpGet.releaseConnection(); - } + String resultContent = getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE); WxError error = WxError.fromJson(resultContent, WxType.CP); if (error.getErrorCode() != 0) { throw new WxErrorException(error); @@ -78,6 +70,92 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { return configStorage.getAccessToken(); } + @Override + public String getContactAccessToken(boolean forceRefresh) throws WxErrorException { + final WxCpConfigStorage configStorage = getWxCpConfigStorage(); + if (!configStorage.isContactAccessTokenExpired() && !forceRefresh) { + return configStorage.getContactAccessToken(); + } + Lock lock = configStorage.getContactAccessTokenLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (!configStorage.isContactAccessTokenExpired() && !forceRefresh) { + return configStorage.getContactAccessToken(); + } + // 使用通讯录同步secret获取access_token + String contactSecret = configStorage.getContactSecret(); + if (contactSecret == null || contactSecret.trim().isEmpty()) { + throw new WxErrorException("通讯录同步secret未配置"); + } + String url = String.format(configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN), + this.configStorage.getCorpId(), contactSecret); + 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.updateContactAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } catch (IOException e) { + throw new WxRuntimeException(e); + } + } finally { + lock.unlock(); + } + return configStorage.getContactAccessToken(); + } + + @Override + public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException { + final WxCpConfigStorage configStorage = getWxCpConfigStorage(); + if (!configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) { + return configStorage.getMsgAuditAccessToken(); + } + Lock lock = configStorage.getMsgAuditAccessTokenLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (!configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) { + return configStorage.getMsgAuditAccessToken(); + } + // 使用会话存档secret获取access_token + String msgAuditSecret = configStorage.getMsgAuditSecret(); + if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) { + throw new WxErrorException("会话存档secret未配置"); + } + String url = String.format(configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN), + this.configStorage.getCorpId(), msgAuditSecret); + 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.updateMsgAuditAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } catch (IOException e) { + throw new WxRuntimeException(e); + } + } finally { + lock.unlock(); + } + return configStorage.getMsgAuditAccessToken(); + } + @Override public String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException { final WxCpConfigStorage configStorage = getWxCpConfigStorage(); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java index ec8a3624ac..ef6d86c665 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java @@ -9,10 +9,12 @@ 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; +import java.util.concurrent.locks.Lock; + /** * The type Wx cp service jodd http. * @@ -33,8 +35,8 @@ public ProxyInfo getRequestHttpProxy() { } @Override - public HttpType getRequestType() { - return HttpType.JODD_HTTP; + public HttpClientType getRequestType() { + return HttpClientType.JODD_HTTP; } @Override @@ -63,6 +65,84 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { return this.configStorage.getAccessToken(); } + @Override + public String getContactAccessToken(boolean forceRefresh) throws WxErrorException { + if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getContactAccessToken(); + } + + Lock lock = this.configStorage.getContactAccessTokenLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getContactAccessToken(); + } + // 使用通讯录同步secret获取access_token + String contactSecret = this.configStorage.getContactSecret(); + if (contactSecret == null || contactSecret.trim().isEmpty()) { + throw new WxErrorException("通讯录同步secret未配置"); + } + HttpRequest request = HttpRequest.get(String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN), + this.configStorage.getCorpId(), contactSecret)); + if (this.httpProxy != null) { + httpClient.useProxy(this.httpProxy); + } + request.withConnectionProvider(httpClient); + HttpResponse response = request.send(); + + String resultContent = response.bodyText(); + WxError error = WxError.fromJson(resultContent, WxType.CP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); + this.configStorage.updateContactAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } finally { + lock.unlock(); + } + return this.configStorage.getContactAccessToken(); + } + + @Override + public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException { + if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getMsgAuditAccessToken(); + } + + Lock lock = this.configStorage.getMsgAuditAccessTokenLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getMsgAuditAccessToken(); + } + // 使用会话存档secret获取access_token + String msgAuditSecret = this.configStorage.getMsgAuditSecret(); + if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) { + throw new WxErrorException("会话存档secret未配置"); + } + HttpRequest request = HttpRequest.get(String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN), + this.configStorage.getCorpId(), msgAuditSecret)); + if (this.httpProxy != null) { + httpClient.useProxy(this.httpProxy); + } + request.withConnectionProvider(httpClient); + HttpResponse response = request.send(); + + String resultContent = response.bodyText(); + WxError error = WxError.fromJson(resultContent, WxType.CP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); + this.configStorage.updateMsgAuditAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } finally { + lock.unlock(); + } + return this.configStorage.getMsgAuditAccessToken(); + } + @Override public void initHttp() { if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java index 73b933f646..76847f1f93 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java @@ -5,13 +5,14 @@ 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.*; import java.io.IOException; +import java.util.concurrent.locks.Lock; import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_TOKEN; @@ -36,8 +37,8 @@ public OkHttpProxyInfo getRequestHttpProxy() { } @Override - public HttpType getRequestType() { - return HttpType.OK_HTTP; + public HttpClientType getRequestType() { + return HttpClientType.OK_HTTP; } @Override @@ -55,12 +56,15 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { this.configStorage.getCorpSecret())) .get() .build(); - String resultContent = null; - try { - Response response = client.newCall(request).execute(); + String resultContent; + try (Response response = client.newCall(request).execute()) { + if (response.body() == null) { + throw new WxErrorException("请求access token失败:响应内容为空"); + } resultContent = response.body().string(); } catch (IOException e) { log.error(e.getMessage(), e); + throw new WxErrorException(e); } WxError error = WxError.fromJson(resultContent, WxType.CP); @@ -74,6 +78,105 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { return this.configStorage.getAccessToken(); } + @Override + public String getContactAccessToken(boolean forceRefresh) throws WxErrorException { + if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getContactAccessToken(); + } + + Lock lock = this.configStorage.getContactAccessTokenLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (!this.configStorage.isContactAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getContactAccessToken(); + } + // 使用通讯录同步secret获取access_token + String contactSecret = this.configStorage.getContactSecret(); + if (contactSecret == null || contactSecret.trim().isEmpty()) { + throw new WxErrorException("通讯录同步secret未配置"); + } + //得到httpClient + OkHttpClient client = getRequestHttpClient(); + //请求的request + Request request = new Request.Builder() + .url(String.format(this.configStorage.getApiUrl(GET_TOKEN), this.configStorage.getCorpId(), + contactSecret)) + .get() + .build(); + String resultContent; + try (Response response = client.newCall(request).execute()) { + if (response.body() == null) { + throw new WxErrorException("请求通讯录同步access token失败:响应内容为空"); + } + resultContent = response.body().string(); + } catch (IOException e) { + log.error(e.getMessage(), e); + throw new WxErrorException(e); + } + WxError error = WxError.fromJson(resultContent, WxType.CP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); + this.configStorage.updateContactAccessToken(accessToken.getAccessToken(), + accessToken.getExpiresIn()); + } finally { + lock.unlock(); + } + return this.configStorage.getContactAccessToken(); + } + + @Override + public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException { + if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getMsgAuditAccessToken(); + } + + Lock lock = this.configStorage.getMsgAuditAccessTokenLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getMsgAuditAccessToken(); + } + // 使用会话存档secret获取access_token + String msgAuditSecret = this.configStorage.getMsgAuditSecret(); + if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) { + throw new WxErrorException("会话存档secret未配置"); + } + //得到httpClient + OkHttpClient client = getRequestHttpClient(); + //请求的request + Request request = new Request.Builder() + .url(String.format(this.configStorage.getApiUrl(GET_TOKEN), this.configStorage.getCorpId(), + msgAuditSecret)) + .get() + .build(); + String resultContent; + try (Response response = client.newCall(request).execute()) { + if (response.body() == null) { + throw new WxErrorException("请求会话存档access token失败:响应内容为空"); + } + resultContent = response.body().string(); + } catch (IOException e) { + log.error(e.getMessage(), e); + throw new WxErrorException(e); + } + + WxError error = WxError.fromJson(resultContent, WxType.CP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); + this.configStorage.updateMsgAuditAccessToken(accessToken.getAccessToken(), + accessToken.getExpiresIn()); + } finally { + lock.unlock(); + } + return this.configStorage.getMsgAuditAccessToken(); + } + @Override public void initHttp() { log.debug("WxCpServiceOkHttpImpl initHttp"); @@ -86,12 +189,12 @@ public void initHttp() { OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); clientBuilder.proxy(getRequestHttpProxy().getProxy()); //设置授权 - clientBuilder.authenticator(new Authenticator() { + clientBuilder.proxyAuthenticator(new Authenticator() { @Override public Request authenticate(Route route, Response response) throws IOException { String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword()); return response.request().newBuilder() - .header("Authorization", credential) + .header("Proxy-Authorization", credential) .build(); } }); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImpl.java index 4802c5549c..0cf0ab6665 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 @@ -28,7 +28,7 @@ public class WxCpTaskCardServiceImpl implements WxCpTaskCardService { @Override public void update(List userIds, String taskId, String replaceName) throws WxErrorException { - Integer agentId = this.mainService.getWxCpConfigStorage().getAgentId(); + Long agentId = this.mainService.getWxCpConfigStorage().getAgentId(); Map data = new HashMap<>(4); data.put("userids", userIds); @@ -45,7 +45,7 @@ public void update(List userIds, String taskId, String replaceName) thro public void updateTemplateCardButton(List userIds, List partyIds, List tagIds, Integer atAll, String responseCode, String replaceName) throws WxErrorException { - Integer agentId = this.mainService.getWxCpConfigStorage().getAgentId(); + Long agentId = this.mainService.getWxCpConfigStorage().getAgentId(); Map data = new HashMap<>(7); data.put("userids", userIds); data.put("partyids", partyIds); @@ -65,6 +65,6 @@ public void updateTemplateCardButton(List userIds, List partyId @Override public void updateTemplateCardButton(TemplateCardMessage templateCardMessage) throws WxErrorException { String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_TEMPLATE_CARD); - this.mainService.post(url, WxGsonBuilder.create().toJson(templateCardMessage)); + this.mainService.post(url, templateCardMessage.toJson()); } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTodoServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTodoServiceImpl.java new file mode 100644 index 0000000000..627784b36c --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTodoServiceImpl.java @@ -0,0 +1,50 @@ +package me.chanjar.weixin.cp.api.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.api.WxCpTodoService; +import me.chanjar.weixin.cp.bean.todo.WxCpTodo; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Todo.*; + +/** + * 企业微信待办接口实现类. + * + * @author Binary Wang created on 2026-08-11 + */ +@Slf4j +@RequiredArgsConstructor +public class WxCpTodoServiceImpl implements WxCpTodoService { + private final WxCpService cpService; + + @Override + public WxCpTodo get(String todoId) throws WxErrorException { + final Map param = new HashMap<>(1); + param.put("todo_id", todoId); + final String response = this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(TODO_GET), + WxCpGsonBuilder.create().toJson(param)); + return WxCpGsonBuilder.create().fromJson(response, WxCpTodo.class); + } + + @Override + public void update(String todoId, Integer status, List attendees) throws WxErrorException { + final Map param = new HashMap<>(3); + param.put("todo_id", todoId); + if (status != null) { + param.put("status", status); + } + if (attendees != null && !attendees.isEmpty()) { + param.put("attendees", attendees); + } + + this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(TODO_UPDATE), + WxCpGsonBuilder.create().toJson(param)); + } +} 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 5d61b3a199..8f96a729a0 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 @@ -32,7 +32,7 @@ public class WxCpAgent implements Serializable { private String errMsg; @SerializedName("agentid") - private Integer agentId; + private Long agentId; @SerializedName("name") private String name; 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 index 4562d9b9b0..8ca7a6cb95 100644 --- 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 @@ -21,7 +21,7 @@ public class WxCpAgentJsapiSignature implements Serializable { private String corpid; - private Integer agentid; + private Long agentid; private long timestamp; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentWorkBench.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentWorkBench.java index a2737f7237..4c17397ecd 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentWorkBench.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentWorkBench.java @@ -6,6 +6,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; import me.chanjar.weixin.cp.bean.workbench.WorkBenchKeyData; import me.chanjar.weixin.cp.bean.workbench.WorkBenchList; import me.chanjar.weixin.cp.constant.WxCpConsts; @@ -33,6 +34,10 @@ public class WxCpAgentWorkBench implements Serializable { * 用户的userid */ private String userId; + /** + * 用户的userIds + */ + private List useridList; /** * 应用id */ @@ -53,6 +58,20 @@ public class WxCpAgentWorkBench implements Serializable { * 是否覆盖用户工作台的数据。设置为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; @@ -88,6 +107,20 @@ public String toUserDataString() { return userDataObject.toString(); } + /** + * 生成批量用户数据Json字符串 + * + * @return the string + */ + public String toBatchUserDataString() { + JsonObject userDataObject = new JsonObject(); + userDataObject.addProperty("agentid", this.agentId); + JsonArray useridList = WxGsonBuilder.create().toJsonTree(this.useridList).getAsJsonArray(); + userDataObject.add("userid_list", useridList); + this.handleBatch(userDataObject); + return userDataObject.toString(); + } + /** * 处理不用类型的工作台数据 */ @@ -135,6 +168,13 @@ private void handle(JsonObject templateObject) { 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; } @@ -144,4 +184,79 @@ private void handle(JsonObject templateObject) { } } + /** + * 处理不用类型的工作台数据 + */ + private void handleBatch(JsonObject templateObject) { + switch (this.getType()) { + case WxCpConsts.WorkBenchType.KEYDATA: { + JsonArray keyDataArray = new JsonArray(); + JsonObject itemsObject = new JsonObject(); + for (WorkBenchKeyData keyDataItem : this.keyDataList) { + JsonObject keyDataObject = new JsonObject(); + keyDataObject.addProperty("key", keyDataItem.getKey()); + keyDataObject.addProperty("data", keyDataItem.getData()); + keyDataObject.addProperty("jump_url", keyDataItem.getJumpUrl()); + keyDataObject.addProperty("pagepath", keyDataItem.getPagePath()); + keyDataArray.add(keyDataObject); + } + itemsObject.add("items", keyDataArray); + JsonObject dataObject = new JsonObject(); + dataObject.addProperty("type", WxCpConsts.WorkBenchType.KEYDATA); + dataObject.add("keydata", itemsObject); + templateObject.add("data", dataObject); + break; + } + case WxCpConsts.WorkBenchType.IMAGE: { + JsonObject image = new JsonObject(); + image.addProperty("url", this.url); + image.addProperty("jump_url", this.jumpUrl); + image.addProperty("pagepath", this.pagePath); + JsonObject dataObject = new JsonObject(); + dataObject.addProperty("type", WxCpConsts.WorkBenchType.IMAGE); + dataObject.add("image", image); + templateObject.add("data", dataObject); + break; + } + case WxCpConsts.WorkBenchType.LIST: { + JsonArray listArray = new JsonArray(); + JsonObject itemsObject = new JsonObject(); + for (WorkBenchList listItem : this.lists) { + JsonObject listObject = new JsonObject(); + listObject.addProperty("title", listItem.getTitle()); + listObject.addProperty("jump_url", listItem.getJumpUrl()); + listObject.addProperty("pagepath", listItem.getPagePath()); + listArray.add(listObject); + } + itemsObject.add("items", listArray); + JsonObject dataObject = new JsonObject(); + dataObject.addProperty("type", WxCpConsts.WorkBenchType.LIST); + dataObject.add("list", itemsObject); + templateObject.add("data", dataObject); + break; + } + case WxCpConsts.WorkBenchType.WEBVIEW: { + JsonObject webview = new JsonObject(); + webview.addProperty("url", this.url); + webview.addProperty("jump_url", this.jumpUrl); + webview.addProperty("pagepath", this.pagePath); + if (this.enableWebviewClick != null) { + webview.addProperty("enable_webview_click", this.enableWebviewClick); + } + webview.addProperty("height", this.height); + if (this.hideTitle != null) { + webview.addProperty("hide_title", this.hideTitle); + } + JsonObject dataObject = new JsonObject(); + dataObject.addProperty("type", WxCpConsts.WorkBenchType.WEBVIEW); + dataObject.add("webview", webview); + templateObject.add("data", dataObject); + break; + } + default: { + //do nothing + } + } + } + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java index 6bf9a30aeb..a895c38a8f 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java @@ -10,7 +10,8 @@ /** * 返回结果 * - * @author yqx & WangWong created on 2020/3/16 + * @author yqx, WangWong + * @since 2020/3/16 */ @Getter @Setter diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java index fa50216153..9919fd72b8 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java @@ -216,7 +216,7 @@ public static class Agent implements Serializable { /** * 付费状态 - *
+ *
*

    *
  • 0-没有付费;
  • *
  • 1-限时试用;
  • diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearch.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpContactSearch.java index a51ca748b5..11c653a433 100644 --- 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 @@ -42,12 +42,6 @@ public class WxCpTpContactSearch implements Serializable { @SerializedName("agentid") private Integer agentId; - /** - * 查询的偏移量,每次调用的offset在上一次offset基础上加上limit - */ - @SerializedName("offset") - private Integer offset; - /** * 查询返回的最大数量,默认为50,最多为200,查询返回的数量可能小于limit指定的值 */ @@ -60,6 +54,12 @@ public class WxCpTpContactSearch implements Serializable { @SerializedName("full_match_field") private Integer fullMatchField; + /** + * 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + */ + @SerializedName("cursor") + private String cursor; + /** * To json string. * 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 index d482875d6c..074b30bc0e 100644 --- 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 @@ -24,6 +24,9 @@ public class WxCpTpContactSearchResp extends WxCpBaseResp { @SerializedName("query_result") private QueryResult queryResult; + @SerializedName("next_cursor") + private String nextCursor; + /** * The type Query result. */ diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCustomizedAppDetail.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCustomizedAppDetail.java new file mode 100644 index 0000000000..f9ca645b82 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCustomizedAppDetail.java @@ -0,0 +1,221 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * 代开发应用详情. + * + * @author Binary Wang + * created on 2026-01-14 + */ +@Data +public class WxCpTpCustomizedAppDetail extends WxCpBaseResp { + + /** + * 授权方企业id + */ + @SerializedName("auth_corpid") + private String authCorpId; + + /** + * 授权方企业名称 + */ + @SerializedName("auth_corp_name") + private String authCorpName; + + /** + * 授权方企业方形头像 + */ + @SerializedName("auth_corp_square_logo_url") + private String authCorpSquareLogoUrl; + + /** + * 授权方企业圆形头像 + */ + @SerializedName("auth_corp_round_logo_url") + private String authCorpRoundLogoUrl; + + /** + * 授权方企业类型,1. 企业; 2. 政府以及事业单位; 3. 其他组织, 4.团队小型企业(原企业微信认证版用户) + */ + @SerializedName("auth_corp_type") + private Integer authCorpType; + + /** + * 授权方企业在微工作台(原企业号)的二维码,可用于关注微工作台 + */ + @SerializedName("auth_corp_qrcode_url") + private String authCorpQrcodeUrl; + + /** + * 授权方企业用户规模 + */ + @SerializedName("auth_corp_user_limit") + private Integer authCorpUserLimit; + + /** + * 授权方企业的主体名称(仅认证或验证过的企业有),即企业全称 + */ + @SerializedName("auth_corp_full_name") + private String authCorpFullName; + + /** + * 企业类型,1. 已验证企业;2. 已认证企业 + */ + @SerializedName("auth_corp_verified_type") + private Integer authCorpVerifiedType; + + /** + * 授权方企业所属行业 + */ + @SerializedName("auth_corp_industry") + private String authCorpIndustry; + + /** + * 授权方企业所属子行业 + */ + @SerializedName("auth_corp_sub_industry") + private String authCorpSubIndustry; + + /** + * 授权方企业所在地址 + */ + @SerializedName("auth_corp_location") + private String authCorpLocation; + + /** + * 代开发自建应用详情 + */ + @SerializedName("customized_app_list") + private List customizedAppList; + + /** + * From json wx cp tp customized app detail. + * + * @param json the json + * @return the wx cp tp customized app detail + */ + public static WxCpTpCustomizedAppDetail fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpCustomizedAppDetail.class); + } + + @Override + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + + /** + * 代开发自建应用信息 + */ + @Data + public static class CustomizedApp implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 代开发自建应用的agentid + */ + @SerializedName("agentid") + private Integer agentId; + + /** + * 代开发自建应用对应的模板id + */ + @SerializedName("template_id") + private String templateId; + + /** + * 代开发自建应用的name + */ + @SerializedName("name") + private String name; + + /** + * 代开发自建应用的description + */ + @SerializedName("description") + private String description; + + /** + * 代开发自建应用的logo url + */ + @SerializedName("logo_url") + private String logoUrl; + + /** + * 代开发自建应用的可见范围 + */ + @SerializedName("allow_userinfos") + private AllowUserInfos allowUserInfos; + + /** + * 代开发自建应用是否被禁用 + */ + @SerializedName("close") + private Integer close; + + /** + * 代开发自建应用主页url + */ + @SerializedName("home_url") + private String homeUrl; + + /** + * 代开发自建应用的模式,0 = 代开发自建应用;1 = 第三方应用代开发 + */ + @SerializedName("app_type") + private Integer appType; + } + + /** + * 应用可见范围 + */ + @Data + public static class AllowUserInfos implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 应用可见范围(成员) + */ + @SerializedName("user") + private List users; + + /** + * 应用可见范围(部门) + */ + @SerializedName("department") + private List departments; + } + + /** + * 成员信息 + */ + @Data + public static class User implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 成员userid + */ + @SerializedName("userid") + private String userId; + } + + /** + * 部门信息 + */ + @Data + public static class Department implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 部门id + */ + @SerializedName("id") + private Integer id; + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java index 522e606a20..5330194abe 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java @@ -225,7 +225,7 @@ public static class Agent implements Serializable { /** * 付费状态 - *
    + *
    *
      *
    • 0-没有付费;
    • *
    • 1-限时试用;
    • diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTag.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTag.java index 74e1fec3f8..a73ec171b4 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTag.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTag.java @@ -10,7 +10,7 @@ * The type Wx cp tp tag. * * @author zhangq - * @since 2021 -02-14 16:15 16:15 + * @since 2021-02-14 16:15 */ @Data public class WxCpTpTag implements Serializable { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagAddOrRemoveUsersResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagAddOrRemoveUsersResult.java index dfbf250480..565cbb408c 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagAddOrRemoveUsersResult.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagAddOrRemoveUsersResult.java @@ -6,7 +6,7 @@ * 企业微信第三方开发-增加标签成员成员api响应体 * * @author zhangq - * @since 2021 /2/14 16:44 + * @since 2021/2/14 16:44 */ public class WxCpTpTagAddOrRemoveUsersResult extends WxCpTagAddOrRemoveUsersResult { private static final long serialVersionUID = 3490401800490702052L; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagGetResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagGetResult.java index 162030c956..134656e438 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagGetResult.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTagGetResult.java @@ -6,7 +6,7 @@ * 获取标签成员接口响应体 * * @author zhangq - * @since 2021 /2/14 16:28 + * @since 2021/2/14 16:28 */ public class WxCpTpTagGetResult extends WxCpTagGetResult { private static final long serialVersionUID = 9051748686315562400L; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTemplateList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTemplateList.java new file mode 100644 index 0000000000..83abbb6e7d --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpTemplateList.java @@ -0,0 +1,83 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * 应用模板列表. + * + * @author Binary Wang + * created on 2026-01-14 + */ +@Data +public class WxCpTpTemplateList extends WxCpBaseResp { + + /** + * 应用模板列表 + */ + @SerializedName("template_list") + private List