mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2026-03-10 00:13:40 +08:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd3bbde725 | ||
|
|
84e70febdf | ||
|
|
155f3f778f | ||
|
|
26f5887e22 | ||
|
|
23a2e18f1c | ||
|
|
8f20f3baba | ||
|
|
93a2fa701a | ||
|
|
1f4ed68750 | ||
|
|
c4f383462e | ||
|
|
03114b9900 | ||
|
|
f6d9e8ba4d | ||
|
|
1da6cf56ff | ||
|
|
e655a33956 | ||
|
|
9940546956 | ||
|
|
1bd50d4d2f | ||
|
|
8e46da5003 | ||
|
|
b1e7cd2cd7 | ||
|
|
7fcd1ed5b3 | ||
|
|
21d5f2fdd2 | ||
|
|
e57b7e1eb5 | ||
|
|
833f60c68d | ||
|
|
1922aaaa94 | ||
|
|
3c440eacb3 | ||
|
|
b8d2bb345a | ||
|
|
730ac24c3f | ||
|
|
ebb9ccdebe | ||
|
|
76c7e994a5 | ||
|
|
092e992817 | ||
|
|
69a2ab9cc7 | ||
|
|
0854e4d971 | ||
|
|
57e12dee29 | ||
|
|
4d4294707b | ||
|
|
9ab64167d5 | ||
|
|
28fac4ec2e | ||
|
|
b94106fd57 | ||
|
|
d4186c49c1 | ||
|
|
bb573afcaf | ||
|
|
e9bc5d0109 | ||
|
|
738bcf7755 | ||
|
|
de4848b2f7 | ||
|
|
b26c786797 | ||
|
|
b9b4f00438 | ||
|
|
98329b11da | ||
|
|
db85c0ab8a | ||
|
|
ca567ce310 | ||
|
|
a6825a62bb | ||
|
|
9fd12b2a09 | ||
|
|
10f71234e4 | ||
|
|
9aa2781e68 |
13
.github/agents/my-agent.agent.md
vendored
Normal file
13
.github/agents/my-agent.agent.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
# Fill in the fields below to create a basic custom agent for your repository.
|
||||
# The Copilot CLI can be used for local testing: https://gh.io/customagents/cli
|
||||
# To make this agent available, merge this file into the default repository branch.
|
||||
# For format details, see: https://gh.io/customagents/config
|
||||
|
||||
name: 全部用中文
|
||||
description: 需要用中文,包括PR标题和分析总结过程
|
||||
---
|
||||
|
||||
# My Agent
|
||||
|
||||
请使用中文输出思考过程和总结,包括PR标题,提交commit信息也要使用中文
|
||||
244
.github/copilot-instructions.md
vendored
244
.github/copilot-instructions.md
vendored
@@ -1,198 +1,202 @@
|
||||
# WxJava - WeChat Java SDK Development Instructions
|
||||
# Copilot Instruction
|
||||
请始终使用中文生成 Pull Request 的标题、描述和提交信息
|
||||
|
||||
WxJava is a comprehensive WeChat Java SDK supporting multiple WeChat platforms including Official Accounts (公众号), Mini Programs (小程序), WeChat Pay (微信支付), Enterprise WeChat (企业微信), Open Platform (开放平台), and Channel/Video (视频号). This is a Maven multi-module project with Spring Boot and Solon framework integrations.
|
||||
|
||||
**ALWAYS reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the information here.**
|
||||
# WxJava - 微信 Java SDK 开发说明
|
||||
|
||||
## Working Effectively
|
||||
WxJava 是一个支持多种微信平台的完整 Java SDK,包含公众号、小程序、微信支付、企业微信、开放平台、视频号、企点等多种功能模块。
|
||||
|
||||
### Prerequisites and Environment Setup
|
||||
- **Java Requirements**: JDK 8+ required (project uses Java 8 as minimum target)
|
||||
- **Maven**: Maven 3.6+ recommended (Maven 3.9.11 validated)
|
||||
- **IDE**: IntelliJ IDEA recommended (project optimized for IDEA)
|
||||
**请始终优先参考本说明,只有在遇到与此内容不一致的意外信息时,才退而使用搜索或 bash 命令。**
|
||||
|
||||
### Bootstrap, Build, and Validate
|
||||
Execute these commands in sequence after cloning:
|
||||
## 高效开发指南
|
||||
|
||||
### 前置条件与环境准备
|
||||
- **Java 要求**:JDK 8+(项目最低目标为 Java 8)
|
||||
- **Maven**:推荐 Maven 3.6+(已验证 Maven 3.9.11)
|
||||
- **IDE**:推荐使用 IntelliJ IDEA(项目针对 IDEA 优化)
|
||||
|
||||
### 引导、构建与校验
|
||||
克隆仓库后按顺序执行以下命令:
|
||||
|
||||
```bash
|
||||
# 1. Basic compilation (NEVER CANCEL - takes 4-5 minutes)
|
||||
# 1. 基础编译(请勿中断 - 约需 4-5 分钟)
|
||||
mvn clean compile -DskipTests=true --no-transfer-progress
|
||||
# Timeout: Set 8+ minutes. Actual time: ~4 minutes
|
||||
# 超时时间:建议设置 8 分钟以上。实际时间:约 4 分钟
|
||||
|
||||
# 2. Full package build (NEVER CANCEL - takes 2-3 minutes)
|
||||
# 2. 完整打包(请勿中断 - 约需 2-3 分钟)
|
||||
mvn clean package -DskipTests=true --no-transfer-progress
|
||||
# Timeout: Set 5+ minutes. Actual time: ~2 minutes
|
||||
# 超时时间:建议设置 5 分钟以上。实际时间:约 2 分钟
|
||||
|
||||
# 3. Code quality validation (NEVER CANCEL - takes 45-60 seconds)
|
||||
# 3. 代码质量校验(请勿中断 - 约需 45-60 秒)
|
||||
mvn checkstyle:check --no-transfer-progress
|
||||
# Timeout: Set 3+ minutes. Actual time: ~50 seconds
|
||||
# 超时时间:建议设置 3 分钟以上。实际时间:约 50 秒
|
||||
```
|
||||
|
||||
**CRITICAL TIMING NOTES:**
|
||||
- **NEVER CANCEL** any Maven build command
|
||||
- Compilation phase takes longest (~4 minutes) due to 34 modules
|
||||
- Full builds are faster on subsequent runs due to incremental compilation
|
||||
- Always use `--no-transfer-progress` to reduce log noise
|
||||
重要时间说明:
|
||||
- 绝对不要中断任意 Maven 构建命令
|
||||
- 编译阶段耗时最长(约 4 分钟),原因是项目包含 34 个模块
|
||||
- 后续构建会更快,因为存在增量编译
|
||||
- 始终使用 `--no-transfer-progress` 以减少日志噪音
|
||||
|
||||
### Testing Structure
|
||||
- **Test Framework**: TestNG (not JUnit)
|
||||
- **Test Files**: 298 test files across all modules
|
||||
- **Default Behavior**: Tests are DISABLED by default in pom.xml (`<skip>true</skip>`)
|
||||
- **Test Configuration**: Tests require external WeChat API credentials via test-config.xml files
|
||||
- **DO NOT** attempt to run tests without proper WeChat API credentials as they will fail
|
||||
### 测试结构
|
||||
- **测试框架**:TestNG(非 JUnit)
|
||||
- **测试文件**:共有 298 个测试文件
|
||||
- **默认行为**:pom.xml 中默认禁用测试(`<skip>true</skip>`)
|
||||
- **测试配置**:测试需要通过 test-config.xml 提供真实的微信 API 凭据
|
||||
- **注意**:没有真实微信 API 凭据请不要尝试运行测试,测试将会失败
|
||||
|
||||
## Project Structure and Navigation
|
||||
## 项目结构与导航
|
||||
|
||||
### Core SDK Modules (Main Development Areas)
|
||||
- `weixin-java-common/` - Common utilities and base classes (most important)
|
||||
- `weixin-java-mp/` - WeChat Official Account SDK (公众号)
|
||||
- `weixin-java-pay/` - WeChat Pay SDK (微信支付)
|
||||
- `weixin-java-miniapp/` - Mini Program SDK (小程序)
|
||||
- `weixin-java-cp/` - Enterprise WeChat SDK (企业微信)
|
||||
- `weixin-java-open/` - Open Platform SDK (开放平台)
|
||||
- `weixin-java-channel/` - Channel/Video SDK (视频号)
|
||||
- `weixin-java-qidian/` - Qidian SDK (企点)
|
||||
### 核心 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
|
||||
|
||||
### Framework Integration Modules
|
||||
- `spring-boot-starters/` - Spring Boot auto-configuration starters
|
||||
- `solon-plugins/` - Solon framework plugins
|
||||
- `weixin-graal/` - GraalVM native image support
|
||||
### 框架集成模块
|
||||
- `spring-boot-starters/` - Spring Boot 自动配置 starter
|
||||
- `solon-plugins/` - Solon 框架插件
|
||||
- `weixin-graal/` - GraalVM 本地镜像支持
|
||||
|
||||
### Configuration and Quality
|
||||
- `quality-checks/google_checks.xml` - Checkstyle configuration
|
||||
- `.editorconfig` - Code formatting rules (2 spaces = 1 tab)
|
||||
- `pom.xml` - Root Maven configuration
|
||||
### 配置与质量控制
|
||||
- `quality-checks/google_checks.xml` - Checkstyle 配置
|
||||
- `.editorconfig` - 代码格式规则(2 个空格等于 1 个制表)
|
||||
- `pom.xml` - 根级 Maven 配置
|
||||
|
||||
## Development Workflow
|
||||
## 开发工作流
|
||||
|
||||
### Making Code Changes
|
||||
1. **Always build first** to establish clean baseline:
|
||||
### 修改代码的流程
|
||||
1. 修改前务必先构建以建立干净基线:
|
||||
```bash
|
||||
mvn clean compile --no-transfer-progress
|
||||
```
|
||||
|
||||
2. **Follow code style** (enforced by checkstyle):
|
||||
- Use 2 spaces for indentation (not tabs)
|
||||
- Follow Google Java Style Guide
|
||||
- Install EditorConfig plugin in your IDE
|
||||
2. 遵循代码风格(由 checkstyle 强制):
|
||||
- 缩进使用 2 个空格(不要用制表符)
|
||||
- 遵循 Google Java 风格指南
|
||||
- 在 IDE 中安装 EditorConfig 插件
|
||||
|
||||
3. **Validate changes incrementally**:
|
||||
3. 增量验证修改:
|
||||
```bash
|
||||
# After each change:
|
||||
# 每次修改后运行:
|
||||
mvn compile --no-transfer-progress
|
||||
mvn checkstyle:check --no-transfer-progress
|
||||
mvn checkstyle:check --no-transfer-progress
|
||||
```
|
||||
|
||||
### Before Submitting Changes
|
||||
**ALWAYS run these validation steps in sequence:**
|
||||
### 提交修改前的必须校验
|
||||
请务必按顺序完成以下校验步骤:
|
||||
|
||||
1. **Code Style Validation**:
|
||||
1. 代码风格校验:
|
||||
```bash
|
||||
mvn checkstyle:check --no-transfer-progress
|
||||
# Must pass - takes ~50 seconds
|
||||
# 必须通过 - 约需 50 秒
|
||||
```
|
||||
|
||||
2. **Full Clean Build**:
|
||||
2. 完整清理构建:
|
||||
```bash
|
||||
mvn clean package -DskipTests=true --no-transfer-progress
|
||||
# Must succeed - takes ~2 minutes
|
||||
# 必须成功 - 约需 2 分钟
|
||||
```
|
||||
|
||||
3. **Documentation**: Update javadoc for public methods and classes
|
||||
4. **Contribution Guidelines**: Follow `CONTRIBUTING.md` - PRs must target `develop` branch
|
||||
3. 文档:为公共方法和类补充或更新 javadoc
|
||||
4. 贡献规范:遵循 `CONTRIBUTING.md`,Pull Request 必须以 `develop` 分支为目标
|
||||
|
||||
## Module Dependencies and Build Order
|
||||
## 模块依赖与构建顺序
|
||||
|
||||
### Core Module Dependencies (Build Order)
|
||||
1. `weixin-graal` (GraalVM support)
|
||||
2. `weixin-java-common` (foundation for all other modules)
|
||||
3. Core SDK modules (mp, pay, miniapp, cp, open, channel, qidian)
|
||||
4. Framework integrations (spring-boot-starters, solon-plugins)
|
||||
### 核心模块依赖(构建顺序)
|
||||
1. `weixin-graal`(GraalVM 支持)
|
||||
2. `weixin-java-common`(所有模块的基础)
|
||||
3. 核心 SDK 模块(mp、pay、miniapp、cp、open、channel、qidian)
|
||||
4. 框架集成(spring-boot-starters、solon-plugins)
|
||||
|
||||
### Key Relationship Patterns
|
||||
- All SDK modules depend on `weixin-java-common`
|
||||
- Spring Boot starters depend on corresponding SDK modules
|
||||
- Solon plugins follow same pattern as Spring Boot starters
|
||||
- Each module has both single and multi-account configurations
|
||||
### 主要关系模式
|
||||
- 所有 SDK 模块都依赖于 `weixin-java-common`
|
||||
- Spring Boot starters 依赖对应的 SDK 模块
|
||||
- Solon 插件遵循与 Spring Boot starters 相同的依赖模式
|
||||
- 每个模块都有单账号与多账号配置支持
|
||||
|
||||
## Common Tasks and Commands
|
||||
## 常见任务与命令
|
||||
|
||||
### Validate Specific Module
|
||||
### 验证指定模块
|
||||
```bash
|
||||
# Build single module (replace 'weixin-java-mp' with target module):
|
||||
# 构建单个模块(将 'weixin-java-mp' 替换为目标模块):
|
||||
cd weixin-java-mp
|
||||
mvn clean compile --no-transfer-progress
|
||||
```
|
||||
|
||||
### Check Dependencies
|
||||
### 检查依赖
|
||||
```bash
|
||||
# Analyze dependencies:
|
||||
# 分析依赖树:
|
||||
mvn dependency:tree --no-transfer-progress
|
||||
|
||||
# Check for dependency updates:
|
||||
# 检查依赖更新:
|
||||
./others/check-dependency-updates.sh
|
||||
```
|
||||
|
||||
### Release and Publishing
|
||||
### 发布与发布准备
|
||||
```bash
|
||||
# Version check:
|
||||
# 版本检查:
|
||||
mvn versions:display-property-updates --no-transfer-progress
|
||||
|
||||
# Deploy (requires credentials):
|
||||
# 部署(需要凭据):
|
||||
mvn clean deploy -P release --no-transfer-progress
|
||||
```
|
||||
|
||||
## Important Files and Locations
|
||||
## 重要文件与位置
|
||||
|
||||
### Configuration Files
|
||||
- `pom.xml` - Root Maven configuration with dependency management
|
||||
- `quality-checks/google_checks.xml` - Checkstyle rules
|
||||
- `.editorconfig` - IDE formatting configuration
|
||||
- `.github/workflows/maven-publish.yml` - CI/CD pipeline
|
||||
### 配置文件
|
||||
- `pom.xml` - 根级 Maven 配置与依赖管理
|
||||
- `quality-checks/google_checks.xml` - Checkstyle 规则
|
||||
- `.editorconfig` - IDE 格式化配置
|
||||
- `.github/workflows/maven-publish.yml` - CI/CD 工作流
|
||||
|
||||
### Documentation
|
||||
- `README.md` - Project overview and usage (Chinese)
|
||||
- `CONTRIBUTING.md` - Development contribution guidelines
|
||||
- `demo.md` - Links to demo projects and examples
|
||||
- Each module has dedicated documentation and examples
|
||||
### 文档
|
||||
- `README.md` - 项目概览与使用说明(中文)
|
||||
- `CONTRIBUTING.md` - 贡献指南
|
||||
- `demo.md` - 示例项目与演示链接
|
||||
- 每个模块均有单独的文档与示例
|
||||
|
||||
### Test Resources
|
||||
- `*/src/test/resources/test-config.sample.xml` - Template for test configuration
|
||||
- Tests require real WeChat API credentials to run
|
||||
### 测试资源
|
||||
- `*/src/test/resources/test-config.sample.xml` - 测试配置模板
|
||||
- 测试运行需要真实的微信 API 凭据
|
||||
|
||||
## SDK Usage Patterns
|
||||
## SDK 使用模式
|
||||
|
||||
### Maven Dependency Usage
|
||||
### Maven 依赖示例
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-mp</artifactId> <!-- or other modules -->
|
||||
<artifactId>weixin-java-mp</artifactId> <!-- 或其他模块 -->
|
||||
<version>4.7.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Common Development Areas
|
||||
- **API Client Implementation**: Located in `*/service/impl/` directories
|
||||
- **Model Classes**: Located in `*/bean/` directories
|
||||
- **Configuration**: Located in `*/config/` directories
|
||||
- **Utilities**: Located in `*/util/` directories in weixin-java-common
|
||||
### 常见开发区域
|
||||
- **API 客户端实现**:位于 `*/service/impl/` 目录
|
||||
- **模型类**:位于 `*/bean/` 目录
|
||||
- **配置**:位于 `*/config/` 目录
|
||||
- **工具类**:位于 `weixin-java-common` 的 `*/util/` 目录
|
||||
|
||||
## Troubleshooting
|
||||
## 故障排查
|
||||
|
||||
### Build Issues
|
||||
- **OutOfMemoryError**: Increase Maven memory: `export MAVEN_OPTS="-Xmx2g"`
|
||||
- **Compilation Failures**: Usually dependency issues - run `mvn clean` first
|
||||
- **Checkstyle Failures**: Check `.editorconfig` settings in IDE
|
||||
### 构建问题
|
||||
- **OutOfMemoryError**:增加 Maven 内存:`export MAVEN_OPTS="-Xmx2g"`
|
||||
- **编译失败**:通常为依赖问题 - 先执行 `mvn clean`
|
||||
- **Checkstyle 失败**:检查 IDE 的 `.editorconfig` 设置
|
||||
|
||||
### Common Gotchas
|
||||
- **Tests Always Skip**: This is normal - tests require WeChat API credentials
|
||||
- **Multi-Module Changes**: Always build from root, not individual modules
|
||||
- **Branch Target**: PRs must target `develop` branch, not `master`/`release`
|
||||
### 常见陷阱
|
||||
- **测试默认跳过**:这是正常现象 — 测试需要微信 API 凭据
|
||||
- **多模块变更**:总是在仓库根目录构建,而不是单独模块
|
||||
- **分支目标**:Pull Request 必须以 `develop` 分支为目标,而不是 `master` 或 `release`
|
||||
|
||||
## Performance Notes
|
||||
- **First Build**: Takes 4-5 minutes due to dependency downloads
|
||||
- **Incremental Builds**: Much faster (~30-60 seconds)
|
||||
- **Checkstyle**: Runs quickly (~50 seconds) and should be run frequently
|
||||
- **IDE Performance**: Project uses Lombok - ensure annotation processing is enabled
|
||||
## 性能说明
|
||||
- **首次构建**:由于依赖下载,耗时 4-5 分钟
|
||||
- **增量构建**:通常更快(约 30-60 秒)
|
||||
- **Checkstyle**:运行迅速(约 50 秒),应当经常运行
|
||||
- **IDE 性能**:项目使用 Lombok,请确保启用注解处理
|
||||
|
||||
Remember: This is a SDK library project, not a runnable application. Changes should focus on API functionality, not application behavior.
|
||||
注意:本项目为 SDK 库项目,而非可运行应用。修改应以 API 功能为主,不要改动应用级行为。
|
||||
|
||||
21
README.md
21
README.md
@@ -65,13 +65,13 @@
|
||||
1. [`WxJava` 荣获 `GitCode` 2024年度十大开源社区奖项](https://mp.weixin.qq.com/s/wM_UlMsDm3IZ1CPPDvcvQw)。
|
||||
2. 项目合作洽谈请联系微信`binary0000`(在微信里自行搜索并添加好友,请注明来意,如有关于SDK问题需讨论请参考下文入群讨论,不要加此微信)。
|
||||
3. **2024-12-30 发布 [【4.7.0正式版】](https://mp.weixin.qq.com/s/_7k-XLYBqeJJhvHWCsdT0A)**!
|
||||
4. 贡献源码可以参考视频:[【贡献源码全过程(上集)】](https://mp.weixin.qq.com/s/3xUZSATWwHR_gZZm207h7Q)、[【贡献源码全过程(下集)】](https://mp.weixin.qq.com/s/nyzJwVVoYSJ4hSbwyvTx9A) ,友情提供:[程序员小山与Bug](https://space.bilibili.com/473631007)
|
||||
5. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用 `maven` 或 `gradle` 引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码;
|
||||
6. 微信开发新手请务必阅读【开发文档】([Gitee Wiki](https://gitee.com/binary/weixin-java-tools/wikis/Home) 或者 [Github Wiki](https://github.com/binarywang/WxJava/wiki))的常见问题部分,可以少走很多弯路,节省不少时间。
|
||||
7. 技术交流群:想获得QQ群/微信群/钉钉企业群等信息的同学,请使用微信扫描上面的微信公众号二维码关注 `WxJava` 后点击相关菜单即可获取加入方式,同时也可以在微信中搜索 `weixin-java-tools` 或 `WxJava` 后选择正确的公众号进行关注,该公众号会及时通知SDK相关更新信息,并不定期分享微信Java开发相关技术知识;
|
||||
8. 钉钉技术交流群:`32206329`(技术交流2群), `30294972`(技术交流1群,目前已满),`35724728`(通知群,实时通知Github项目变更记录)。
|
||||
9. 微信开发新手或者Java开发新手在群内提问或新开Issue提问前,请先阅读[【提问的智慧】](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md),并确保已查阅过 [【开发文档Wiki】](https://github.com/binarywang/WxJava/wiki) ,避免浪费大家的宝贵时间;
|
||||
10. 寻求帮助时需贴代码或大长串异常信息的,请利用 http://paste.ubuntu.com
|
||||
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
|
||||
|
||||
--------------------------------
|
||||
### 其他说明
|
||||
@@ -106,6 +106,13 @@
|
||||
- 企业微信:`weixin-java-cp`
|
||||
- 微信视频号/微信小店:`weixin-java-channel`
|
||||
|
||||
**注意**:
|
||||
- **移动应用开发**:如果你的移动应用(iOS/Android App)需要接入微信登录、分享等功能:
|
||||
- 微信登录(网页授权):使用 `weixin-java-open` 模块,在服务端处理 OAuth 授权
|
||||
- 微信支付:使用 `weixin-java-pay` 模块
|
||||
- 客户端集成:需使用微信官方提供的移动端SDK(iOS/Android),本项目为服务端SDK
|
||||
- **微信开放平台**(`weixin-java-open`)主要用于第三方平台,代公众号或小程序进行开发和管理
|
||||
|
||||
|
||||
---------------------------------
|
||||
### 版本说明
|
||||
|
||||
80
docs/MINIAPP_KEFU_SERVICE.md
Normal file
80
docs/MINIAPP_KEFU_SERVICE.md
Normal file
@@ -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.
|
||||
112
docs/QUARKUS_SUPPORT.md
Normal file
112
docs/QUARKUS_SUPPORT.md
Normal file
@@ -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
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-miniapp</artifactId> <!-- 或其他模块 -->
|
||||
<version>4.7.8.B</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 构建 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)。
|
||||
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>WxJava - Weixin/Wechat Java SDK</name>
|
||||
<description>微信开发Java SDK</description>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<packaging>pom</packaging>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-solon-plugins</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<packaging>pom</packaging>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ public class WxOpenServiceAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WxOpenMessageRouter wxOpenMessageRouter(WxOpenService wxOpenService) {
|
||||
return new WxOpenMessageRouter(wxOpenService);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>weixin-graal</artifactId>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>weixin-java-channel</artifactId>
|
||||
|
||||
@@ -2,11 +2,8 @@ package me.chanjar.weixin.channel.api;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import me.chanjar.weixin.channel.bean.after.AfterSaleInfoResponse;
|
||||
import me.chanjar.weixin.channel.bean.after.AfterSaleListParam;
|
||||
import me.chanjar.weixin.channel.bean.after.AfterSaleListResponse;
|
||||
import me.chanjar.weixin.channel.bean.after.AfterSaleReasonResponse;
|
||||
import me.chanjar.weixin.channel.bean.after.AfterSaleRejectReasonResponse;
|
||||
|
||||
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;
|
||||
@@ -149,4 +146,41 @@ public interface WxChannelAfterSaleService {
|
||||
* @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<String> 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;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Compla
|
||||
@Slf4j
|
||||
public class WxChannelAfterSaleServiceImpl implements WxChannelAfterSaleService {
|
||||
|
||||
/** 微信商店服务 */
|
||||
/**
|
||||
* 微信商店服务
|
||||
*/
|
||||
private final BaseWxChannelServiceImpl<?, ?> shopService;
|
||||
|
||||
public WxChannelAfterSaleServiceImpl(BaseWxChannelServiceImpl<?, ?> shopService) {
|
||||
@@ -107,4 +109,25 @@ public class WxChannelAfterSaleServiceImpl implements WxChannelAfterSaleService
|
||||
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<String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package me.chanjar.weixin.channel.bean.after;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 售后单换货发货信息
|
||||
*
|
||||
* @author <a href="https://gitee.com/cchengg">Chu</a>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class AfterSaleAcceptExchangeReshipParam extends AfterSaleIdParam {
|
||||
private static final long serialVersionUID = -7946679037747710613L;
|
||||
|
||||
/** 快递单号*/
|
||||
@JsonProperty("waybill_id")
|
||||
private String waybillId;
|
||||
|
||||
/** 快递公司id,通过获取快递公司列表接口获得,非主流快递公司可以填OTHER*/
|
||||
@JsonProperty("delivery_id")
|
||||
private String deliveryId;
|
||||
|
||||
public AfterSaleAcceptExchangeReshipParam() {
|
||||
|
||||
}
|
||||
|
||||
public AfterSaleAcceptExchangeReshipParam(String afterSaleOrderId, String waybillId, String deliveryId) {
|
||||
super(afterSaleOrderId);
|
||||
this.waybillId = waybillId;
|
||||
this.deliveryId = deliveryId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,4 +31,12 @@ public class AfterSaleExchangeProductInfo implements Serializable {
|
||||
/** 数量 */
|
||||
@JsonProperty("product_cnt")
|
||||
private String productCnt;
|
||||
|
||||
/** 旧商品价格 */
|
||||
@JsonProperty("old_sku_price")
|
||||
private Integer oldSkuPrice;
|
||||
|
||||
/** 新商品价格 */
|
||||
@JsonProperty("new_sku_price")
|
||||
private Integer newSkuPrice;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package me.chanjar.weixin.channel.bean.after;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 售后单商家协商信息
|
||||
*
|
||||
* @author <a href="https://gitee.com/cchengg">Chu</a>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class AfterSaleMerchantUpdateParam extends AfterSaleIdParam {
|
||||
private static final long serialVersionUID = -3672834150982780L;
|
||||
|
||||
/** 协商修改把售后单修改成该售后类型。1:退款;2:退货退款*/
|
||||
@JsonProperty("type")
|
||||
private Integer type;
|
||||
|
||||
/** 金额(单位:分)*/
|
||||
@JsonProperty("amount")
|
||||
private Integer amount;
|
||||
|
||||
/** 协商描述*/
|
||||
@JsonProperty("merchant_update_desc")
|
||||
private String merchantUpdateDesc;
|
||||
|
||||
/** 协商原因*/
|
||||
@JsonProperty("update_reason_type")
|
||||
private Integer updateReasonType;
|
||||
|
||||
/** 1:已协商一致,邀请买家取消售后; 2:邀请买家核实与补充凭证; 3:修改买家售后申请*/
|
||||
@JsonProperty("merchant_update_type")
|
||||
private Integer merchantUpdateType;
|
||||
|
||||
/** 协商凭证id列表,可使用图片上传接口获取media_id(数据类型填0),当update_reason_type对应的need_image为1时必填*/
|
||||
@JsonProperty("media_ids")
|
||||
private List<String> mediaIds;
|
||||
|
||||
public AfterSaleMerchantUpdateParam() {
|
||||
}
|
||||
|
||||
public AfterSaleMerchantUpdateParam(String afterSaleOrderId, Integer type, Integer updateReasonType, Integer merchantUpdateType
|
||||
, Integer amount, String merchantUpdateDesc, List<String> mediaIds) {
|
||||
super(afterSaleOrderId);
|
||||
this.type = type;
|
||||
this.updateReasonType = updateReasonType;
|
||||
this.merchantUpdateType = merchantUpdateType;
|
||||
this.amount = amount;
|
||||
this.merchantUpdateDesc = merchantUpdateDesc;
|
||||
this.mediaIds = mediaIds;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package me.chanjar.weixin.channel.bean.after;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 售后单换货拒绝发货信息
|
||||
*
|
||||
* @author <a href="https://gitee.com/cchengg">Chu</a>
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class AfterSaleRejectExchangeReshipParam extends AfterSaleIdParam {
|
||||
private static final long serialVersionUID = -7946679037747710613L;
|
||||
|
||||
/** 拒绝原因具体描述 ,可使用默认描述,也可以自定义描述*/
|
||||
@JsonProperty("reject_reason")
|
||||
private String rejectReason;
|
||||
|
||||
/** 拒绝原因枚举 */
|
||||
@JsonProperty("reject_reason_type")
|
||||
private Integer rejectReasonType;
|
||||
|
||||
/** 退款凭证,可使用图片上传接口获取media_id(数据类型填0)*/
|
||||
@JsonProperty("reject_certificates")
|
||||
private List<String> rejectCertificates;
|
||||
|
||||
public AfterSaleRejectExchangeReshipParam() {
|
||||
}
|
||||
|
||||
public AfterSaleRejectExchangeReshipParam(String afterSaleOrderId, String rejectReason, Integer rejectReasonType, List<String> rejectCertificates) {
|
||||
super(afterSaleOrderId);
|
||||
this.rejectReason = rejectReason;
|
||||
this.rejectReasonType = rejectReasonType;
|
||||
this.rejectCertificates = rejectCertificates;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,4 +36,9 @@ public class AfterSaleRejectReason implements Serializable {
|
||||
@JsonProperty("reject_reason")
|
||||
private String rejectReason;
|
||||
|
||||
/**
|
||||
* 售后拒绝原因适用场景
|
||||
*/
|
||||
@JsonProperty("reject_scene")
|
||||
private Integer rejectScene;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class ComplaintHistory implements Serializable {
|
||||
|
||||
/** 用户联系电话 */
|
||||
@JsonProperty("phone_number")
|
||||
private Integer phoneNumber;
|
||||
private String phoneNumber;
|
||||
|
||||
/** 相关文本内容 */
|
||||
@JsonProperty("content")
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package me.chanjar.weixin.channel.bean.order;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 更换sku信息
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ChangeSkuInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 8783442929429377519L;
|
||||
|
||||
/**
|
||||
* 发货前更换sku状态。3:等待商家处理,4:商家审核通过,5:商家拒绝,6:用户主动取消,7:超时默认拒绝
|
||||
*/
|
||||
@JsonProperty("preshipment_change_sku_state")
|
||||
private Integer preshipmentChangeSkuState;
|
||||
|
||||
/**
|
||||
* 原sku_id
|
||||
*/
|
||||
@JsonProperty("old_sku_id")
|
||||
private String oldSkuId;
|
||||
|
||||
/**
|
||||
* 用户申请更换的sku_id
|
||||
*/
|
||||
@JsonProperty("new_sku_id")
|
||||
private String newSkuId;
|
||||
|
||||
/**
|
||||
* 商家处理请求的最后时间,只有当前换款请求处于"等待商家处理"才有值
|
||||
*/
|
||||
@JsonProperty("ddl_time_stamp")
|
||||
private Integer deadlineTimeStamp;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.chanjar.weixin.channel.bean.order;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 代发相关信息
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class DropshipInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -4562618835611282016L;
|
||||
|
||||
/**
|
||||
* 代发单号
|
||||
*/
|
||||
@JsonProperty("ds_order_id")
|
||||
private Long dsOrderId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.chanjar.weixin.channel.bean.order;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 赠品信息
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class FreeGiftInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 2024061212345678901L;
|
||||
|
||||
/**
|
||||
* 赠品对应的主品信息
|
||||
*/
|
||||
@JsonProperty("main_product_list")
|
||||
private List<MainProductInfo> mainProductList;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package me.chanjar.weixin.channel.bean.order;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 赠品对应的主品信息
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class MainProductInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 2024061212345678901L;
|
||||
|
||||
/**
|
||||
* 赠品数量
|
||||
*/
|
||||
@JsonProperty("gift_cnt")
|
||||
private Integer giftCnt;
|
||||
|
||||
/**
|
||||
* 活动id
|
||||
*/
|
||||
@JsonProperty("task_id")
|
||||
private Integer taskId;
|
||||
|
||||
/**
|
||||
* 商品id
|
||||
*/
|
||||
@JsonProperty("product_id")
|
||||
private Integer productId;
|
||||
|
||||
/**
|
||||
* 主品sku_id
|
||||
*/
|
||||
@JsonProperty("sku_id")
|
||||
private Integer skuId;
|
||||
|
||||
}
|
||||
@@ -18,4 +18,24 @@ public class OrderCouponInfo implements Serializable {
|
||||
/** 用户优惠券id */
|
||||
@JsonProperty("user_coupon_id")
|
||||
private String userCouponId;
|
||||
|
||||
/**
|
||||
* 优惠券类型
|
||||
* 1 商家优惠
|
||||
* 2 达人优惠
|
||||
* 3 平台优惠
|
||||
* 4 国家补贴
|
||||
* 5 地方补贴
|
||||
*/
|
||||
@JsonProperty("coupon_type")
|
||||
private Integer couponType;
|
||||
|
||||
/** 优惠金额,单位为分,该张优惠券、抵扣该商品的金额 */
|
||||
@JsonProperty("discounted_price")
|
||||
private Integer discountedPrice;
|
||||
|
||||
/** 优惠券id */
|
||||
@JsonProperty("coupon_id")
|
||||
private String couponId;
|
||||
|
||||
}
|
||||
|
||||
@@ -16,12 +16,8 @@ public class OrderPayInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -5085386252699113948L;
|
||||
/** 预支付id */
|
||||
@JsonProperty("prepayId")
|
||||
private String prepayId;
|
||||
|
||||
/** 预支付时间,秒级时间戳 */
|
||||
@JsonProperty("prepay_time")
|
||||
private Long prepayTime;
|
||||
@JsonProperty("payment_method")
|
||||
private Integer paymentMethod;
|
||||
|
||||
/** 支付时间,秒级时间戳 */
|
||||
@JsonProperty("pay_time")
|
||||
|
||||
@@ -107,4 +107,34 @@ public class OrderPriceInfo implements Serializable {
|
||||
@JsonProperty("finder_discounted_price")
|
||||
private Integer finderDiscountedPrice;
|
||||
|
||||
/**
|
||||
* 订单维度会员权益优惠金额
|
||||
*/
|
||||
@JsonProperty("vip_discounted_price")
|
||||
private Integer vipDiscountedPrice;
|
||||
|
||||
/**
|
||||
* 订单维度一起买优惠金额,单位为分
|
||||
*/
|
||||
@JsonProperty("bulkbuy_discounted_price")
|
||||
private Integer bulkbuyDiscountedPrice;
|
||||
|
||||
/**
|
||||
* 订单维度国补优惠金额
|
||||
*/
|
||||
@JsonProperty("national_subsidy_discounted_price")
|
||||
private Integer nationalSubsidyDiscountedPrice;
|
||||
|
||||
/**
|
||||
* 订单维度平台券优惠金额,单位为分
|
||||
*/
|
||||
@JsonProperty("cash_coupon_discounted_price")
|
||||
private Integer cashCouponDiscountedPrice;
|
||||
|
||||
/**
|
||||
* 订单维度地方补贴优惠金额(商家出资),单位为分
|
||||
*/
|
||||
@JsonProperty("national_subsidy_merchant_discounted_price")
|
||||
private Integer nationalSubsidyMerchantDiscountedPrice;
|
||||
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ public class OrderProductInfo implements Serializable {
|
||||
private Integer merchantDiscountedPrice;
|
||||
|
||||
/**
|
||||
* 商家优惠金额,单位为分
|
||||
* 达人优惠金额,单位为分
|
||||
*/
|
||||
@JsonProperty("finder_discounted_price")
|
||||
private Integer finderDiscountedPrice;
|
||||
@@ -186,4 +186,59 @@ public class OrderProductInfo implements Serializable {
|
||||
*/
|
||||
@JsonProperty("is_free_gift")
|
||||
private Boolean freeGift;
|
||||
|
||||
/**
|
||||
* 订单内商品维度会员权益优惠金额,单位为分
|
||||
*/
|
||||
@JsonProperty("vip_discounted_price")
|
||||
private Integer vipDiscountedPrice;
|
||||
|
||||
/**
|
||||
* 商品常量编号,订单内商品唯一标识,下单后不会发生变化
|
||||
*/
|
||||
@JsonProperty("product_unique_id")
|
||||
private String productUniqueId;
|
||||
|
||||
/**
|
||||
* 更换sku信息
|
||||
*/
|
||||
@JsonProperty("change_sku_info")
|
||||
private ChangeSkuInfo changeSkuInfo;
|
||||
|
||||
/**
|
||||
* 赠品信息
|
||||
*/
|
||||
@JsonProperty("free_gift_info")
|
||||
private FreeGiftInfo freeGiftInfo;
|
||||
|
||||
/**
|
||||
* 订单内商品维度一起买优惠金额,单位为分
|
||||
*/
|
||||
@JsonProperty("bulkbuy_discounted_price")
|
||||
private Integer bulkbuyDiscountedPrice;
|
||||
|
||||
/**
|
||||
* 订单内商品维度国补优惠金额,单位为分
|
||||
*/
|
||||
@JsonProperty("national_subsidy_discounted_price")
|
||||
private Integer nationalSubsidyDiscountedPrice;
|
||||
|
||||
/**
|
||||
* 代发相关信息
|
||||
*/
|
||||
@JsonProperty("dropship_info")
|
||||
private DropshipInfo dropshipInfo;
|
||||
|
||||
/**
|
||||
* 是否闪购商品
|
||||
*/
|
||||
@JsonProperty("is_flash_sale")
|
||||
private Boolean flashSale;
|
||||
|
||||
/**
|
||||
* 订单内商品维度地方补贴优惠金额(商家出资),单位为分
|
||||
*/
|
||||
@JsonProperty("national_subsidy_merchant_discounted_price")
|
||||
private Integer nationalSubsidyMerchantDiscountedPrice;
|
||||
|
||||
}
|
||||
|
||||
@@ -139,4 +139,16 @@ public class SpuInfo extends SpuSimpleInfo {
|
||||
/** 尺码表信息 */
|
||||
@JsonProperty("size_chart")
|
||||
private SpuSizeChart sizeChart;
|
||||
|
||||
/** 短标题 */
|
||||
@JsonProperty("short_title")
|
||||
private String shortTitle;
|
||||
|
||||
/** 销量 */
|
||||
@JsonProperty("total_sold_num")
|
||||
private Integer totalSoldNum;
|
||||
|
||||
/** 发布模式,0: 普通模式;1: 极简模式 */
|
||||
@JsonProperty("release_mode")
|
||||
private Integer releaseMode;
|
||||
}
|
||||
|
||||
@@ -232,6 +232,12 @@ public class WxChannelApiUrlConstants {
|
||||
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";
|
||||
}
|
||||
|
||||
/** 纠纷相关接口 */
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>weixin-java-common</artifactId>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -37,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<DocumentBuilder> BUILDER_LOCAL = ThreadLocal.withInitial(() -> {
|
||||
try {
|
||||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
@@ -109,10 +122,10 @@ public class WxCryptUtil {
|
||||
*/
|
||||
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();
|
||||
@@ -320,14 +333,28 @@ public class WxCryptUtil {
|
||||
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不相同的情况 暂时忽略这段判断
|
||||
|
||||
@@ -12,14 +12,14 @@ import java.io.Reader;
|
||||
public class GsonParser {
|
||||
|
||||
public static JsonObject parse(String json) {
|
||||
return JsonParser.parseString(json).getAsJsonObject();
|
||||
return new JsonParser().parse(json).getAsJsonObject();
|
||||
}
|
||||
|
||||
public static JsonObject parse(Reader json) {
|
||||
return JsonParser.parseReader(json).getAsJsonObject();
|
||||
return new JsonParser().parse(json).getAsJsonObject();
|
||||
}
|
||||
|
||||
public static JsonObject parse(JsonReader json) {
|
||||
return JsonParser.parseReader(json).getAsJsonObject();
|
||||
return new JsonParser().parse(json).getAsJsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.WxNetCheckResult;
|
||||
import me.chanjar.weixin.common.bean.menu.WxMenu;
|
||||
import me.chanjar.weixin.common.error.WxError;
|
||||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
|
||||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -25,6 +30,17 @@ public class WxGsonBuilder {
|
||||
INSTANCE.registerTypeAdapter(WxMediaUploadResult.class, new WxMediaUploadResultAdapter());
|
||||
INSTANCE.registerTypeAdapter(WxNetCheckResult.class, new WxNetCheckResultGsonAdapter());
|
||||
|
||||
INSTANCE.setExclusionStrategies(new ExclusionStrategy() {
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipClass(Class<?> aClass) {
|
||||
return aClass == File.class || aClass == ApacheHttpClientBuilder.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Gson create() {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
@@ -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 <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
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");
|
||||
}
|
||||
}
|
||||
141
weixin-java-cp/APPROVAL_WORKFLOW_GUIDE.md
Normal file
141
weixin-java-cp/APPROVAL_WORKFLOW_GUIDE.md
Normal file
@@ -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<String> 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文档。
|
||||
107
weixin-java-cp/INTELLIGENT_ROBOT.md
Normal file
107
weixin-java-cp/INTELLIGENT_ROBOT.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# 企业微信智能机器人接口
|
||||
|
||||
本模块提供企业微信智能机器人相关的API接口实现。
|
||||
|
||||
## 官方文档
|
||||
|
||||
- [企业微信智能机器人接口](https://developer.work.weixin.qq.com/document/path/101039)
|
||||
|
||||
## 接口说明
|
||||
|
||||
### 获取服务实例
|
||||
|
||||
```java
|
||||
WxCpService wxCpService = ...; // 初始化企业微信服务
|
||||
WxCpIntelligentRobotService robotService = wxCpService.getIntelligentRobotService();
|
||||
```
|
||||
|
||||
### 创建智能机器人
|
||||
|
||||
```java
|
||||
WxCpIntelligentRobotCreateRequest request = new WxCpIntelligentRobotCreateRequest();
|
||||
request.setName("我的智能机器人");
|
||||
request.setDescription("这是一个智能客服机器人");
|
||||
request.setAvatar("http://example.com/avatar.jpg");
|
||||
|
||||
WxCpIntelligentRobotCreateResponse response = robotService.createRobot(request);
|
||||
String robotId = response.getRobotId();
|
||||
```
|
||||
|
||||
### 更新智能机器人
|
||||
|
||||
```java
|
||||
WxCpIntelligentRobotUpdateRequest request = new WxCpIntelligentRobotUpdateRequest();
|
||||
request.setRobotId("robot_id_here");
|
||||
request.setName("更新后的机器人名称");
|
||||
request.setDescription("更新后的描述");
|
||||
request.setStatus(1); // 1:启用, 0:停用
|
||||
|
||||
robotService.updateRobot(request);
|
||||
```
|
||||
|
||||
### 查询智能机器人
|
||||
|
||||
```java
|
||||
String robotId = "robot_id_here";
|
||||
WxCpIntelligentRobot robot = robotService.getRobot(robotId);
|
||||
|
||||
System.out.println("机器人名称: " + robot.getName());
|
||||
System.out.println("机器人状态: " + robot.getStatus());
|
||||
```
|
||||
|
||||
### 智能对话
|
||||
|
||||
```java
|
||||
WxCpIntelligentRobotChatRequest request = new WxCpIntelligentRobotChatRequest();
|
||||
request.setRobotId("robot_id_here");
|
||||
request.setUserid("user123");
|
||||
request.setMessage("你好,请问如何使用这个功能?");
|
||||
request.setSessionId("session123"); // 可选,用于保持会话连续性
|
||||
|
||||
WxCpIntelligentRobotChatResponse response = robotService.chat(request);
|
||||
String reply = response.getReply();
|
||||
String sessionId = response.getSessionId();
|
||||
```
|
||||
|
||||
### 重置会话
|
||||
|
||||
```java
|
||||
String robotId = "robot_id_here";
|
||||
String userid = "user123";
|
||||
String sessionId = "session123";
|
||||
|
||||
robotService.resetSession(robotId, userid, sessionId);
|
||||
```
|
||||
|
||||
### 删除智能机器人
|
||||
|
||||
```java
|
||||
String robotId = "robot_id_here";
|
||||
robotService.deleteRobot(robotId);
|
||||
```
|
||||
|
||||
## 主要类说明
|
||||
|
||||
### 请求类
|
||||
|
||||
- `WxCpIntelligentRobotCreateRequest`: 创建机器人请求
|
||||
- `WxCpIntelligentRobotUpdateRequest`: 更新机器人请求
|
||||
- `WxCpIntelligentRobotChatRequest`: 智能对话请求
|
||||
|
||||
### 响应类
|
||||
|
||||
- `WxCpIntelligentRobotCreateResponse`: 创建机器人响应
|
||||
- `WxCpIntelligentRobotChatResponse`: 智能对话响应
|
||||
- `WxCpIntelligentRobot`: 机器人信息实体
|
||||
|
||||
### 服务接口
|
||||
|
||||
- `WxCpIntelligentRobotService`: 智能机器人服务接口
|
||||
- `WxCpIntelligentRobotServiceImpl`: 智能机器人服务实现
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 需要确保企业微信应用具有智能机器人相关权限
|
||||
2. 智能机器人功能可能需要特定的企业微信版本支持
|
||||
3. 会话ID可以用于保持对话的连续性,提升用户体验
|
||||
4. 机器人状态: 0表示停用,1表示启用
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>weixin-java-cp</artifactId>
|
||||
|
||||
@@ -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类型的消息
|
||||
*
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package me.chanjar.weixin.cp.api;
|
||||
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.cp.bean.intelligentrobot.*;
|
||||
|
||||
/**
|
||||
* 企业微信智能机器人接口
|
||||
* 官方文档: https://developer.work.weixin.qq.com/document/path/101039
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
public interface WxCpIntelligentRobotService {
|
||||
|
||||
/**
|
||||
* 创建智能机器人
|
||||
*
|
||||
* @param request 创建请求参数
|
||||
* @return 创建结果
|
||||
* @throws WxErrorException 微信接口异常
|
||||
*/
|
||||
WxCpIntelligentRobotCreateResponse createRobot(WxCpIntelligentRobotCreateRequest request) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* 删除智能机器人
|
||||
*
|
||||
* @param robotId 机器人ID
|
||||
* @throws WxErrorException 微信接口异常
|
||||
*/
|
||||
void deleteRobot(String robotId) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* 更新智能机器人
|
||||
*
|
||||
* @param request 更新请求参数
|
||||
* @throws WxErrorException 微信接口异常
|
||||
*/
|
||||
void updateRobot(WxCpIntelligentRobotUpdateRequest request) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* 查询智能机器人
|
||||
*
|
||||
* @param robotId 机器人ID
|
||||
* @return 机器人信息
|
||||
* @throws WxErrorException 微信接口异常
|
||||
*/
|
||||
WxCpIntelligentRobot getRobot(String robotId) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* 智能机器人会话
|
||||
*
|
||||
* @param request 聊天请求参数
|
||||
* @return 聊天响应
|
||||
* @throws WxErrorException 微信接口异常
|
||||
*/
|
||||
WxCpIntelligentRobotChatResponse chat(WxCpIntelligentRobotChatRequest request) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* 重置智能机器人会话
|
||||
*
|
||||
* @param robotId 机器人ID
|
||||
* @param userid 用户ID
|
||||
* @param sessionId 会话ID
|
||||
* @throws WxErrorException 微信接口异常
|
||||
*/
|
||||
void resetSession(String robotId, String userid, String sessionId) throws WxErrorException;
|
||||
|
||||
}
|
||||
@@ -587,4 +587,11 @@ public interface WxCpService extends WxService {
|
||||
* @return
|
||||
*/
|
||||
WxCpCorpGroupService getCorpGroupService();
|
||||
|
||||
/**
|
||||
* 获取智能机器人服务
|
||||
*
|
||||
* @return 智能机器人服务 intelligent robot service
|
||||
*/
|
||||
WxCpIntelligentRobotService getIntelligentRobotService();
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
|
||||
|
||||
private final WxCpMeetingService meetingService = new WxCpMeetingServiceImpl(this);
|
||||
private final WxCpCorpGroupService corpGroupService = new WxCpCorpGroupServiceImpl(this);
|
||||
private final WxCpIntelligentRobotService intelligentRobotService = new WxCpIntelligentRobotServiceImpl(this);
|
||||
|
||||
/**
|
||||
* 全局的是否正在刷新access token的锁.
|
||||
@@ -702,4 +703,9 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
|
||||
public WxCpCorpGroupService getCorpGroupService() {
|
||||
return corpGroupService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxCpIntelligentRobotService getIntelligentRobotService() {
|
||||
return this.intelligentRobotService;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,11 @@ public class WxCpGroupRobotServiceImpl implements WxCpGroupRobotService {
|
||||
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 class WxCpGroupRobotServiceImpl implements WxCpGroupRobotService {
|
||||
.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()
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package me.chanjar.weixin.cp.api.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.cp.api.WxCpIntelligentRobotService;
|
||||
import me.chanjar.weixin.cp.api.WxCpService;
|
||||
import me.chanjar.weixin.cp.bean.intelligentrobot.*;
|
||||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
||||
|
||||
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.IntelligentRobot.*;
|
||||
|
||||
/**
|
||||
* 企业微信智能机器人接口实现
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class WxCpIntelligentRobotServiceImpl implements WxCpIntelligentRobotService {
|
||||
|
||||
private final WxCpService cpService;
|
||||
|
||||
@Override
|
||||
public WxCpIntelligentRobotCreateResponse createRobot(WxCpIntelligentRobotCreateRequest request) throws WxErrorException {
|
||||
String responseText = this.cpService.post(CREATE_ROBOT, request.toJson());
|
||||
return WxCpIntelligentRobotCreateResponse.fromJson(responseText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRobot(String robotId) throws WxErrorException {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("robot_id", robotId);
|
||||
this.cpService.post(DELETE_ROBOT, jsonObject.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRobot(WxCpIntelligentRobotUpdateRequest request) throws WxErrorException {
|
||||
this.cpService.post(UPDATE_ROBOT, request.toJson());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxCpIntelligentRobot getRobot(String robotId) throws WxErrorException {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("robot_id", robotId);
|
||||
String responseText = this.cpService.post(GET_ROBOT, jsonObject.toString());
|
||||
return WxCpIntelligentRobot.fromJson(responseText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxCpIntelligentRobotChatResponse chat(WxCpIntelligentRobotChatRequest request) throws WxErrorException {
|
||||
String responseText = this.cpService.post(CHAT, request.toJson());
|
||||
return WxCpIntelligentRobotChatResponse.fromJson(responseText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetSession(String robotId, String userid, String sessionId) throws WxErrorException {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("robot_id", robotId);
|
||||
jsonObject.addProperty("userid", userid);
|
||||
jsonObject.addProperty("session_id", sessionId);
|
||||
this.cpService.post(RESET_SESSION, jsonObject.toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import me.chanjar.weixin.common.util.json.GsonParser;
|
||||
import me.chanjar.weixin.cp.api.WxCpMsgAuditService;
|
||||
import me.chanjar.weixin.cp.api.WxCpService;
|
||||
import me.chanjar.weixin.cp.bean.msgaudit.*;
|
||||
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
|
||||
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
|
||||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -35,20 +36,59 @@ import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.MsgAudit.*;
|
||||
public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService {
|
||||
private final WxCpService cpService;
|
||||
|
||||
/**
|
||||
* SDK初始化有效期,根据企微文档为7200秒
|
||||
*/
|
||||
private static final int SDK_EXPIRES_TIME = 7200;
|
||||
|
||||
@Override
|
||||
public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, String passwd,
|
||||
@NonNull long timeout) throws Exception {
|
||||
String configPath = cpService.getWxCpConfigStorage().getMsgAuditLibPath();
|
||||
// 获取或初始化SDK
|
||||
long sdk = this.initSdk();
|
||||
|
||||
long slice = Finance.NewSlice();
|
||||
long ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
|
||||
if (ret != 0) {
|
||||
Finance.FreeSlice(slice);
|
||||
throw new WxErrorException("getchatdata err ret " + ret);
|
||||
}
|
||||
|
||||
// 拉取会话存档
|
||||
String content = Finance.GetContentFromSlice(slice);
|
||||
Finance.FreeSlice(slice);
|
||||
WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content);
|
||||
if (chatDatas.getErrCode().intValue() != 0) {
|
||||
throw new WxErrorException(chatDatas.toJson());
|
||||
}
|
||||
|
||||
chatDatas.setSdk(sdk);
|
||||
return chatDatas;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或初始化SDK,如果SDK已过期则重新初始化
|
||||
*
|
||||
* @return sdk id
|
||||
* @throws WxErrorException 初始化失败时抛出异常
|
||||
*/
|
||||
private synchronized long initSdk() throws WxErrorException {
|
||||
WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
|
||||
|
||||
// 检查SDK是否已缓存且未过期
|
||||
if (!configStorage.isMsgAuditSdkExpired()) {
|
||||
long cachedSdk = configStorage.getMsgAuditSdk();
|
||||
if (cachedSdk > 0) {
|
||||
return cachedSdk;
|
||||
}
|
||||
}
|
||||
|
||||
// SDK未初始化或已过期,需要重新初始化
|
||||
String configPath = configStorage.getMsgAuditLibPath();
|
||||
if (StringUtils.isEmpty(configPath)) {
|
||||
throw new WxErrorException("请配置会话存档sdk文件的路径,不要配错了!!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整的文件库路径:
|
||||
*
|
||||
* /www/osfile/libcrypto-1_1-x64.dll,libssl-1_1-x64.dll,libcurl-x64.dll,WeWorkFinanceSdk.dll,
|
||||
* libWeWorkFinanceSdk_Java.so
|
||||
*/
|
||||
// 替换斜杠
|
||||
String replacePath = configPath.replace("\\", "/");
|
||||
// 获取最后一个斜杠的下标,用作分割路径
|
||||
@@ -79,36 +119,22 @@ public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService {
|
||||
|
||||
Finance.loadingLibraries(osLib, prefixPath);
|
||||
long sdk = Finance.NewSdk();
|
||||
//因为会话存档单独有个secret,优先使用会话存档的secret
|
||||
String msgAuditSecret = cpService.getWxCpConfigStorage().getMsgAuditSecret();
|
||||
// 因为会话存档单独有个secret,优先使用会话存档的secret
|
||||
String msgAuditSecret = configStorage.getMsgAuditSecret();
|
||||
if (StringUtils.isEmpty(msgAuditSecret)) {
|
||||
msgAuditSecret = cpService.getWxCpConfigStorage().getCorpSecret();
|
||||
msgAuditSecret = configStorage.getCorpSecret();
|
||||
}
|
||||
long ret = Finance.Init(sdk, cpService.getWxCpConfigStorage().getCorpId(), msgAuditSecret);
|
||||
long ret = Finance.Init(sdk, configStorage.getCorpId(), msgAuditSecret);
|
||||
if (ret != 0) {
|
||||
Finance.DestroySdk(sdk);
|
||||
throw new WxErrorException("init sdk err ret " + ret);
|
||||
}
|
||||
|
||||
long slice = Finance.NewSlice();
|
||||
ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
|
||||
if (ret != 0) {
|
||||
Finance.FreeSlice(slice);
|
||||
Finance.DestroySdk(sdk);
|
||||
throw new WxErrorException("getchatdata err ret " + ret);
|
||||
}
|
||||
// 缓存SDK
|
||||
configStorage.updateMsgAuditSdk(sdk, SDK_EXPIRES_TIME);
|
||||
log.debug("初始化会话存档SDK成功,sdk={}", sdk);
|
||||
|
||||
// 拉取会话存档
|
||||
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());
|
||||
}
|
||||
|
||||
chatDatas.setSdk(sdk);
|
||||
return chatDatas;
|
||||
return sdk;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -128,36 +154,27 @@ public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService {
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
public String decryptChatData(long sdk, WxCpChatDatas.WxCpChatData chatData, Integer pkcs1) throws Exception {
|
||||
/**
|
||||
* 企业获取的会话内容,使用企业自行配置的消息加密公钥进行加密,企业可用自行保存的私钥解开会话内容数据。
|
||||
* msgAuditPriKey 会话存档私钥不能为空
|
||||
*/
|
||||
// 企业获取的会话内容,使用企业自行配置的消息加密公钥进行加密,企业可用自行保存的私钥解开会话内容数据。
|
||||
// msgAuditPriKey 会话存档私钥不能为空
|
||||
String priKey = cpService.getWxCpConfigStorage().getMsgAuditPriKey();
|
||||
if (StringUtils.isEmpty(priKey)) {
|
||||
throw new WxErrorException("请配置会话存档私钥【msgAuditPriKey】");
|
||||
}
|
||||
|
||||
String decryptByPriKey = WxCpCryptUtil.decryptPriKey(chatData.getEncryptRandomKey(), priKey, pkcs1);
|
||||
/**
|
||||
* 每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。
|
||||
*/
|
||||
// 每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。
|
||||
long msg = Finance.NewSlice();
|
||||
|
||||
/**
|
||||
* 解密会话存档内容
|
||||
* sdk不会要求用户传入rsa私钥,保证用户会话存档数据只有自己能够解密。
|
||||
* 此处需要用户先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk来解密encrypt_chat_msg获取会话存档明文。
|
||||
*/
|
||||
// 解密会话存档内容
|
||||
// sdk不会要求用户传入rsa私钥,保证用户会话存档数据只有自己能够解密。
|
||||
// 此处需要用户先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk来解密encrypt_chat_msg获取会话存档明文。
|
||||
int ret = Finance.DecryptData(sdk, decryptByPriKey, chatData.getEncryptChatMsg(), msg);
|
||||
if (ret != 0) {
|
||||
Finance.FreeSlice(msg);
|
||||
Finance.DestroySdk(sdk);
|
||||
throw new WxErrorException("msg err ret " + ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 明文
|
||||
*/
|
||||
// 明文
|
||||
String plainText = Finance.GetContentFromSlice(msg);
|
||||
Finance.FreeSlice(msg);
|
||||
return plainText;
|
||||
@@ -209,7 +226,6 @@ public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,12 @@ public class WxCpContactWayList extends WxCpBaseResp implements Serializable {
|
||||
@SerializedName("contact_way")
|
||||
private List<ContactWay> contactWay;
|
||||
|
||||
/**
|
||||
* 分页参数,用于查询下一个分页的数据,为空时表示没有更多的分页
|
||||
*/
|
||||
@SerializedName("next_cursor")
|
||||
private String nextCursor;
|
||||
|
||||
/**
|
||||
* The type Contact way.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package me.chanjar.weixin.cp.bean.intelligentrobot;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import me.chanjar.weixin.cp.bean.WxCpBaseResp;
|
||||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 智能机器人信息
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WxCpIntelligentRobot extends WxCpBaseResp implements Serializable {
|
||||
private static final long serialVersionUID = -1L;
|
||||
|
||||
/**
|
||||
* 机器人ID
|
||||
*/
|
||||
@SerializedName("robot_id")
|
||||
private String robotId;
|
||||
|
||||
/**
|
||||
* 机器人名称
|
||||
*/
|
||||
@SerializedName("name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 机器人描述
|
||||
*/
|
||||
@SerializedName("description")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 机器人头像
|
||||
*/
|
||||
@SerializedName("avatar")
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 机器人状态 0:停用 1:启用
|
||||
*/
|
||||
@SerializedName("status")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@SerializedName("create_time")
|
||||
private Long createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@SerializedName("update_time")
|
||||
private Long updateTime;
|
||||
|
||||
/**
|
||||
* From json wx cp intelligent robot.
|
||||
*
|
||||
* @param json the json
|
||||
* @return the wx cp intelligent robot
|
||||
*/
|
||||
public static WxCpIntelligentRobot fromJson(String json) {
|
||||
return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobot.class);
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return WxCpGsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package me.chanjar.weixin.cp.bean.intelligentrobot;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Data;
|
||||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 智能机器人聊天请求
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@Data
|
||||
public class WxCpIntelligentRobotChatRequest implements Serializable {
|
||||
private static final long serialVersionUID = -1L;
|
||||
|
||||
/**
|
||||
* 机器人ID
|
||||
*/
|
||||
@SerializedName("robot_id")
|
||||
private String robotId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@SerializedName("userid")
|
||||
private String userid;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
@SerializedName("message")
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 会话ID,可选,用于保持会话连续性
|
||||
*/
|
||||
@SerializedName("session_id")
|
||||
private String sessionId;
|
||||
|
||||
public String toJson() {
|
||||
return WxCpGsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
public static WxCpIntelligentRobotChatRequest fromJson(String json) {
|
||||
return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotChatRequest.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package me.chanjar.weixin.cp.bean.intelligentrobot;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import me.chanjar.weixin.cp.bean.WxCpBaseResp;
|
||||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 智能机器人聊天响应
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WxCpIntelligentRobotChatResponse extends WxCpBaseResp implements Serializable {
|
||||
private static final long serialVersionUID = -1L;
|
||||
|
||||
/**
|
||||
* 机器人回复内容
|
||||
*/
|
||||
@SerializedName("reply")
|
||||
private String reply;
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
@SerializedName("session_id")
|
||||
private String sessionId;
|
||||
|
||||
/**
|
||||
* 消息ID
|
||||
*/
|
||||
@SerializedName("msg_id")
|
||||
private String msgId;
|
||||
|
||||
public static WxCpIntelligentRobotChatResponse fromJson(String json) {
|
||||
return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotChatResponse.class);
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return WxCpGsonBuilder.create().toJson(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package me.chanjar.weixin.cp.bean.intelligentrobot;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Data;
|
||||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 创建智能机器人请求
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@Data
|
||||
public class WxCpIntelligentRobotCreateRequest implements Serializable {
|
||||
private static final long serialVersionUID = -1L;
|
||||
|
||||
/**
|
||||
* 机器人名称
|
||||
*/
|
||||
@SerializedName("name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 机器人描述
|
||||
*/
|
||||
@SerializedName("description")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 机器人头像
|
||||
*/
|
||||
@SerializedName("avatar")
|
||||
private String avatar;
|
||||
|
||||
public String toJson() {
|
||||
return WxCpGsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
public static WxCpIntelligentRobotCreateRequest fromJson(String json) {
|
||||
return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotCreateRequest.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package me.chanjar.weixin.cp.bean.intelligentrobot;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import me.chanjar.weixin.cp.bean.WxCpBaseResp;
|
||||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 创建智能机器人响应
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WxCpIntelligentRobotCreateResponse extends WxCpBaseResp implements Serializable {
|
||||
private static final long serialVersionUID = -1L;
|
||||
|
||||
/**
|
||||
* 机器人ID
|
||||
*/
|
||||
@SerializedName("robot_id")
|
||||
private String robotId;
|
||||
|
||||
public static WxCpIntelligentRobotCreateResponse fromJson(String json) {
|
||||
return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotCreateResponse.class);
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return WxCpGsonBuilder.create().toJson(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package me.chanjar.weixin.cp.bean.intelligentrobot;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Data;
|
||||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 更新智能机器人请求
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@Data
|
||||
public class WxCpIntelligentRobotUpdateRequest implements Serializable {
|
||||
private static final long serialVersionUID = -1L;
|
||||
|
||||
/**
|
||||
* 机器人ID
|
||||
*/
|
||||
@SerializedName("robot_id")
|
||||
private String robotId;
|
||||
|
||||
/**
|
||||
* 机器人名称
|
||||
*/
|
||||
@SerializedName("name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 机器人描述
|
||||
*/
|
||||
@SerializedName("description")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 机器人头像
|
||||
*/
|
||||
@SerializedName("avatar")
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 机器人状态 0:停用 1:启用
|
||||
*/
|
||||
@SerializedName("status")
|
||||
private Integer status;
|
||||
|
||||
public String toJson() {
|
||||
return WxCpGsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
public static WxCpIntelligentRobotUpdateRequest fromJson(String json) {
|
||||
return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotUpdateRequest.class);
|
||||
}
|
||||
}
|
||||
@@ -252,6 +252,12 @@ public class WxCpGroupRobotMessage implements Serializable {
|
||||
messageJson.add("markdown", text);
|
||||
break;
|
||||
}
|
||||
case MARKDOWN_V2: {
|
||||
JsonObject text = new JsonObject();
|
||||
text.addProperty("content", this.getContent());
|
||||
messageJson.add("markdown_v2", text);
|
||||
break;
|
||||
}
|
||||
case IMAGE: {
|
||||
JsonObject text = new JsonObject();
|
||||
text.addProperty("base64", this.getBase64());
|
||||
|
||||
@@ -44,7 +44,13 @@ public class WxCpOaApplyEventRequest implements Serializable {
|
||||
private Integer chooseDepartment;
|
||||
|
||||
/**
|
||||
* 审批流程信息,用于指定审批申请的审批流程,支持单人审批、多人会签、多人或签,可能有多个审批节点,仅use_template_approver为0时生效。
|
||||
* 审批流程信息(新版流程列表),用于指定审批申请的审批流程,支持单人审批、多人会签、多人或签,可能有多个审批节点,仅use_template_approver为0时生效。
|
||||
*/
|
||||
@SerializedName("process")
|
||||
private Process process;
|
||||
|
||||
/**
|
||||
* 审批流程信息(旧版),用于指定审批申请的审批流程,支持单人审批、多人会签、多人或签,可能有多个审批节点,仅use_template_approver为0时生效。
|
||||
*/
|
||||
@SerializedName("approver")
|
||||
private List<Approver> approvers;
|
||||
@@ -118,4 +124,46 @@ public class WxCpOaApplyEventRequest implements Serializable {
|
||||
private List<ApplyDataContent> contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批流程信息(新版).
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class Process implements Serializable {
|
||||
private static final long serialVersionUID = 4758206091546930988L;
|
||||
|
||||
/**
|
||||
* 审批流程节点列表,当use_template_approver为0时必填
|
||||
*/
|
||||
@SerializedName("node_list")
|
||||
private List<ProcessNode> nodeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批流程节点.
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class ProcessNode implements Serializable {
|
||||
private static final long serialVersionUID = 1758206091546930988L;
|
||||
|
||||
/**
|
||||
* 节点类型:1-审批人,2-抄送人,3-抄送人
|
||||
*/
|
||||
@SerializedName("type")
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 多人审批方式:1-全签,2-或签,3-依次审批
|
||||
*/
|
||||
@SerializedName("apv_rel")
|
||||
private Integer apvRel;
|
||||
|
||||
/**
|
||||
* 审批节点审批人userid列表,若为多人会签、多人或签,需填写每个人的userid
|
||||
*/
|
||||
@SerializedName("userid")
|
||||
private String[] userIds;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -260,7 +260,36 @@ public interface WxCpConfigStorage {
|
||||
|
||||
/**
|
||||
* 获取会话存档的secret
|
||||
*
|
||||
* @return msg audit secret
|
||||
*/
|
||||
String getMsgAuditSecret();
|
||||
|
||||
/**
|
||||
* 获取会话存档SDK
|
||||
* 会话存档SDK初始化后有效期为7200秒,无需每次重新初始化
|
||||
*
|
||||
* @return sdk id,如果未初始化或已过期返回0
|
||||
*/
|
||||
long getMsgAuditSdk();
|
||||
|
||||
/**
|
||||
* 检查会话存档SDK是否已过期
|
||||
*
|
||||
* @return true: 已过期, false: 未过期
|
||||
*/
|
||||
boolean isMsgAuditSdkExpired();
|
||||
|
||||
/**
|
||||
* 更新会话存档SDK
|
||||
*
|
||||
* @param sdk sdk id
|
||||
* @param expiresInSeconds 过期时间(秒)
|
||||
*/
|
||||
void updateMsgAuditSdk(long sdk, int expiresInSeconds);
|
||||
|
||||
/**
|
||||
* 使会话存档SDK过期
|
||||
*/
|
||||
void expireMsgAuditSdk();
|
||||
}
|
||||
|
||||
@@ -49,6 +49,11 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
|
||||
private volatile String msgAuditSecret;
|
||||
private volatile String msgAuditPriKey;
|
||||
private volatile String msgAuditLibPath;
|
||||
/**
|
||||
* 会话存档SDK及其过期时间
|
||||
*/
|
||||
private volatile long msgAuditSdk;
|
||||
private volatile long msgAuditSdkExpiresTime;
|
||||
private volatile String oauth2redirectUri;
|
||||
private volatile String httpProxyHost;
|
||||
private volatile int httpProxyPort;
|
||||
@@ -444,10 +449,34 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
|
||||
|
||||
/**
|
||||
* 设置会话存档secret
|
||||
* @param msgAuditSecret
|
||||
*
|
||||
* @param msgAuditSecret the msg audit secret
|
||||
* @return this
|
||||
*/
|
||||
public WxCpDefaultConfigImpl setMsgAuditSecret(String msgAuditSecret) {
|
||||
this.msgAuditSecret = msgAuditSecret;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMsgAuditSdk() {
|
||||
return this.msgAuditSdk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMsgAuditSdkExpired() {
|
||||
return System.currentTimeMillis() > this.msgAuditSdkExpiresTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void updateMsgAuditSdk(long sdk, int expiresInSeconds) {
|
||||
this.msgAuditSdk = sdk;
|
||||
// 预留200秒的时间
|
||||
this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireMsgAuditSdk() {
|
||||
this.msgAuditSdkExpiresTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,11 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
|
||||
private volatile File tmpDirFile;
|
||||
private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
|
||||
private volatile String webhookKey;
|
||||
/**
|
||||
* 会话存档SDK及其过期时间(SDK是本地JVM变量,不适合存储到Redis)
|
||||
*/
|
||||
private volatile long msgAuditSdk;
|
||||
private volatile long msgAuditSdkExpiresTime;
|
||||
|
||||
/**
|
||||
* Instantiates a new Wx cp redis config.
|
||||
@@ -470,4 +475,26 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
|
||||
public String getMsgAuditSecret() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMsgAuditSdk() {
|
||||
return this.msgAuditSdk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMsgAuditSdkExpired() {
|
||||
return System.currentTimeMillis() > this.msgAuditSdkExpiresTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void updateMsgAuditSdk(long sdk, int expiresInSeconds) {
|
||||
this.msgAuditSdk = sdk;
|
||||
// 预留200秒的时间
|
||||
this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireMsgAuditSdk() {
|
||||
this.msgAuditSdkExpiresTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1631,4 +1631,40 @@ public interface WxCpApiPathConsts {
|
||||
*/
|
||||
String CONVERT_TMP_EXTERNAL_USER_ID = "/cgi-bin/idconvert/convert_tmp_external_userid";
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能机器人相关接口
|
||||
* 官方文档: https://developer.work.weixin.qq.com/document/path/101039
|
||||
*/
|
||||
interface IntelligentRobot {
|
||||
/**
|
||||
* 创建智能机器人
|
||||
*/
|
||||
String CREATE_ROBOT = "/cgi-bin/intelligent_robot/create";
|
||||
|
||||
/**
|
||||
* 删除智能机器人
|
||||
*/
|
||||
String DELETE_ROBOT = "/cgi-bin/intelligent_robot/delete";
|
||||
|
||||
/**
|
||||
* 更新智能机器人
|
||||
*/
|
||||
String UPDATE_ROBOT = "/cgi-bin/intelligent_robot/update";
|
||||
|
||||
/**
|
||||
* 查询智能机器人
|
||||
*/
|
||||
String GET_ROBOT = "/cgi-bin/intelligent_robot/get";
|
||||
|
||||
/**
|
||||
* 智能机器人会话
|
||||
*/
|
||||
String CHAT = "/cgi-bin/intelligent_robot/chat";
|
||||
|
||||
/**
|
||||
* 重置智能机器人会话
|
||||
*/
|
||||
String RESET_SESSION = "/cgi-bin/intelligent_robot/reset_session";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,6 +630,11 @@ public class WxCpConsts {
|
||||
*/
|
||||
public static final String MARKDOWN = "markdown";
|
||||
|
||||
/**
|
||||
* markdown_v2消息.
|
||||
*/
|
||||
public static final String MARKDOWN_V2 = "markdown_v2";
|
||||
|
||||
/**
|
||||
* 图文消息(点击跳转到外链).
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package me.chanjar.weixin.cp.util.json;
|
||||
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.FieldAttributes;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import me.chanjar.weixin.common.bean.menu.WxMenu;
|
||||
import me.chanjar.weixin.common.error.WxError;
|
||||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
|
||||
import me.chanjar.weixin.common.util.json.WxErrorAdapter;
|
||||
import me.chanjar.weixin.cp.bean.WxCpChat;
|
||||
import me.chanjar.weixin.cp.bean.WxCpDepart;
|
||||
@@ -11,6 +14,7 @@ import me.chanjar.weixin.cp.bean.WxCpTag;
|
||||
import me.chanjar.weixin.cp.bean.WxCpUser;
|
||||
import me.chanjar.weixin.cp.bean.kf.WxCpKfGetCorpStatisticResp;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -32,6 +36,18 @@ public class WxCpGsonBuilder {
|
||||
INSTANCE.registerTypeAdapter(WxMenu.class, new WxCpMenuGsonAdapter());
|
||||
INSTANCE.registerTypeAdapter(WxCpTag.class, new WxCpTagGsonAdapter());
|
||||
INSTANCE.registerTypeAdapter(WxCpKfGetCorpStatisticResp.StatisticList.class, new StatisticListAdapter());
|
||||
|
||||
INSTANCE.setExclusionStrategies(new ExclusionStrategy() {
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipClass(Class<?> aClass) {
|
||||
return aClass == File.class || aClass == ApacheHttpClientBuilder.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -281,7 +281,12 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
|
||||
}
|
||||
addProperty(o, MAIN_DEPARTMENT, user.getMainDepartment());
|
||||
|
||||
addArrayProperty(o, DIRECT_LEADER, user.getDirectLeader());
|
||||
// Special handling for directLeader: include empty arrays to support WeChat Work API reset functionality
|
||||
if (user.getDirectLeader() != null) {
|
||||
JsonArray directLeaderArray = new JsonArray();
|
||||
Arrays.stream(user.getDirectLeader()).forEach(directLeaderArray::add);
|
||||
o.add(DIRECT_LEADER, directLeaderArray);
|
||||
}
|
||||
|
||||
if (!user.getExtAttrs().isEmpty()) {
|
||||
JsonArray attrsJsonArray = new JsonArray();
|
||||
|
||||
@@ -64,6 +64,51 @@ public class WxCpGroupRobotServiceImplTest {
|
||||
robotService.sendMarkdown(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test send mark down v2.
|
||||
*
|
||||
* @throws WxErrorException the wx error exception
|
||||
*/
|
||||
@Test
|
||||
public void testSendMarkDownV2() throws WxErrorException {
|
||||
String content = "# 一、标题\n" +
|
||||
"## 二级标题\n" +
|
||||
"### 三级标题\n" +
|
||||
"# 二、字体\n" +
|
||||
"*斜体*\n" +
|
||||
"\n" +
|
||||
"**加粗**\n" +
|
||||
"# 三、列表 \n" +
|
||||
"- 无序列表 1 \n" +
|
||||
"- 无序列表 2\n" +
|
||||
" - 无序列表 2.1\n" +
|
||||
" - 无序列表 2.2\n" +
|
||||
"1. 有序列表 1\n" +
|
||||
"2. 有序列表 2\n" +
|
||||
"# 四、引用\n" +
|
||||
"> 一级引用\n" +
|
||||
">>二级引用\n" +
|
||||
">>>三级引用\n" +
|
||||
"# 五、链接\n" +
|
||||
"[这是一个链接](https://work.weixin.qq.com/api/doc)\n" +
|
||||
"\n" +
|
||||
"# 六、分割线\n" +
|
||||
"\n" +
|
||||
"---\n" +
|
||||
"# 七、代码\n" +
|
||||
"`这是行内代码`\n" +
|
||||
"```\n" +
|
||||
"这是独立代码块\n" +
|
||||
"```\n" +
|
||||
"\n" +
|
||||
"# 八、表格\n" +
|
||||
"| 姓名 | 文化衫尺寸 | 收货地址 |\n" +
|
||||
"| :----- | :----: | -------: |\n" +
|
||||
"| 张三 | S | 广州 |\n" +
|
||||
"| 李四 | L | 深圳 |";
|
||||
robotService.sendMarkdownV2(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test send image.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package me.chanjar.weixin.cp.api.impl;
|
||||
|
||||
import me.chanjar.weixin.cp.api.WxCpService;
|
||||
import me.chanjar.weixin.cp.api.ApiTestModule;
|
||||
import me.chanjar.weixin.cp.bean.intelligentrobot.*;
|
||||
import org.testng.annotations.Guice;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* 智能机器人接口测试
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@Test
|
||||
@Guice(modules = ApiTestModule.class)
|
||||
public class WxCpIntelligentRobotServiceImplTest {
|
||||
|
||||
@Inject
|
||||
private WxCpService wxCpService;
|
||||
|
||||
@Test
|
||||
public void testCreateRobot() {
|
||||
// 测试创建智能机器人请求对象创建
|
||||
WxCpIntelligentRobotCreateRequest request = new WxCpIntelligentRobotCreateRequest();
|
||||
request.setName("测试机器人");
|
||||
request.setDescription("这是一个测试的智能机器人");
|
||||
request.setAvatar("avatar_url");
|
||||
|
||||
// 验证JSON序列化
|
||||
String json = request.toJson();
|
||||
assert json.contains("测试机器人");
|
||||
assert json.contains("这是一个测试的智能机器人");
|
||||
|
||||
// 验证反序列化
|
||||
WxCpIntelligentRobotCreateRequest fromJson = WxCpIntelligentRobotCreateRequest.fromJson(json);
|
||||
assert fromJson.getName().equals("测试机器人");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatRequest() {
|
||||
// 测试聊天请求对象创建
|
||||
WxCpIntelligentRobotChatRequest request = new WxCpIntelligentRobotChatRequest();
|
||||
request.setRobotId("robot123");
|
||||
request.setUserid("user123");
|
||||
request.setMessage("你好,机器人");
|
||||
request.setSessionId("session123");
|
||||
|
||||
// 验证JSON序列化
|
||||
String json = request.toJson();
|
||||
assert json.contains("robot123");
|
||||
assert json.contains("你好,机器人");
|
||||
|
||||
// 验证反序列化
|
||||
WxCpIntelligentRobotChatRequest fromJson = WxCpIntelligentRobotChatRequest.fromJson(json);
|
||||
assert fromJson.getRobotId().equals("robot123");
|
||||
assert fromJson.getMessage().equals("你好,机器人");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRequest() {
|
||||
// 测试更新请求对象创建
|
||||
WxCpIntelligentRobotUpdateRequest request = new WxCpIntelligentRobotUpdateRequest();
|
||||
request.setRobotId("robot123");
|
||||
request.setName("更新后的机器人");
|
||||
request.setDescription("更新后的描述");
|
||||
request.setStatus(1);
|
||||
|
||||
// 验证JSON序列化
|
||||
String json = request.toJson();
|
||||
assert json.contains("robot123");
|
||||
assert json.contains("更新后的机器人");
|
||||
|
||||
// 验证反序列化
|
||||
WxCpIntelligentRobotUpdateRequest fromJson = WxCpIntelligentRobotUpdateRequest.fromJson(json);
|
||||
assert fromJson.getRobotId().equals("robot123");
|
||||
assert fromJson.getName().equals("更新后的机器人");
|
||||
assert fromJson.getStatus().equals(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceIntegration() {
|
||||
// 验证服务可以正确获取
|
||||
assert this.wxCpService.getIntelligentRobotService() != null;
|
||||
assert this.wxCpService.getIntelligentRobotService() instanceof WxCpIntelligentRobotServiceImpl;
|
||||
}
|
||||
}
|
||||
@@ -95,4 +95,99 @@ public class WxCpOaApplyEventRequestTest {
|
||||
|
||||
assertThat(request.toJson()).isEqualTo(GsonParser.parse(json).toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to json with process.
|
||||
*/
|
||||
@Test
|
||||
public void testToJsonWithProcess() {
|
||||
String json = "{\n" +
|
||||
" \"creator_userid\": \"WangXiaoMing\",\n" +
|
||||
" \"template_id\": \"3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioeFXkaaa\",\n" +
|
||||
" \"use_template_approver\":0,\n" +
|
||||
" \"process\": {\n" +
|
||||
" \"node_list\": [\n" +
|
||||
" {\n" +
|
||||
" \"type\": 1,\n" +
|
||||
" \"apv_rel\": 2,\n" +
|
||||
" \"userid\": [\"WuJunJie\",\"WangXiaoMing\"]\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"type\": 1,\n" +
|
||||
" \"apv_rel\": 1,\n" +
|
||||
" \"userid\": [\"LiuXiaoGang\"]\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"type\": 2,\n" +
|
||||
" \"userid\": [\"ZhangSan\",\"LiSi\"]\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
" },\n" +
|
||||
" \"apply_data\": {\n" +
|
||||
" \"contents\": [\n" +
|
||||
" {\n" +
|
||||
" \"control\": \"Text\",\n" +
|
||||
" \"id\": \"Text-15111111111\",\n" +
|
||||
" \"value\": {\n" +
|
||||
" \"text\": \"文本填写的内容\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
" },\n" +
|
||||
" \"summary_list\": [\n" +
|
||||
" {\n" +
|
||||
" \"summary_info\": [{\n" +
|
||||
" \"text\": \"摘要第1行\",\n" +
|
||||
" \"lang\": \"zh_CN\"\n" +
|
||||
" }]\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"summary_info\": [{\n" +
|
||||
" \"text\": \"摘要第2行\",\n" +
|
||||
" \"lang\": \"zh_CN\"\n" +
|
||||
" }]\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"summary_info\": [{\n" +
|
||||
" \"text\": \"摘要第3行\",\n" +
|
||||
" \"lang\": \"zh_CN\"\n" +
|
||||
" }]\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}";
|
||||
|
||||
WxCpOaApplyEventRequest request = new WxCpOaApplyEventRequest();
|
||||
request.setCreatorUserId("WangXiaoMing")
|
||||
.setTemplateId("3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioeFXkaaa")
|
||||
.setUseTemplateApprover(0)
|
||||
.setProcess(new WxCpOaApplyEventRequest.Process()
|
||||
.setNodeList(Arrays.asList(
|
||||
new WxCpOaApplyEventRequest.ProcessNode()
|
||||
.setType(1)
|
||||
.setApvRel(2)
|
||||
.setUserIds(new String[]{"WuJunJie", "WangXiaoMing"}),
|
||||
new WxCpOaApplyEventRequest.ProcessNode()
|
||||
.setType(1)
|
||||
.setApvRel(1)
|
||||
.setUserIds(new String[]{"LiuXiaoGang"}),
|
||||
new WxCpOaApplyEventRequest.ProcessNode()
|
||||
.setType(2)
|
||||
.setUserIds(new String[]{"ZhangSan", "LiSi"})
|
||||
)))
|
||||
.setApplyData(new WxCpOaApplyEventRequest.ApplyData()
|
||||
.setContents(Collections.singletonList(new ApplyDataContent()
|
||||
.setControl("Text").setId("Text-15111111111").setValue(new ContentValue().setText("文本填写的内容")))))
|
||||
.setSummaryList(Arrays.asList(new SummaryInfo()
|
||||
.setSummaryInfoData(Collections.singletonList(new SummaryInfo.SummaryInfoData().setLang("zh_CN").setText(
|
||||
"摘要第1行"))),
|
||||
new SummaryInfo()
|
||||
.setSummaryInfoData(Collections.singletonList(new SummaryInfo.SummaryInfoData().setLang("zh_CN").setText(
|
||||
"摘要第2行"))),
|
||||
new SummaryInfo()
|
||||
.setSummaryInfoData(Collections.singletonList(new SummaryInfo.SummaryInfoData().setLang("zh_CN").setText(
|
||||
"摘要第3行")))))
|
||||
;
|
||||
|
||||
assertThat(request.toJson()).isEqualTo(GsonParser.parse(json).toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
package me.chanjar.weixin.cp.demo;
|
||||
|
||||
import me.chanjar.weixin.cp.api.WxCpService;
|
||||
import me.chanjar.weixin.cp.bean.oa.WxCpApprovalDetailResult;
|
||||
import me.chanjar.weixin.cp.bean.oa.WxCpApprovalInfo;
|
||||
import me.chanjar.weixin.cp.bean.oa.WxCpOaApplyEventRequest;
|
||||
import me.chanjar.weixin.cp.bean.oa.applydata.ApplyDataContent;
|
||||
import me.chanjar.weixin.cp.bean.oa.applydata.ContentValue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 企业微信流程审批功能演示代码
|
||||
* WeChat Enterprise Workflow Approval Demo
|
||||
*
|
||||
* 演示如何使用WxJava SDK中已实现的完整审批流程功能
|
||||
* Demonstrates how to use the comprehensive approval workflow features already implemented in WxJava SDK
|
||||
*
|
||||
* 文档参考 Documentation Reference: https://work.weixin.qq.com/api/doc/90000/90135/91853
|
||||
*/
|
||||
public class WxCpApprovalWorkflowDemo {
|
||||
|
||||
private WxCpService wxCpService;
|
||||
|
||||
public WxCpApprovalWorkflowDemo(WxCpService wxCpService) {
|
||||
this.wxCpService = wxCpService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例1: 提交审批申请
|
||||
* Example 1: Submit Approval Application
|
||||
* API: /cgi-bin/oa/applyevent (Document 91853)
|
||||
*/
|
||||
public String submitApprovalApplication() throws Exception {
|
||||
// 构建审批申请请求
|
||||
WxCpOaApplyEventRequest request = new WxCpOaApplyEventRequest()
|
||||
.setCreatorUserId("creator_user_id") // 申请人userid
|
||||
.setTemplateId("3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioUBGCNS") // 模板id
|
||||
.setUseTemplateApprover(0) // 不使用模板中的审批流
|
||||
.setApprovers(Arrays.asList(
|
||||
new WxCpOaApplyEventRequest.Approver()
|
||||
.setAttr(2) // 审批类型: 或签
|
||||
.setUserIds(new String[]{"approver1", "approver2"})
|
||||
))
|
||||
.setNotifiers(new String[]{"notifier1", "notifier2"}) // 抄送人
|
||||
.setNotifyType(1) // 抄送方式: 提单时抄送
|
||||
.setApplyData(new WxCpOaApplyEventRequest.ApplyData()
|
||||
.setContents(Arrays.asList(
|
||||
// 文本控件
|
||||
new ApplyDataContent()
|
||||
.setControl("Text")
|
||||
.setId("Text-1234567890")
|
||||
.setValue(new ContentValue().setText("这是一个审批申请的文本内容")),
|
||||
|
||||
// 数字控件
|
||||
new ApplyDataContent()
|
||||
.setControl("Number")
|
||||
.setId("Number-1234567890")
|
||||
.setValue(new ContentValue().setNewNumber("1000")),
|
||||
|
||||
// 金额控件
|
||||
new ApplyDataContent()
|
||||
.setControl("Money")
|
||||
.setId("Money-1234567890")
|
||||
.setValue(new ContentValue().setNewMoney("10000"))
|
||||
))
|
||||
);
|
||||
|
||||
// 提交审批申请
|
||||
String spNo = wxCpService.getOaService().apply(request);
|
||||
System.out.println("审批申请提交成功,审批单号: " + spNo);
|
||||
|
||||
return spNo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例2: 获取审批申请详情
|
||||
* Example 2: Get Approval Application Details
|
||||
* API: /cgi-bin/oa/getapprovaldetail
|
||||
*/
|
||||
public void getApprovalDetails(String spNo) throws Exception {
|
||||
WxCpApprovalDetailResult result = wxCpService.getOaService().getApprovalDetail(spNo);
|
||||
|
||||
WxCpApprovalDetailResult.WxCpApprovalDetail detail = result.getInfo();
|
||||
|
||||
System.out.println("审批单号: " + detail.getSpNo());
|
||||
System.out.println("审批名称: " + detail.getSpName());
|
||||
System.out.println("审批状态: " + detail.getSpStatus().getCode());
|
||||
System.out.println("申请人: " + detail.getApplyer().getUserId());
|
||||
System.out.println("申请时间: " + detail.getApplyTime());
|
||||
|
||||
// 打印审批记录
|
||||
if (detail.getSpRecord() != null) {
|
||||
detail.getSpRecord().forEach(record -> {
|
||||
System.out.println("审批节点状态: " + record.getSpStatus());
|
||||
System.out.println("审批人: " + record.getDetails());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例3: 批量获取审批单号
|
||||
* Example 3: Batch Get Approval Numbers
|
||||
* API: /cgi-bin/oa/getapprovalinfo
|
||||
*/
|
||||
public void batchGetApprovalInfo() throws Exception {
|
||||
// 获取最近7天的审批单
|
||||
Date startTime = new Date(System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000L);
|
||||
Date endTime = new Date();
|
||||
|
||||
WxCpApprovalInfo approvalInfo = wxCpService.getOaService()
|
||||
.getApprovalInfo(startTime, endTime, "0", 100, null);
|
||||
|
||||
System.out.println("获取到的审批单数量: " + approvalInfo.getCount());
|
||||
|
||||
// 遍历审批单号
|
||||
if (approvalInfo.getSpNoList() != null) {
|
||||
approvalInfo.getSpNoList().forEach(spNo -> {
|
||||
System.out.println("审批单号: " + spNo);
|
||||
// 可以进一步调用 getApprovalDetails 获取详情
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例4: 模板管理
|
||||
* Example 4: Template Management
|
||||
*/
|
||||
public void templateManagement() throws Exception {
|
||||
// 获取模板详情
|
||||
String templateId = "3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioUBGCNS";
|
||||
var templateResult = wxCpService.getOaService().getTemplateDetail(templateId);
|
||||
|
||||
System.out.println("模板名称: " + templateResult.getTemplateNames());
|
||||
System.out.println("模板内容: " + templateResult.getTemplateContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整的审批流程演示
|
||||
* Complete Approval Workflow Demo
|
||||
*/
|
||||
public void completeWorkflowDemo() {
|
||||
try {
|
||||
System.out.println("=== 企业微信流程审批完整演示 ===");
|
||||
|
||||
// 1. 提交审批申请
|
||||
System.out.println("\n1. 提交审批申请...");
|
||||
String spNo = submitApprovalApplication();
|
||||
|
||||
// 2. 获取审批详情
|
||||
System.out.println("\n2. 获取审批详情...");
|
||||
getApprovalDetails(spNo);
|
||||
|
||||
// 3. 批量获取审批信息
|
||||
System.out.println("\n3. 批量获取审批信息...");
|
||||
batchGetApprovalInfo();
|
||||
|
||||
// 4. 模板管理
|
||||
System.out.println("\n4. 模板管理...");
|
||||
templateManagement();
|
||||
|
||||
System.out.println("\n=== 演示完成 ===");
|
||||
System.out.println("WxJava SDK 已经完整支持企业微信流程审批功能!");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("演示过程中发生错误: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 注意: 这里需要配置真实的企业微信服务
|
||||
// Note: You need to configure real WeChat Enterprise service here
|
||||
|
||||
System.out.println("企业微信流程审批功能演示");
|
||||
System.out.println("该演示代码展示了WxJava SDK中已经完整实现的审批流程功能");
|
||||
System.out.println("包括文档91853中描述的所有核心功能");
|
||||
System.out.println("");
|
||||
System.out.println("主要功能:");
|
||||
System.out.println("- 提交审批申请 (/cgi-bin/oa/applyevent)");
|
||||
System.out.println("- 获取审批详情 (/cgi-bin/oa/getapprovaldetail)");
|
||||
System.out.println("- 批量获取审批单号 (/cgi-bin/oa/getapprovalinfo)");
|
||||
System.out.println("- 模板管理功能");
|
||||
System.out.println("- 审批流程引擎支持");
|
||||
System.out.println("");
|
||||
System.out.println("如需运行演示,请配置正确的企业微信服务参数。");
|
||||
}
|
||||
}
|
||||
@@ -180,4 +180,31 @@ public class WxCpUserGsonAdapterTest {
|
||||
"{\"type\":2,\"name\":\"测试app\"," +
|
||||
"\"miniprogram\":{\"appid\":\"wx8bd80126147df384\",\"pagepath\":\"/index\",\"title\":\"my miniprogram\"}}]}}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test directLeader empty array serialization.
|
||||
* This test verifies that empty directLeader arrays are included in JSON as "direct_leader":[]
|
||||
* instead of being omitted, which is required for WeChat Work API to reset user direct leaders.
|
||||
*/
|
||||
@Test
|
||||
public void testDirectLeaderEmptyArraySerialization() {
|
||||
WxCpUser user = new WxCpUser();
|
||||
user.setUserId("testuser");
|
||||
user.setName("Test User");
|
||||
|
||||
// Test with empty array - should be serialized as "direct_leader":[]
|
||||
user.setDirectLeader(new String[]{});
|
||||
String json = user.toJson();
|
||||
assertThat(json).contains("\"direct_leader\":[]");
|
||||
|
||||
// Test with null - should not include direct_leader field
|
||||
user.setDirectLeader(null);
|
||||
json = user.toJson();
|
||||
assertThat(json).doesNotContain("direct_leader");
|
||||
|
||||
// Test with non-empty array - should be serialized normally
|
||||
user.setDirectLeader(new String[]{"leader1", "leader2"});
|
||||
json = user.toJson();
|
||||
assertThat(json).contains("\"direct_leader\":[\"leader1\",\"leader2\"]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java</artifactId>
|
||||
<version>4.7.8.B</version>
|
||||
<version>4.7.9.B</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>weixin-java-miniapp</artifactId>
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
package cn.binarywang.wx.miniapp.api;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.complaint.*;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 小程序交易投诉接口
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
* created on 2025-01-01
|
||||
*/
|
||||
public interface WxMaComplaintService {
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 查询投诉单列表API
|
||||
* 商户可通过调用此接口,查询指定时间段的所有用户投诉信息,以分页输出查询结果。
|
||||
* 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
|
||||
* </pre>
|
||||
*
|
||||
* @param request {@link WxMaComplaintRequest} 查询投诉单列表请求数据
|
||||
* @return {@link WxMaComplaintResult} 微信返回的投诉单列表
|
||||
* @throws WxErrorException the wx error exception
|
||||
*/
|
||||
WxMaComplaintResult queryComplaints(WxMaComplaintRequest request) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 查询投诉单详情API
|
||||
* 商户可通过调用此接口,查询指定投诉单的用户投诉详情,包含投诉内容、投诉关联订单、投诉人联系方式等信息,方便商户处理投诉。
|
||||
* 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
|
||||
* </pre>
|
||||
*
|
||||
* @param request {@link WxMaComplaintDetailRequest} 投诉单详情请求数据
|
||||
* @return {@link WxMaComplaintDetailResult} 微信返回的投诉单详情
|
||||
* @throws WxErrorException the wx error exception
|
||||
*/
|
||||
WxMaComplaintDetailResult getComplaint(WxMaComplaintDetailRequest request) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 查询投诉协商历史API
|
||||
* 商户可通过调用此接口,查询指定投诉的用户商户协商历史,以分页输出查询结果,方便商户根据处理历史来制定后续处理方案。
|
||||
* 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
|
||||
* </pre>
|
||||
*
|
||||
* @param request {@link WxMaNegotiationHistoryRequest} 请求数据
|
||||
* @return {@link WxMaNegotiationHistoryResult} 微信返回结果
|
||||
* @throws WxErrorException the wx error exception
|
||||
*/
|
||||
WxMaNegotiationHistoryResult queryNegotiationHistorys(WxMaNegotiationHistoryRequest request) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 创建投诉通知回调地址API
|
||||
* 商户通过调用此接口创建投诉通知回调URL,当用户产生新投诉且投诉状态已变更时,微信会通过回调URL通知商户。
|
||||
* 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
|
||||
* </pre>
|
||||
*
|
||||
* @param request {@link WxMaComplaintNotifyUrlRequest} 请求数据
|
||||
* @return {@link WxMaComplaintNotifyUrlResult} 微信返回结果
|
||||
* @throws WxErrorException the wx error exception
|
||||
*/
|
||||
WxMaComplaintNotifyUrlResult addComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 查询投诉通知回调地址API
|
||||
* 商户通过调用此接口查询投诉通知的回调URL。
|
||||
* 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
|
||||
* </pre>
|
||||
*
|
||||
* @return {@link WxMaComplaintNotifyUrlResult} 微信返回结果
|
||||
* @throws WxErrorException the wx error exception
|
||||
*/
|
||||
WxMaComplaintNotifyUrlResult getComplaintNotifyUrl() throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 更新投诉通知回调地址API
|
||||
* 商户通过调用此接口更新投诉通知的回调URL。
|
||||
* 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
|
||||
* </pre>
|
||||
*
|
||||
* @param request {@link WxMaComplaintNotifyUrlRequest} 请求数据
|
||||
* @return {@link WxMaComplaintNotifyUrlResult} 微信返回结果
|
||||
* @throws WxErrorException the wx error exception
|
||||
*/
|
||||
WxMaComplaintNotifyUrlResult updateComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 删除投诉通知回调地址API
|
||||
* 当商户不再需要推送通知时,可通过调用此接口删除投诉通知的回调URL,取消通知回调。
|
||||
* 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
|
||||
* </pre>
|
||||
*
|
||||
* @throws WxErrorException the wx error exception
|
||||
*/
|
||||
void deleteComplaintNotifyUrl() throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 提交回复API
|
||||
* 商户可通过调用此接口,提交回复内容。其中上传图片凭证需首先调用商户上传反馈图片接口,得到图片id,再将id填入请求。
|
||||
* 回复可配置文字链,传入跳转链接文案和跳转链接字段,用户点击即可跳转对应页面
|
||||
* 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
|
||||
* </pre>
|
||||
*
|
||||
* @param request {@link WxMaResponseRequest} 请求数据
|
||||
* @throws WxErrorException the wx error exception
|
||||
*/
|
||||
void submitResponse(WxMaResponseRequest request) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 反馈处理完成API
|
||||
* 商户可通过调用此接口,反馈投诉单已处理完成。
|
||||
* 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
|
||||
* </pre>
|
||||
*
|
||||
* @param request {@link WxMaCompleteRequest} 请求数据
|
||||
* @throws WxErrorException the wx error exception
|
||||
*/
|
||||
void complete(WxMaCompleteRequest request) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 商户上传反馈图片API
|
||||
* 商户可通过调用此接口上传反馈图片凭证,上传成功后可在提交回复时使用。
|
||||
* 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
|
||||
* </pre>
|
||||
*
|
||||
* @param imageFile 需要上传的图片文件
|
||||
* @return String 微信返回的媒体文件标识Id
|
||||
* @throws WxErrorException the wx error exception
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
String uploadResponseImage(File imageFile) throws WxErrorException, IOException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 商户上传反馈图片API
|
||||
* 商户可通过调用此接口上传反馈图片凭证,上传成功后可在提交回复时使用。
|
||||
* 文档详见: https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
|
||||
* </pre>
|
||||
*
|
||||
* @param inputStream 需要上传的图片文件流
|
||||
* @param fileName 需要上传的图片文件名
|
||||
* @return String 微信返回的媒体文件标识Id
|
||||
* @throws WxErrorException the wx error exception
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
String uploadResponseImage(InputStream inputStream, String fileName) throws WxErrorException, IOException;
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package cn.binarywang.wx.miniapp.api;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfInfo;
|
||||
import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfList;
|
||||
import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSession;
|
||||
import cn.binarywang.wx.miniapp.bean.kefu.WxMaKfSessionList;
|
||||
import cn.binarywang.wx.miniapp.bean.kefu.request.WxMaKfAccountRequest;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 小程序客服管理接口.
|
||||
* 不同于 WxMaCustomserviceWorkService (企业微信客服绑定) 和 WxMaMsgService.sendKefuMsg (发送客服消息),
|
||||
* 此接口专门处理小程序客服账号管理、会话管理等功能。
|
||||
*
|
||||
* 注意:小程序客服管理接口与公众号客服管理接口在API端点和功能上有所不同。
|
||||
* </pre>
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public interface WxMaKefuService {
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 获取客服基本信息
|
||||
* 详情请见:<a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/customer-service/customerServiceMessage.getContactList.html">获取客服基本信息</a>
|
||||
* 接口url格式:https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=ACCESS_TOKEN
|
||||
* </pre>
|
||||
*
|
||||
* @return 客服列表
|
||||
* @throws WxErrorException 异常
|
||||
*/
|
||||
WxMaKfList kfList() throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 添加客服账号
|
||||
* 详情请见:<a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/customer-service/customerServiceMessage.addKfAccount.html">添加客服账号</a>
|
||||
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/add?access_token=ACCESS_TOKEN
|
||||
* </pre>
|
||||
*
|
||||
* @param request 客服账号信息
|
||||
* @return 是否成功
|
||||
* @throws WxErrorException 异常
|
||||
*/
|
||||
boolean kfAccountAdd(WxMaKfAccountRequest request) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 修改客服账号
|
||||
* 详情请见:<a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/customer-service/customerServiceMessage.updateKfAccount.html">修改客服账号</a>
|
||||
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/update?access_token=ACCESS_TOKEN
|
||||
* </pre>
|
||||
*
|
||||
* @param request 客服账号信息
|
||||
* @return 是否成功
|
||||
* @throws WxErrorException 异常
|
||||
*/
|
||||
boolean kfAccountUpdate(WxMaKfAccountRequest request) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 删除客服账号
|
||||
* 详情请见:<a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/customer-service/customerServiceMessage.deleteKfAccount.html">删除客服账号</a>
|
||||
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/del?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
|
||||
* </pre>
|
||||
*
|
||||
* @param kfAccount 客服账号
|
||||
* @return 是否成功
|
||||
* @throws WxErrorException 异常
|
||||
*/
|
||||
boolean kfAccountDel(String kfAccount) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 创建会话
|
||||
* 详情请见:<a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/customer-service/customerServiceMessage.createSession.html">创建会话</a>
|
||||
* 接口url格式:https://api.weixin.qq.com/customservice/kfsession/create?access_token=ACCESS_TOKEN
|
||||
* </pre>
|
||||
*
|
||||
* @param openid 用户openid
|
||||
* @param kfAccount 客服账号
|
||||
* @return 是否成功
|
||||
* @throws WxErrorException 异常
|
||||
*/
|
||||
boolean kfSessionCreate(String openid, String kfAccount) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 关闭会话
|
||||
* 详情请见:<a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/customer-service/customerServiceMessage.closeSession.html">关闭会话</a>
|
||||
* 接口url格式:https://api.weixin.qq.com/customservice/kfsession/close?access_token=ACCESS_TOKEN
|
||||
* </pre>
|
||||
*
|
||||
* @param openid 用户openid
|
||||
* @param kfAccount 客服账号
|
||||
* @return 是否成功
|
||||
* @throws WxErrorException 异常
|
||||
*/
|
||||
boolean kfSessionClose(String openid, String kfAccount) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 获取客户的会话状态
|
||||
* 详情请见:<a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/customer-service/customerServiceMessage.getSession.html">获取客户的会话状态</a>
|
||||
* 接口url格式:https://api.weixin.qq.com/customservice/kfsession/getsession?access_token=ACCESS_TOKEN&openid=OPENID
|
||||
* </pre>
|
||||
*
|
||||
* @param openid 用户openid
|
||||
* @return 会话信息
|
||||
* @throws WxErrorException 异常
|
||||
*/
|
||||
WxMaKfSession kfSessionGet(String openid) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 获取客服的会话列表
|
||||
* 详情请见:<a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/customer-service/customerServiceMessage.getSessionList.html">获取客服的会话列表</a>
|
||||
* 接口url格式:https://api.weixin.qq.com/customservice/kfsession/getsessionlist?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT
|
||||
* </pre>
|
||||
*
|
||||
* @param kfAccount 客服账号
|
||||
* @return 会话列表
|
||||
* @throws WxErrorException 异常
|
||||
*/
|
||||
WxMaKfSessionList kfSessionList(String kfAccount) throws WxErrorException;
|
||||
|
||||
}
|
||||
@@ -285,6 +285,13 @@ public interface WxMaService extends WxService {
|
||||
*/
|
||||
WxMaCustomserviceWorkService getCustomserviceWorkService();
|
||||
|
||||
/**
|
||||
* 获取小程序客服管理服务。
|
||||
*
|
||||
* @return 客服管理服务对象WxMaKefuService
|
||||
*/
|
||||
WxMaKefuService getKefuService();
|
||||
|
||||
/**
|
||||
* 获取jsapi操作相关服务对象。
|
||||
*
|
||||
@@ -605,4 +612,13 @@ public interface WxMaService extends WxService {
|
||||
* @return 同城配送服务对象WxMaIntracityService
|
||||
*/
|
||||
WxMaIntracityService getIntracityService();
|
||||
|
||||
/**
|
||||
* 获取交易投诉服务对象。
|
||||
* <br>
|
||||
* 文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/complaint.html
|
||||
*
|
||||
* @return 交易投诉服务对象WxMaComplaintService
|
||||
*/
|
||||
WxMaComplaintService getComplaintService();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.binarywang.wx.miniapp.api;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaGroupEnterInfo;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaShareInfo;
|
||||
|
||||
/**
|
||||
@@ -18,4 +19,16 @@ public interface WxMaShareService {
|
||||
*/
|
||||
WxMaShareInfo getShareInfo(String sessionKey, String encryptedData, String ivStr);
|
||||
|
||||
/**
|
||||
* 解密群入口敏感数据.
|
||||
* 对应 wx.getGroupEnterInfo 接口返回的 encryptedData 解密
|
||||
*
|
||||
* @param sessionKey 会话密钥
|
||||
* @param encryptedData 消息密文
|
||||
* @param ivStr 加密算法的初始向量
|
||||
* @return 群入口信息
|
||||
* @see <a href="https://developers.weixin.qq.com/miniprogram/dev/api/open-api/group/wx.getGroupEnterInfo.html">wx.getGroupEnterInfo 官方文档</a>
|
||||
*/
|
||||
WxMaGroupEnterInfo getGroupEnterInfo(String sessionKey, String encryptedData, String ivStr);
|
||||
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
|
||||
private final WxMaAnalysisService analysisService = new WxMaAnalysisServiceImpl(this);
|
||||
private final WxMaCodeService codeService = new WxMaCodeServiceImpl(this);
|
||||
private final WxMaCustomserviceWorkService customserviceWorkService = new WxMaCustomserviceWorkServiceImpl(this);
|
||||
private final WxMaKefuService maKefuService = new WxMaKefuServiceImpl(this);
|
||||
private final WxMaInternetService internetService = new WxMaInternetServiceImpl(this);
|
||||
private final WxMaSettingService settingService = new WxMaSettingServiceImpl(this);
|
||||
private final WxMaJsapiService jsapiService = new WxMaJsapiServiceImpl(this);
|
||||
@@ -165,6 +166,7 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
|
||||
new WxMaExpressDeliveryReturnServiceImpl(this);
|
||||
private final WxMaPromotionService wxMaPromotionService = new WxMaPromotionServiceImpl(this);
|
||||
private final WxMaIntracityService intracityService = new WxMaIntracityServiceImpl(this);
|
||||
private final WxMaComplaintService complaintService = new WxMaComplaintServiceImpl(this);
|
||||
|
||||
private Map<String, WxMaConfig> configMap = new HashMap<>();
|
||||
private int retrySleepMillis = 1000;
|
||||
@@ -425,8 +427,9 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
|
||||
}
|
||||
String accessToken = getAccessToken(false);
|
||||
|
||||
if (StringUtils.isNotEmpty(this.getWxMaConfig().getApiHostUrl())) {
|
||||
uri = uri.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl());
|
||||
String effectiveApiHostUrl = this.getWxMaConfig().getEffectiveApiHostUrl();
|
||||
if (!WxMaConfig.DEFAULT_API_HOST_URL.equals(effectiveApiHostUrl)) {
|
||||
uri = uri.replace(WxMaConfig.DEFAULT_API_HOST_URL, effectiveApiHostUrl);
|
||||
}
|
||||
|
||||
String uriWithAccessToken =
|
||||
@@ -657,6 +660,11 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
|
||||
return this.customserviceWorkService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaKefuService getKefuService() {
|
||||
return this.maKefuService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaJsapiService getJsapiService() {
|
||||
return this.jsapiService;
|
||||
@@ -1030,4 +1038,9 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
|
||||
public WxMaIntracityService getIntracityService() {
|
||||
return this.intracityService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaComplaintService getComplaintService() {
|
||||
return this.complaintService;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaComplaintService;
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.complaint.*;
|
||||
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.chanjar.weixin.common.bean.CommonUploadParam;
|
||||
import me.chanjar.weixin.common.error.WxError;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.common.util.fs.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.UUID;
|
||||
|
||||
import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Complaint.*;
|
||||
|
||||
/**
|
||||
* 小程序交易投诉接口实现
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
* created on 2025-01-01
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class WxMaComplaintServiceImpl implements WxMaComplaintService {
|
||||
private static final String JSON_CONTENT_TYPE = "application/json";
|
||||
private final WxMaService wxMaService;
|
||||
|
||||
@Override
|
||||
public WxMaComplaintResult queryComplaints(WxMaComplaintRequest request) throws WxErrorException {
|
||||
String responseContent = this.wxMaService.post(QUERY_COMPLAINTS_URL, request.toJson());
|
||||
return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaComplaintDetailResult getComplaint(WxMaComplaintDetailRequest request) throws WxErrorException {
|
||||
String responseContent = this.wxMaService.post(GET_COMPLAINT_URL, request.toJson());
|
||||
return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintDetailResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaNegotiationHistoryResult queryNegotiationHistorys(WxMaNegotiationHistoryRequest request) throws WxErrorException {
|
||||
String responseContent = this.wxMaService.post(QUERY_NEGOTIATION_HISTORY_URL, request.toJson());
|
||||
return WxMaGsonBuilder.create().fromJson(responseContent, WxMaNegotiationHistoryResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaComplaintNotifyUrlResult addComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException {
|
||||
String responseContent = this.wxMaService.post(ADD_COMPLAINT_NOTIFY_URL, request.toJson());
|
||||
return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintNotifyUrlResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaComplaintNotifyUrlResult getComplaintNotifyUrl() throws WxErrorException {
|
||||
String responseContent = this.wxMaService.get(GET_COMPLAINT_NOTIFY_URL, null);
|
||||
return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintNotifyUrlResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaComplaintNotifyUrlResult updateComplaintNotifyUrl(WxMaComplaintNotifyUrlRequest request) throws WxErrorException {
|
||||
String responseContent = this.wxMaService.post(UPDATE_COMPLAINT_NOTIFY_URL, request.toJson());
|
||||
return WxMaGsonBuilder.create().fromJson(responseContent, WxMaComplaintNotifyUrlResult.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteComplaintNotifyUrl() throws WxErrorException {
|
||||
this.wxMaService.post(DELETE_COMPLAINT_NOTIFY_URL, "{}");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submitResponse(WxMaResponseRequest request) throws WxErrorException {
|
||||
this.wxMaService.post(SUBMIT_RESPONSE_URL, request.toJson());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void complete(WxMaCompleteRequest request) throws WxErrorException {
|
||||
this.wxMaService.post(COMPLETE_COMPLAINT_URL, request.toJson());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String uploadResponseImage(File imageFile) throws WxErrorException, IOException {
|
||||
String result = this.wxMaService.upload(UPLOAD_RESPONSE_IMAGE_URL,
|
||||
CommonUploadParam.fromFile("image", imageFile));
|
||||
JsonObject jsonResult = WxMaGsonBuilder.create().fromJson(result, JsonObject.class);
|
||||
return jsonResult.get("media_id").getAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String uploadResponseImage(InputStream inputStream, String fileName) throws WxErrorException, IOException {
|
||||
try {
|
||||
return this.uploadResponseImage(FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileName));
|
||||
} catch (IOException e) {
|
||||
throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user