143 Commits

Author SHA1 Message Date
Copilot
7c6dbf15e4 🎨 #3827【企业微信】修复群聊变更事件MemChangeList字段命名规范问题
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2026-01-03 16:45:03 +08:00
夏沫浅微凉
db15aec8de 🐛 #3825 【微信支付】修复初始化api v3请求时证书序列号值生成条件错误的问题 2025-12-27 21:10:39 +08:00
Copilot
6504f5d82c 🆕 #3823 【企业微信】添加获取应用管理员列表的接口 2025-12-24 16:14:20 +08:00
Copilot
912ba354e9 🎨 #3821 【企业微信】修复 WxCpRedisConfigImpl.getWebhookKey() 方法无限递归调用的问题 2025-12-24 16:12:56 +08:00
Charon-张同学
f00a71965a 🎨 修改微信医保支付模块的规范性问题,主要涉及字段命名标准化、数据类型调整和 API 方法签名优化
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-12-24 15:47:54 +08:00
yanglegetuo
c2d0cf8d4e 🆕 #3818 【小程序】 新增设备组相关的 API 接口 2025-12-24 10:05:07 +08:00
Copilot
8e760a972d 🎨 #3654 【开放平台】消息路由处理程序增加对小程序的支持 2025-12-22 09:37:25 +08:00
Copilot
b601b557f9 🎨 支付回调结果解析时提供必要的检测报错判断 2025-12-21 21:57:27 +08:00
Copilot
40428bc7cf 📝 添加微信开放平台小程序审核额度管理指南 2025-12-21 21:54:43 +08:00
Copilot
e3463a0adf 🎨 #3686 【微信支付】付款码支付接口添加服务商模式支持 2025-12-21 20:27:44 +08:00
Copilot
f249450dbe 🎨 #3798 【微信支付】微信支付下单接口结果JsapiResult类中添加prepayId字段 2025-12-21 18:03:36 +08:00
Copilot
b0d9c6cc76 🆕 #3717 【微信支付】商户转账新增免确认收款授权模式支持
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-12-21 17:49:11 +08:00
Copilot
621b8dc0e4 🆕 #3812 【小程序】实现用工关系API支持 2025-12-21 17:31:53 +08:00
Copilot
58cdef951f 🆕 #3811 【小程序】添加小游戏道具直购(present_goods)API的支持 2025-12-20 20:08:30 +08:00
Copilot
14537479c7 🎨 #3813 【微信支付】修复委托代扣协议字段名拼写错误 2025-12-20 20:07:04 +08:00
Copilot
3b8d0a8814 🆕 #3814 【微信支付】增加实名验证的接口 2025-12-20 17:23:55 +08:00
Sexy Six
c8cdfb56e1 🆕 #3816 【微信支付】增加 V3 医保相关接口的完整支持 2025-12-20 17:20:45 +08:00
Copilot
81816456c1 🆕 #3815 【企业微信】增加智能机器人消息接收和主动发送功能 2025-12-20 17:18:22 +08:00
Binary Wang
b9f49094c0 修改中文输出和单元测试要求
更新了中文输出要求,添加了单元测试代码的说明。
2025-12-20 11:54:34 +08:00
舍出
b56eddd47b 🎨 #3800 【微信支付】小微商家进件 API 进行参数结构调整 2025-12-15 15:30:31 +08:00
hyf1844
ef5f28cb28 🎨 #3806 【视频号】微信小店订单详情接口新增会员权益等优惠金额字段 2025-12-11 16:32:39 +08:00
Copilot
d26172e393 🎨 #3802 【小程序】修复 WxMaExpressOrderCargo 几个字段类型以支持小数值 2025-12-10 14:27:27 +08:00
buaazyl
c777dc32a8 🎨 #3799 【小程序】发货信息管理功能补充消息推送字段,支持订单发货和结算事件的完整信息接收 2025-12-10 10:07:36 +08:00
Copilot
c0db88091f 🎨 #3791 【微信支付】特约商户进件接口添加了小微商户(个体经营者)的数据字段支持
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-12-05 16:28:23 +08:00
Copilot
85bd27424d 🎨 #3795 【小程序】客服消息API新增了 aimsgcontext 字段,用于AI消息上下文关联 2025-12-05 16:20:18 +08:00
Copilot
ddcdf658cf 🐛 #3797 【微信支付】修复商家转账API路径错误,移除多余的operation部分 2025-12-05 16:19:06 +08:00
Copilot
4e46486f2c 🎨 #3598 【企业微信】 会话存档接口添加对音视频通话(voiptext)消息类型的支持 2025-12-04 19:43:15 +08:00
Copilot
219a8f4f36 🎨 #3620 【小程序】修复同城配送API签名错误问题(添加RSA私钥序列号到签名payload和请求头) 2025-12-04 19:40:03 +08:00
Copilot
cd4317ab3e 🎨 #3384 【公众号】为 starter 添加 HttpComponents (httpclient5) 支持 2025-12-04 19:38:10 +08:00
Copilot
085125960b 🎨 #3608 【微信支付】修复 fullPublicKeyModel 配置在 Spring Boot Starter 和 Solon 插件中无效的问题 2025-12-03 17:27:24 +08:00
Copilot
524762704c 🆕 #3524 【小程序】添加多端登录 code2VerifyInfo 接口的支持 2025-12-03 17:25:53 +08:00
Copilot
f7a196c129 🎨 修复支付公钥模式下回调验证serialNumber空指针异常 2025-12-02 12:08:01 +08:00
Copilot
3bf3595dc1 🎨 修复 WxCpApprovalWorkflowDemo.java 的兼容性和编译错误 2025-12-02 12:03:03 +08:00
Copilot
4d0617f7bf 🐛 #3515 修复OkHttp请求方式时代理认证头设置错误的问题 2025-12-02 11:24:40 +08:00
Binary Wang
fd3bbde725 🔖 发布 4.7.9.B 测试版本
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-11-28 22:37:15 +08:00
Binary Wang
84e70febdf update agent description
更新代理名称和描述为中文,并强调提交信息也需使用中文。
2025-11-28 21:54:48 +08:00
Copilot
155f3f778f 🎨 #3570 【小程序】添加微信云托管支持 2025-11-28 21:38:57 +08:00
Copilot
26f5887e22 📝 为现有的企业微信流程审批功能添加完整的使用文档
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-11-28 11:52:33 +08:00
Copilot
23a2e18f1c 🆕 #3682 【企业微信】新增智能机器人的接口支持 2025-11-28 11:50:50 +08:00
Copilot
8f20f3baba 🆕 #3678 【小程序】新增客服管理相关接口 2025-11-28 11:49:15 +08:00
Copilot
93a2fa701a 🆕 #3519 【小程序】实现交易投诉的相关接口 2025-11-28 11:46:10 +08:00
Copilot
1f4ed68750 🎨 #3750 【微信支付】修复 V2 支付回调签名验证失败的问题 2025-11-28 11:29:22 +08:00
Copilot
c4f383462e 🆕 #3737 【微信支付】增加V3服务商申请退款的接口 2025-11-28 11:26:46 +08:00
Copilot
03114b9900 🎨 #3753 【微信支付】添加商家转账用户授权免确认模式相关接口 2025-11-28 11:23:05 +08:00
Copilot
f6d9e8ba4d 🎨 #3738 【微信支付】修复V3新商户完全公钥模式下publicKey为空的问题 2025-11-28 11:20:51 +08:00
Copilot
1da6cf56ff 🎨 #3755 【企业微信】修复会话存档SDK重复初始化导致接口超限问题 2025-11-28 11:19:38 +08:00
Copilot
e655a33956 🎨 #3752 修复 Gson 在 Java 9+ 环境下反射访问 java.io.File#path 字段失败的问题 2025-11-28 11:17:33 +08:00
Copilot
9940546956 🐛 #3675 【小程序】修复退货组件 API 接口请求方法错误的问题 2025-11-28 11:15:41 +08:00
chu
1bd50d4d2f 🆕 #3770 【视频号】新增售后单商家协商、换货发货与拒绝发货等接口 2025-11-28 10:08:55 +08:00
Copilot
8e46da5003 🎨 #3683 【开放平台】修改component_verify_ticket 的有效时间为12小时 2025-11-27 23:20:04 +08:00
Dell Well
b1e7cd2cd7 🐛 #3664: 【微信支付】修复微信支付分订单接口提示“商户暂无权限使用此服务” 的问题 2025-11-27 23:16:49 +08:00
hyf1844
7fcd1ed5b3 🎨 #3767 【视频号】微信小店订单详情接口新增会员权益等优惠金额字段 2025-11-27 23:14:16 +08:00
Copilot
21d5f2fdd2 🆕 #3764 【开放平台】添加小程序类目管理 - 获取类目名称信息的接口 2025-11-27 23:06:19 +08:00
Copilot
e57b7e1eb5 🎨 #3765 【微信支付】修复从 base64 字符串加载私钥时的双重解码问题 2025-11-27 23:04:11 +08:00
Copilot
833f60c68d 🆕 #3736 【小程序】增加解密群入口敏感数据的方法 2025-11-17 12:01:15 +08:00
Copilot
1922aaaa94 🎨 #3751 【企业微信】提交审批申请接口增加process参数以支持新版的审批流程信息 2025-11-17 11:59:51 +08:00
Copilot
3c440eacb3 🎨 修复 GsonParser 与低版本 Gson 的兼容性问题 2025-11-17 11:11:51 +08:00
Binary Wang
b8d2bb345a 📝 添加自定义agent的文档 2025-11-17 11:00:07 +08:00
Binary Wang
730ac24c3f 🎨 优化小程序配置类的javadoc 2025-11-17 10:47:29 +08:00
Copilot
ebb9ccdebe 🎨 #3376 【小程序】完善事件消息推送对json数据格式的支持 2025-11-17 10:33:09 +08:00
Copilot
76c7e994a5 🐛 #3700 修复空指针问题
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-11-15 17:14:45 +08:00
Copilot
092e992817 🆕 #3688 【微信支付】 实现预约扣费服务的相关接口 2025-11-15 17:12:21 +08:00
Copilot
69a2ab9cc7 🆕 #3720 【微信支付】实现微信押金支付的相关功能接口 2025-11-15 17:09:29 +08:00
Copilot
0854e4d971 🆕 #3507 【微信支付】实现运营工具-商家转账API相关接口 2025-11-15 17:04:27 +08:00
Copilot
57e12dee29 🐛 #3704 【小程序】修复物流服务-同城配送服务-查询门店余额接口错误的逻辑 2025-11-15 17:03:08 +08:00
Copilot
4d4294707b 🎨 #3728 【微信支付】修复V3支付公钥转账出现的签名验证失败问题 2025-11-15 16:52:49 +08:00
Copilot
9ab64167d5 🎨 #3680 【微信支付】修复PEM格式的私钥和证书处理的问题 2025-11-15 16:47:06 +08:00
helloJetBase-tech
28fac4ec2e 🎨 #3756 【企业微信】修复企业微信 API 回调验签过程中 WxCryptUtil.decrypt 方法可能抛出异常的问题 2025-11-12 13:44:18 +08:00
kongkong
b94106fd57 🎨 #3757 【微信支付】修复当只设置 privateCertString 或 PrivateCertContent 时 certSerialNo 没有被正确生成的问题 2025-11-12 13:41:52 +08:00
Copilot
d4186c49c1 🆕 #3494 【微信支付】平台收付通提现接口新增回调参数支持及补充日终余额提现API 2025-11-05 10:45:50 +08:00
Copilot
bb573afcaf 🎨 #3732 Add Quarkus/GraalVM Native Image support - Fix Random instance initialization issues 2025-11-02 16:30:13 +08:00
troubleMTT
e9bc5d0109 🎨 #3746 【企业微信】获取企业已配置的「联系我」列表接口返回值增加next_cursor字段
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-10-30 19:43:35 +08:00
Copilot
738bcf7755 🆕 #3739 【开放平台】补充完善第三方平台小程序快速配置域名的相关接口 2025-10-28 11:59:56 +08:00
Binary Wang
de4848b2f7 🎨 规范mp模块的javadoc接口注释文档
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-10-28 11:55:15 +08:00
accept mediocrity
b26c786797 🎨 #3745 【微信支付】修复请求微信仿真测试系统时验签密钥接口的 Content-Type 问题 2025-10-28 11:51:26 +08:00
Binary Wang
b9b4f00438 📝 Revise development instructions with Chinese translations
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-10-23 11:00:01 +08:00
小付同学
98329b11da 🐛 #3640 【微信支付】修复使用连接池后SSL客户端证书未正确发送导致退款接口报错的问题 2025-10-23 10:12:23 +08:00
Copilot
db85c0ab8a 🎨 完善关于开放平台功能的说明文档 2025-10-04 01:47:50 +08:00
Copilot
ca567ce310 🆕 #3618 【微信支付】增加境外微信支付的支持 2025-10-04 01:44:56 +08:00
Copilot
a6825a62bb 🎨 #3628【企业微信】修复更新用户接口重置用户直属领导字段的问题 2025-10-04 01:39:35 +08:00
Mr.Robot
9fd12b2a09 🎨 WxOpenMessageRouter增加注解ConditionalOnMissingBean 2025-10-04 01:32:23 +08:00
xiaoyun461
10f71234e4 🆕 #3725【企业微信】 增加markdown_v2的消息类型支持 2025-10-04 01:31:24 +08:00
Copilot
9aa2781e68 🎨 #3634 【微信支付】尝试修复服务商模式分账动账通知非法请求、头部信息验证失败的问题 2025-09-27 17:11:32 +08:00
Binary Wang
cfe1f6dc95 🔖 发布 4.7.8.B 测试版本
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-09-24 21:55:26 +08:00
Copilot
c0edd9fd16 🎨 #3698 【微信支付】 修复字符编码导致的解析报文异常问题 2025-09-24 21:43:30 +08:00
Copilot
1788d9071e 🎨 #3715 企业微信回调消息中的MsgId改为字符串类型 2025-09-24 21:40:27 +08:00
Copilot
e6a844ab70 🐛 #3631 修复tryLockunlock中针对Key的序列化方式不一致导致无法unlock的问题
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-09-23 10:53:09 +08:00
Copilot
6fc17201e1 🎨 #3697 【企业微信】修复自定义工作台enable_webview_click等参数设置无效的问题 2025-09-23 10:40:53 +08:00
Binary Wang
ad19f653c7 🎨 优化代码,修复过期方法引用 2025-09-23 10:31:35 +08:00
wangshuai
7018dceb1c :art:【企业微信】企微客户群回调事件添加LastMemVer、CurMemVer字段
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-09-22 16:59:32 +00:00
ihao
399f3648c0 :art:【微信支付】公众号签约缺失执行签名方法,增加非必填参数判断;微信信h5纯签约接口,增加非必填字段判断 2025-09-23 00:58:11 +08:00
graalvm-samples
88bdd4a36e 🆕 #3703 【企业微信】第三方应用基础接口实现 2025-09-23 00:25:39 +08:00
imyzt
213cf6fd67 🐛 #3631 修复tryLockunlock中针对Key的序列化方式不一致导致无法unlock的问题 2025-09-23 00:24:02 +08:00
hb0730
c8d84da2ab 🎨 #3702 【小程序】多小程序spring starter增加Redis Template支持 2025-09-15 09:45:15 +08:00
dengfengfeng
118839a49d 🐛 #3692【小程序】【公众号】修复图片智能裁剪接口请求类型错误的问题
Co-authored-by: dengfeng <dengfeng@huaxincem.com>
2025-09-04 09:19:20 +08:00
水依寒
8447caa75f 🎨 #3694 统一各模块微信接口请求的基础地址字段命名为apiHostUrl,并完善Spring Boot starter配置支持 2025-09-04 09:17:36 +08:00
LiYaoheng
1eefd11e03 🎨 修复文档错误,关于channel-springboot-starter的依赖 2025-09-04 09:13:27 +08:00
tryking123
781798345e 🆕 #3681 【小程序】增加企微客服增删查3个接口 2025-08-31 21:44:24 +08:00
Copilot
0424d75882 🎨 #3323 【企业微信】获取企业所有打卡规则接口增加官方新添加的late_rule等字段 2025-08-08 18:21:59 +08:00
Copilot
e61ad44d27 📝 Add comprehensive GitHub Copilot instructions for WxJava SDK development 2025-08-08 18:18:39 +08:00
Copilot
dc46d9de75 🎨 #3640 【微信支付】使用HttpClient发送http请求时调整为使用连接池的形式 2025-08-08 17:47:20 +08:00
Copilot
bc6fb7b58e 🎨 #3656 【微信支付】 修复平台收付通 - 合单支付 - 关闭订单接口,增加缺少的两个参数
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-08-08 17:40:28 +08:00
Copilot
983e4f1e66 🎨 #3659 【微信支付】新增BaseWxPayV3Result基类,提供rawJsonString字段保存原始API响应 2025-08-08 17:25:40 +08:00
痴货
7ffc1b3719 :new:【开放平台】添加获取换绑管理员 URL 的接口 2025-08-08 06:14:25 +00:00
Binary Wang
8bb4a21598 🎨 #3662 【企业微信】修复获取审批模板详情接口异常的问题:Expected BEGIN_OBJECT but was BEGIN_ARRAY
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-07-30 23:29:59 +08:00
Copilot
a881de59a5 🎨 #3567 【企业微信】获取企业所有打卡规则接口调整 2025-07-30 23:10:30 +08:00
Copilot
14f8c8ebc2 🎨 #3655 通过支持现代 TLS 版本修复 SSL 握手失败问题
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-07-25 11:41:06 +08:00
Winnie
875c35e745 🎨 #3660【企业微信】获取会议详情接口响应类补充官方新加的若干字段 2025-07-25 11:35:46 +08:00
Copilot
262a02b606 📝 为微信支付新增商户转账接口添加全面的文档和示例 2025-07-24 16:43:23 +08:00
Copilot
3a1d8075fc 🎨 Fix StackOverflowError in WxCpRedissonConfigImpl and WxCpRedisTemplateConfigImpl toString() methods 2025-07-24 16:39:56 +08:00
Binary Wang
2e952c76c8 🎨 修复错误的变量命名
Some checks are pending
Publish to Maven Central / build-and-publish (push) Waiting to run
2025-07-24 15:49:15 +08:00
Farahani
e5ef34553d 🆕 #3645【小程序】增加虚拟支付的更多接口 2025-07-24 15:29:25 +08:00
匠心
4cdd056a3e 🎨 修复代码缩进格式 2025-07-24 15:18:42 +08:00
痴货
57a6ef1179 🆕 【小程序】增加小程序基本信息通知事件相关的属性
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-07-16 15:06:45 +00:00
痴货
4754a562ca 🎨 调整部分starter模块接口连接的 soTimeout 默认值为 5000毫秒 2025-07-16 15:05:00 +00:00
Binary Wang
62f89987c0 🔖 发布 4.7.7.B 测试版本 2025-07-16 20:05:09 +08:00
痴货
990dbba06b 🎨 【开放平台】重构小程序认证及备案相关接口 2025-07-16 11:47:11 +00:00
痴货
d85e04a72a 🎨 starter模块添加 HttpClient 超时配置 2025-07-16 11:46:38 +00:00
Binary Wang
aacfa947be 🎨 统一优化gson adapter类,抽取常量,优化重复代码
Some checks are pending
Publish to Maven Central / build-and-publish (push) Waiting to run
2025-07-16 13:50:55 +08:00
Ryn
3e4ee57e1c 🎨 #3639 【小程序】修复多账号starter不支持设置数据格式,导致消息解析出错的问题 2025-07-16 13:44:50 +08:00
benben
d4cf48d059 🎨 【公众号】删除已下线的新增删除永久图文素材的接口
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-07-12 08:59:11 +00:00
ihao
696ce8621e 🆕 #3636【微信支付】增加微信签约、解约结果通知解析的方法 2025-07-12 16:57:50 +08:00
dependabot[bot]
8c8139533d ⬆️ Bump org.apache.commons:commons-lang3 from 3.10 to 3.18.0 2025-07-12 16:49:40 +08:00
zhfkt
84c69d4900 🎨 Add null check for textJsonElement in WxCpUserGsonAdapter 2025-07-04 15:40:29 +08:00
xuxiancan
d52e225626 🎨 #3633【微信支付】创建支付分订单接口请求参数里增加设备信息字段 2025-07-04 15:39:15 +08:00
ihao
686dff6436 🎨 优化微信h5纯签约接口,增加非必填字段判断 2025-07-03 01:55:33 +00:00
Style-lo
92431aabd9 🎨 getConfigKey方法改成public的 2025-07-03 09:53:30 +08:00
Binary Wang
29bc950f92 🎨 为小程序短剧管理服务接口增加接口 2025-07-02 10:35:54 +08:00
Binary Wang
5c74c1d930 🎨 为微信小程序物流退货组件接口增加注释文档 2025-07-02 10:35:54 +08:00
Binary Wang
52d0b2fc1c 🎨 为小程序虚拟支付接口类增加注释文档 2025-07-02 10:35:54 +08:00
Binary Wang
5b816242a4 🎨 优化小程序数据分析接口类的注释文档 2025-07-02 10:35:54 +08:00
Binary Wang
a45f0ac048 🎨 优化小程序云开发接口类的注释文档 2025-07-02 10:35:54 +08:00
Binary Wang
4731479b93 🎨 优化小程序接口注释文档 2025-07-02 10:35:54 +08:00
benben
cc344f105e 🎨 #3624 【企业微信】企微微盘API相关接口去掉废弃的userId参数 2025-07-02 10:33:56 +08:00
Well
172b96444f 🎨 #3626 【微信支付】创建代金券批次接口新增字段,包括背景色、卡包跳转目标、小程序 appid 与 path等 2025-07-02 10:31:26 +08:00
jinceon
b3b8c273c7 🎨 【企业微信】增加“审批申请状态变化回调通知”事件类型 2025-06-28 23:07:36 +08:00
大粽子
b6d489e9c8 📝 修改赞助商信息 2025-06-28 23:07:36 +08:00
zhoushi1
72266c3aeb 🎨 #3627 【开放平台】小程序三方平台代上传代码提交额外信息对象新增requiredPrivateInfos信息
Co-authored-by: zhoushi1 <qiannanyou9@gmail.com>
2025-06-28 20:57:37 +08:00
altusea
ccbfa98864 🎨 添加 Apache HttpComponents Client 5.x 为可选的 http client
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-06-09 14:29:35 +08:00
buaazyl
bf35797b91 :art:【小程序】发订阅消息的“43101用户未订阅消息”异常日志调整为debug 2025-06-09 14:28:56 +08:00
buaazyl
41081561b5 :art:【小程序】不再打印发订阅消息的“43101用户未订阅消息”警告日志 2025-06-09 11:19:37 +08:00
Binary Wang
8878f06c1e 🎨 修复单元测试代码
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
2025-06-06 17:07:52 +08:00
Zeyes Lee
22d288f6cd 🎨 #3609 【视频号】微信小店获取售后单接口返回新增换货相关的数据 2025-06-06 16:55:46 +08:00
601 changed files with 31137 additions and 3270 deletions

14
.github/agents/my-agent.agent.md vendored Normal file
View File

@@ -0,0 +1,14 @@
---
# Fill in the fields below to create a basic custom agent for your repository.
# The Copilot CLI can be used for local testing: https://gh.io/customagents/cli
# To make this agent available, merge this file into the default repository branch.
# For format details, see: https://gh.io/customagents/config
name: 全部用中文
description: 需要用中文包括PR标题和分析总结过程
---
# My Agent
1、请使用中文输出思考过程和总结包括PR标题提交commit信息也要使用中文
2、生成代码时需要提供必要的单元测试代码。

202
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,202 @@
# Copilot Instruction
请始终使用中文生成 Pull Request 的标题、描述和提交信息
# WxJava - 微信 Java SDK 开发说明
WxJava 是一个支持多种微信平台的完整 Java SDK包含公众号、小程序、微信支付、企业微信、开放平台、视频号、企点等多种功能模块。
**请始终优先参考本说明,只有在遇到与此内容不一致的意外信息时,才退而使用搜索或 bash 命令。**
## 高效开发指南
### 前置条件与环境准备
- **Java 要求**JDK 8+(项目最低目标为 Java 8
- **Maven**:推荐 Maven 3.6+(已验证 Maven 3.9.11
- **IDE**:推荐使用 IntelliJ IDEA项目针对 IDEA 优化)
### 引导、构建与校验
克隆仓库后按顺序执行以下命令:
```bash
# 1. 基础编译(请勿中断 - 约需 4-5 分钟)
mvn clean compile -DskipTests=true --no-transfer-progress
# 超时时间:建议设置 8 分钟以上。实际时间:约 4 分钟
# 2. 完整打包(请勿中断 - 约需 2-3 分钟)
mvn clean package -DskipTests=true --no-transfer-progress
# 超时时间:建议设置 5 分钟以上。实际时间:约 2 分钟
# 3. 代码质量校验(请勿中断 - 约需 45-60 秒)
mvn checkstyle:check --no-transfer-progress
# 超时时间:建议设置 3 分钟以上。实际时间:约 50 秒
```
重要时间说明:
- 绝对不要中断任意 Maven 构建命令
- 编译阶段耗时最长(约 4 分钟),原因是项目包含 34 个模块
- 后续构建会更快,因为存在增量编译
- 始终使用 `--no-transfer-progress` 以减少日志噪音
### 测试结构
- **测试框架**TestNG非 JUnit
- **测试文件**:共有 298 个测试文件
- **默认行为**pom.xml 中默认禁用测试(`<skip>true</skip>`
- **测试配置**:测试需要通过 test-config.xml 提供真实的微信 API 凭据
- **注意**:没有真实微信 API 凭据请不要尝试运行测试,测试将会失败
## 项目结构与导航
### 核心 SDK 模块(主要开发区)
- `weixin-java-common/` - 通用工具与基础类(最重要)
- `weixin-java-mp/` - 公众号 SDK
- `weixin-java-pay/` - 微信支付 SDK
- `weixin-java-miniapp/` - 小程序 SDK
- `weixin-java-cp/` - 企业微信 SDK
- `weixin-java-open/` - 开放平台 SDK
- `weixin-java-channel/` - 视频号 / Channel SDK
- `weixin-java-qidian/` - 企点 SDK
### 框架集成模块
- `spring-boot-starters/` - Spring Boot 自动配置 starter
- `solon-plugins/` - Solon 框架插件
- `weixin-graal/` - GraalVM 本地镜像支持
### 配置与质量控制
- `quality-checks/google_checks.xml` - Checkstyle 配置
- `.editorconfig` - 代码格式规则2 个空格等于 1 个制表)
- `pom.xml` - 根级 Maven 配置
## 开发工作流
### 修改代码的流程
1. 修改前务必先构建以建立干净基线:
```bash
mvn clean compile --no-transfer-progress
```
2. 遵循代码风格(由 checkstyle 强制):
- 缩进使用 2 个空格(不要用制表符)
- 遵循 Google Java 风格指南
- 在 IDE 中安装 EditorConfig 插件
3. 增量验证修改:
```bash
# 每次修改后运行:
mvn compile --no-transfer-progress
mvn checkstyle:check --no-transfer-progress
```
### 提交修改前的必须校验
请务必按顺序完成以下校验步骤:
1. 代码风格校验:
```bash
mvn checkstyle:check --no-transfer-progress
# 必须通过 - 约需 50 秒
```
2. 完整清理构建:
```bash
mvn clean package -DskipTests=true --no-transfer-progress
# 必须成功 - 约需 2 分钟
```
3. 文档:为公共方法和类补充或更新 javadoc
4. 贡献规范:遵循 `CONTRIBUTING.md`Pull Request 必须以 `develop` 分支为目标
## 模块依赖与构建顺序
### 核心模块依赖(构建顺序)
1. `weixin-graal`GraalVM 支持)
2. `weixin-java-common`(所有模块的基础)
3. 核心 SDK 模块mp、pay、miniapp、cp、open、channel、qidian
4. 框架集成spring-boot-starters、solon-plugins
### 主要关系模式
- 所有 SDK 模块都依赖于 `weixin-java-common`
- Spring Boot starters 依赖对应的 SDK 模块
- Solon 插件遵循与 Spring Boot starters 相同的依赖模式
- 每个模块都有单账号与多账号配置支持
## 常见任务与命令
### 验证指定模块
```bash
# 构建单个模块(将 'weixin-java-mp' 替换为目标模块):
cd weixin-java-mp
mvn clean compile --no-transfer-progress
```
### 检查依赖
```bash
# 分析依赖树:
mvn dependency:tree --no-transfer-progress
# 检查依赖更新:
./others/check-dependency-updates.sh
```
### 发布与发布准备
```bash
# 版本检查:
mvn versions:display-property-updates --no-transfer-progress
# 部署(需要凭据):
mvn clean deploy -P release --no-transfer-progress
```
## 重要文件与位置
### 配置文件
- `pom.xml` - 根级 Maven 配置与依赖管理
- `quality-checks/google_checks.xml` - Checkstyle 规则
- `.editorconfig` - IDE 格式化配置
- `.github/workflows/maven-publish.yml` - CI/CD 工作流
### 文档
- `README.md` - 项目概览与使用说明(中文)
- `CONTRIBUTING.md` - 贡献指南
- `demo.md` - 示例项目与演示链接
- 每个模块均有单独的文档与示例
### 测试资源
- `*/src/test/resources/test-config.sample.xml` - 测试配置模板
- 测试运行需要真实的微信 API 凭据
## SDK 使用模式
### Maven 依赖示例
```xml
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId> <!-- 或其他模块 -->
<version>4.7.0</version>
</dependency>
```
### 常见开发区域
- **API 客户端实现**:位于 `*/service/impl/` 目录
- **模型类**:位于 `*/bean/` 目录
- **配置**:位于 `*/config/` 目录
- **工具类**:位于 `weixin-java-common` 的 `*/util/` 目录
## 故障排查
### 构建问题
- **OutOfMemoryError**:增加 Maven 内存:`export MAVEN_OPTS="-Xmx2g"`
- **编译失败**:通常为依赖问题 - 先执行 `mvn clean`
- **Checkstyle 失败**:检查 IDE 的 `.editorconfig` 设置
### 常见陷阱
- **测试默认跳过**:这是正常现象 — 测试需要微信 API 凭据
- **多模块变更**:总是在仓库根目录构建,而不是单独模块
- **分支目标**Pull Request 必须以 `develop` 分支为目标,而不是 `master` 或 `release`
## 性能说明
- **首次构建**:由于依赖下载,耗时 4-5 分钟
- **增量构建**:通常更快(约 30-60 秒)
- **Checkstyle**:运行迅速(约 50 秒),应当经常运行
- **IDE 性能**:项目使用 Lombok请确保启用注解处理
注意:本项目为 SDK 库项目,而非可运行应用。修改应以 API 功能为主,不要改动应用级行为。

View File

@@ -53,8 +53,8 @@
</a>
</td>
<td align="center">
<a href="#" target="_blank">
<img height="120" src="images/banners/aliyun.jpg" alt="ad">
<a href="https://www.crmeb.com/xiazai" target="_blank">
<img height="120" src="https://crmebjavamer.oss-cn-beijing.aliyuncs.com/crmebimage/gitee/WechatIMG202.jpg" alt="ad">
</a>
</td>
</tr>
@@ -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` 模块
- 客户端集成需使用微信官方提供的移动端SDKiOS/Android本项目为服务端SDK
- **微信开放平台**`weixin-java-open`)主要用于第三方平台,代公众号或小程序进行开发和管理
---------------------------------
### 版本说明

View 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.

View File

@@ -0,0 +1,154 @@
# 微信支付新版商户转账API支持
## 问题解答
**问题**: 新开通的商户号只能使用最新版本的商户转账接口WxJava是否支持
**答案**: **WxJava 已经完整支持新版商户转账API** 从2025年1月15日开始生效的新版转账API已在WxJava中实现。
## 新版转账API特性
### 1. API接口对比
| 特性 | 传统转账API | 新版转账API (2025.1.15+) |
|------|-------------|-------------------------|
| **服务类** | `MerchantTransferService` | `TransferService` |
| **API路径** | `/v3/transfer/batches` | `/v3/fund-app/mch-transfer/transfer-bills` |
| **转账方式** | 批量转账 | 单笔转账 |
| **场景支持** | 基础场景 | 丰富场景(如佣金报酬等) |
| **撤销功能** | ❌ 不支持 | ✅ 支持 |
| **授权模式** | 仅需确认模式 | ✅ 支持免确认授权模式 |
| **适用范围** | 所有商户 | **新开通商户必须使用** |
### 2. 新版API功能列表
**发起转账** - `transferBills()`
**查询转账** - `getBillsByOutBillNo()` / `getBillsByTransferBillNo()`
**撤销转账** - `transformBillsCancel()`
**回调通知** - `parseTransferBillsNotifyResult()`
**RSA加密** - 自动处理用户姓名加密
**场景支持** - 支持多种转账场景ID
**授权模式** - 支持免确认收款授权模式
### 3. 收款授权模式支持
**新增功能:免确认收款授权模式**
- **需确认收款授权模式**(默认):用户需要手动确认才能收款
- **免确认收款授权模式**:用户授权后,收款无需确认,转账直接到账
#### 使用方法
```java
// 免确认授权模式 - 提升用户体验
TransferBillsRequest request = TransferBillsRequest.newBuilder()
.receiptAuthorizationMode(WxPayConstants.ReceiptAuthorizationMode.NO_CONFIRM_RECEIPT_AUTHORIZATION)
// 其他参数...
.build();
// 需确认授权模式(默认)
TransferBillsRequest request2 = TransferBillsRequest.newBuilder()
.receiptAuthorizationMode(WxPayConstants.ReceiptAuthorizationMode.CONFIRM_RECEIPT_AUTHORIZATION)
// 其他参数...
.build();
```
## 快速开始
### 1. 获取服务实例
```java
// 获取WxPayService实例
WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(config);
// 获取新版转账服务 - 这就是新开通商户需要使用的服务!
TransferService transferService = wxPayService.getTransferService();
```
### 2. 发起转账新版API
```java
// 构建转账请求
TransferBillsRequest request = TransferBillsRequest.newBuilder()
.appid("your_appid") // 应用ID
.outBillNo("T" + System.currentTimeMillis()) // 商户转账单号
.transferSceneId("1005") // 转账场景ID佣金报酬
.openid("user_openid") // 用户openid
.userName("张三") // 收款用户姓名(可选,自动加密)
.transferAmount(100) // 转账金额(分)
.transferRemark("佣金报酬") // 转账备注
.build();
// 发起转账
TransferBillsResult result = transferService.transferBills(request);
System.out.println("转账成功,微信转账单号:" + result.getTransferBillNo());
```
### 3. 查询转账结果
```java
// 通过商户单号查询
TransferBillsGetResult result = transferService.getBillsByOutBillNo("T1642567890123");
// 通过微信转账单号查询
TransferBillsGetResult result2 = transferService.getBillsByTransferBillNo("wx_transfer_bill_no");
System.out.println("转账状态:" + result.getState());
```
### 4. 撤销转账(新功能)
```java
// 撤销转账
TransferBillsCancelResult cancelResult = transferService.transformBillsCancel("T1642567890123");
System.out.println("撤销状态:" + cancelResult.getState());
```
## 重要说明
### 转账场景ID (transfer_scene_id)
- **1005**: 佣金报酬(常用场景)
- 其他场景需要在微信商户平台申请
### 转账状态说明
- **ACCEPTED**: 转账已受理
- **PROCESSING**: 转账处理中
- **SUCCESS**: 转账成功
- **FAIL**: 转账失败
- **CANCELLED**: 转账撤销完成
### 新开通商户使用建议
1. **优先使用** `TransferService` 新版API
2. **不要使用** `MerchantTransferService` (可能不支持)
3. **必须设置** 转账场景ID (`transfer_scene_id`)
4. **建议开启** 回调通知以实时获取转账结果
## 完整示例代码
详细的使用示例请参考:
- 📄 [NEW_TRANSFER_API_USAGE.md](./NEW_TRANSFER_API_USAGE.md) - 详细使用指南
- 💻 [NewTransferApiExample.java](./weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java) - 完整代码示例
## 常见问题
**Q: 我是新开通的商户,应该使用哪个服务?**
A: 使用 `TransferService`这是专为新版API设计的服务。
**Q: 新版API和旧版API有什么区别**
A: 新版API使用单笔转账模式支持更丰富的转账场景并且支持撤销功能。
**Q: 如何设置转账场景ID**
A: 在商户平台申请相应场景常用的佣金报酬场景ID是"1005"。
**Q: 用户姓名需要加密吗?**
A: WxJava会自动处理RSA加密您只需要传入明文姓名即可。
## 版本要求
- WxJava 版本4.7.0+
- 支持时间2025年1月15日+
- 适用商户:所有商户(新开通商户强制使用)
通过以上说明新开通的微信支付商户可以放心使用WxJava进行商户转账操作

View File

@@ -0,0 +1,242 @@
# 微信支付新版商户转账API使用指南
## 概述
从2025年1月15日开始微信支付推出了新版的商户转账API。新开通的商户号只能使用最新版本的商户转账接口。WxJava 已经完整支持新版转账API。
## API对比
### 传统转账API (仍然支持)
- **服务类**: `MerchantTransferService`
- **API前缀**: `/v3/transfer/batches`
- **特点**: 支持批量转账,一次可以转账给多个用户
### 新版转账API (2025.1.15+)
- **服务类**: `TransferService`
- **API前缀**: `/v3/fund-app/mch-transfer/transfer-bills`
- **特点**: 单笔转账,支持更丰富的转账场景
## 收款授权模式功能
### 授权模式说明
微信支付转账支持两种收款授权模式:
#### 1. 需确认收款授权模式(默认)
- **常量**: `WxPayConstants.ReceiptAuthorizationMode.CONFIRM_RECEIPT_AUTHORIZATION`
- **特点**: 用户收到转账后需要手动点击确认才能到账
- **适用场景**: 一般的转账场景
- **用户体验**: 安全性高,但需要额外操作
#### 2. 免确认收款授权模式
- **常量**: `WxPayConstants.ReceiptAuthorizationMode.NO_CONFIRM_RECEIPT_AUTHORIZATION`
- **特点**: 用户事先授权后,转账直接到账,无需确认
- **适用场景**: 高频转账场景,如佣金发放、返现等
- **用户体验**: 体验流畅,无需额外操作
- **前提条件**: 需要用户事先进行授权
### 使用示例
#### 免确认授权模式转账
```java
TransferBillsRequest request = TransferBillsRequest.newBuilder()
.appid("your_appid")
.outBillNo("NO_CONFIRM_" + System.currentTimeMillis())
.transferSceneId("1005") // 佣金报酬场景
.openid("user_openid")
.transferAmount(200) // 2元
.transferRemark("免确认收款转账")
.receiptAuthorizationMode(WxPayConstants.ReceiptAuthorizationMode.NO_CONFIRM_RECEIPT_AUTHORIZATION)
.userRecvPerception("Y")
.build();
try {
TransferBillsResult result = transferService.transferBills(request);
System.out.println("转账成功,直接到账:" + result.getTransferBillNo());
} catch (WxPayException e) {
if ("USER_NOT_AUTHORIZED".equals(e.getErrCode())) {
System.err.println("用户未授权免确认收款,请先引导用户进行授权");
}
}
```
#### 需确认授权模式转账(默认)
```java
TransferBillsRequest request = TransferBillsRequest.newBuilder()
.appid("your_appid")
.outBillNo("CONFIRM_" + System.currentTimeMillis())
.transferSceneId("1005")
.openid("user_openid")
.transferAmount(150) // 1.5元
.transferRemark("需确认收款转账")
// .receiptAuthorizationMode(...) // 不设置时使用默认的确认模式
.userRecvPerception("Y")
.build();
TransferBillsResult result = transferService.transferBills(request);
System.out.println("转账发起成功,等待用户确认:" + result.getPackageInfo());
```
### 错误处理
使用免确认授权模式时,需要处理以下可能的错误:
```java
try {
TransferBillsResult result = transferService.transferBills(request);
} catch (WxPayException e) {
switch (e.getErrCode()) {
case "USER_NOT_AUTHORIZED":
// 用户未授权免确认收款
System.err.println("请先引导用户进行免确认收款授权");
// 可以引导用户到授权页面
break;
case "AUTHORIZATION_EXPIRED":
// 授权已过期
System.err.println("用户授权已过期,请重新授权");
break;
default:
System.err.println("转账失败:" + e.getMessage());
}
}
```
### 使用建议
1. **高频转账场景**推荐使用免确认模式,提升用户体验
2. **首次使用**需引导用户进行授权
3. **处理异常**妥善处理授权相关异常,提供友好的错误提示
4. **场景选择**根据业务场景选择合适的授权模式
## 使用新版转账API
### 1. 获取服务实例
```java
// 获取WxPayService实例
WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(config);
// 获取新版转账服务
TransferService transferService = wxPayService.getTransferService();
```
### 2. 发起转账
```java
// 构建转账请求
TransferBillsRequest request = TransferBillsRequest.newBuilder()
.appid("your_appid") // 应用ID
.outBillNo("T" + System.currentTimeMillis()) // 商户转账单号
.transferSceneId("1005") // 转账场景ID佣金报酬
.openid("user_openid") // 用户openid
.userName("张三") // 收款用户姓名(可选,需要加密)
.transferAmount(100) // 转账金额(分)
.transferRemark("佣金报酬") // 转账备注
.notifyUrl("https://your-domain.com/notify") // 回调地址(可选)
.userRecvPerception("Y") // 用户收款感知(可选)
.build();
try {
TransferBillsResult result = transferService.transferBills(request);
System.out.println("转账成功,微信转账单号:" + result.getTransferBillNo());
System.out.println("状态:" + result.getState());
} catch (WxPayException e) {
System.err.println("转账失败:" + e.getMessage());
}
```
### 3. 查询转账结果
```java
// 通过商户单号查询
String outBillNo = "T1642567890123";
TransferBillsGetResult result = transferService.getBillsByOutBillNo(outBillNo);
// 通过微信转账单号查询
String transferBillNo = "1000000000000000000000000001";
TransferBillsGetResult result2 = transferService.getBillsByTransferBillNo(transferBillNo);
System.out.println("转账状态:" + result.getState());
System.out.println("转账金额:" + result.getTransferAmount());
```
### 4. 撤销转账
```java
// 撤销转账(仅在特定状态下可撤销)
String outBillNo = "T1642567890123";
TransferBillsCancelResult cancelResult = transferService.transformBillsCancel(outBillNo);
System.out.println("撤销结果:" + cancelResult.getState());
```
### 5. 处理回调通知
```java
// 在回调接口中处理通知
@PostMapping("/transfer/notify")
public String handleTransferNotify(HttpServletRequest request) throws Exception {
String notifyData = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
// 构建签名头
SignatureHeader header = new SignatureHeader();
header.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
header.setNonce(request.getHeader("Wechatpay-Nonce"));
header.setSignature(request.getHeader("Wechatpay-Signature"));
header.setSerial(request.getHeader("Wechatpay-Serial"));
try {
TransferBillsNotifyResult notifyResult = transferService.parseTransferBillsNotifyResult(notifyData, header);
// 处理业务逻辑
String outBillNo = notifyResult.getOutBillNo();
String state = notifyResult.getState();
System.out.println("转账单号:" + outBillNo + ",状态:" + state);
return "SUCCESS";
} catch (WxPayException e) {
System.err.println("验签失败:" + e.getMessage());
return "FAIL";
}
}
```
## 重要参数说明
### 转账场景ID (transfer_scene_id)
- **1005**: 佣金报酬(常用)
- 其他场景ID需要在商户平台申请
### 转账状态
- **PROCESSING**: 转账中
- **SUCCESS**: 转账成功
- **FAILED**: 转账失败
- **REFUNDED**: 已退款
### 用户收款感知 (user_recv_perception)
- **Y**: 用户会收到微信转账通知
- **N**: 用户不会收到微信转账通知
## 新旧API对比总结
| 特性 | 传统API (MerchantTransferService) | 新版API (TransferService) |
|------|----------------------------------|---------------------------|
| 发起方式 | 批量转账 | 单笔转账 |
| API路径 | `/v3/transfer/batches` | `/v3/fund-app/mch-transfer/transfer-bills` |
| 场景支持 | 基础转账场景 | 丰富的转账场景 |
| 回调通知 | 支持 | 支持 |
| 撤销功能 | 不支持 | 支持 |
| 适用商户 | 所有商户 | 新开通商户必须使用 |
## 注意事项
1. **新开通的商户号**: 必须使用新版API (`TransferService`)
2. **转账场景ID**: 需要在商户平台申请相应的转账场景
3. **用户姓名加密**: 如果传入用户姓名会自动进行RSA加密
4. **回调验签**: 建议开启回调验签以确保安全性
5. **错误处理**: 妥善处理各种异常情况
通过以上指南您可以轻松使用WxJava的新版商户转账API功能。

112
docs/QUARKUS_SUPPORT.md Normal file
View 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)。

0
others/mvnw vendored Normal file → Executable file
View File

25
pom.xml
View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
<packaging>pom</packaging>
<name>WxJava - Weixin/Wechat Java SDK</name>
<description>微信开发Java SDK</description>
@@ -154,7 +154,7 @@
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.5.0</version>
<version>4.12.0</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -187,7 +187,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
<version>3.18.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
@@ -212,7 +212,7 @@
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.18.1</version>
<version>2.18.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -333,7 +333,7 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.78.1</version>
<version>1.80</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -468,6 +468,21 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version> <!-- 建议使用最新版本 -->
<executions>
<execution>
<id>attach-sources</id>
<!-- 绑定到 verify 阶段,在 install 之前执行 -->
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal> <!-- 使用 jar-no-fork 效率更高 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<packaging>pom</packaging>
<artifactId>wx-java-solon-plugins</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -8,6 +8,7 @@ import com.binarywang.solon.wxjava.channel.service.WxChannelMultiServicesImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.channel.api.WxChannelService;
import me.chanjar.weixin.channel.api.impl.WxChannelServiceHttpComponentsImpl;
import me.chanjar.weixin.channel.api.impl.WxChannelServiceHttpClientImpl;
import me.chanjar.weixin.channel.api.impl.WxChannelServiceImpl;
import me.chanjar.weixin.channel.config.WxChannelConfig;
@@ -84,6 +85,9 @@ public abstract class AbstractWxChannelConfiguration {
case HTTP_CLIENT:
wxChannelService = new WxChannelServiceHttpClientImpl();
break;
case HTTP_COMPONENTS:
wxChannelService = new WxChannelServiceHttpComponentsImpl();
break;
default:
wxChannelService = new WxChannelServiceImpl();
break;

View File

@@ -11,6 +11,10 @@ public enum HttpClientType {
* HttpClient
*/
HTTP_CLIENT,
/**
* HttpComponents
*/
HTTP_COMPONENTS
// WxChannelServiceOkHttpImpl 实现经测试无法正常完成业务固暂不支持OK_HTTP方式
// /**
// * OkHttp.

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -7,10 +7,7 @@ import com.binarywang.solon.wxjava.cp_multi.service.WxCpMultiServicesImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.impl.WxCpServiceApacheHttpClientImpl;
import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
import me.chanjar.weixin.cp.api.impl.WxCpServiceJoddHttpImpl;
import me.chanjar.weixin.cp.api.impl.WxCpServiceOkHttpImpl;
import me.chanjar.weixin.cp.api.impl.*;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import org.apache.commons.lang3.StringUtils;
@@ -96,6 +93,9 @@ public abstract class AbstractWxCpConfiguration {
case HTTP_CLIENT:
wxCpService = new WxCpServiceApacheHttpClientImpl();
break;
case HTTP_COMPONENTS:
wxCpService = new WxCpServiceHttpComponentsImpl();
break;
default:
wxCpService = new WxCpServiceImpl();
break;

View File

@@ -117,6 +117,10 @@ public class WxCpMultiProperties implements Serializable {
* HttpClient
*/
HTTP_CLIENT,
/**
* HttpComponents
*/
HTTP_COMPONENTS,
/**
* OkHttp
*/

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -46,13 +46,20 @@ public class WxPayAutoConfiguration {
payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
payConfig.setUseSandboxEnv(this.properties.isUseSandboxEnv());
payConfig.setNotifyUrl(StringUtils.trimToNull(this.properties.getNotifyUrl()));
//以下是apiv3以及支付分相关
payConfig.setServiceId(StringUtils.trimToNull(this.properties.getServiceId()));
payConfig.setPayScoreNotifyUrl(StringUtils.trimToNull(this.properties.getPayScoreNotifyUrl()));
payConfig.setPayScorePermissionNotifyUrl(StringUtils.trimToNull(this.properties.getPayScorePermissionNotifyUrl()));
payConfig.setPrivateKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyPath()));
payConfig.setPrivateCertPath(StringUtils.trimToNull(this.properties.getPrivateCertPath()));
payConfig.setCertSerialNo(StringUtils.trimToNull(this.properties.getCertSerialNo()));
payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiv3Key()));
payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));
payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));
payConfig.setApiHostUrl(StringUtils.trimToNull(this.properties.getApiHostUrl()));
payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial());
payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel());
wxPayService.setConfig(payConfig);
return wxPayService;

View File

@@ -82,4 +82,40 @@ public class WxPayProperties {
*/
private boolean useSandboxEnv;
/**
* 微信支付异步回调地址通知url必须为直接可访问的url不能携带参数
*/
private String notifyUrl;
/**
* 微信支付分授权回调地址
*/
private String payScorePermissionNotifyUrl;
/**
* 公钥ID
*/
private String publicKeyId;
/**
* pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
*/
private String publicKeyPath;
/**
* 自定义API主机地址用于替换默认的 https://api.mch.weixin.qq.com
* 例如http://proxy.company.com:8080
*/
private String apiHostUrl;
/**
* 是否将全部v3接口的请求都添加Wechatpay-Serial请求头默认不添加
*/
private boolean strictlyNeedWechatPaySerial = false;
/**
* 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认不使用
*/
private boolean fullPublicKeyModel = false;
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>wx-java-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<packaging>pom</packaging>
<artifactId>wx-java-spring-boot-starters</artifactId>
@@ -26,6 +26,7 @@
<module>wx-java-open-spring-boot-starter</module>
<module>wx-java-qidian-spring-boot-starter</module>
<module>wx-java-cp-multi-spring-boot-starter</module>
<module>wx-java-cp-tp-multi-spring-boot-starter</module>
<module>wx-java-cp-spring-boot-starter</module>
<module>wx-java-channel-spring-boot-starter</module>
<module>wx-java-channel-multi-spring-boot-starter</module>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -119,6 +119,8 @@ public abstract class AbstractWxChannelConfiguration {
config.setAesKey(aesKey);
}
config.setStableAccessToken(useStableAccessToken);
config.setApiHostUrl(StringUtils.trimToNull(wxChannelSingleProperties.getApiHostUrl()));
config.setAccessTokenUrl(StringUtils.trimToNull(wxChannelSingleProperties.getAccessTokenUrl()));
}
private void configHttp(WxChannelDefaultConfigImpl config, WxChannelMultiProperties.ConfigStorage storage) {

View File

@@ -40,4 +40,16 @@ public class WxChannelSingleProperties implements Serializable {
* 是否使用稳定版 Access Token
*/
private boolean useStableAccessToken = false;
/**
* 自定义API主机地址用于替换默认的 https://api.weixin.qq.com
* 例如http://proxy.company.com:8080
*/
private String apiHostUrl;
/**
* 自定义获取AccessToken地址用于向自定义统一服务获取AccessToken
* 例如http://proxy.company.com:8080/oauth/token
*/
private String accessTokenUrl;
}

View File

@@ -6,7 +6,7 @@
<dependencies>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-channel-multi-spring-boot-starter</artifactId>
<artifactId>wx-java-channel-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -16,6 +16,8 @@ public abstract class AbstractWxChannelConfigStorageConfiguration {
config.setAesKey(StringUtils.trimToNull(properties.getAesKey()));
config.setMsgDataFormat(StringUtils.trimToNull(properties.getMsgDataFormat()));
config.setStableAccessToken(properties.isUseStableAccessToken());
config.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl()));
config.setAccessTokenUrl(StringUtils.trimToNull(properties.getAccessTokenUrl()));
WxChannelProperties.ConfigStorage configStorageProperties = properties.getConfigStorage();
config.setHttpProxyHost(configStorageProperties.getHttpProxyHost());

View File

@@ -46,6 +46,18 @@ public class WxChannelProperties {
*/
private boolean useStableAccessToken = false;
/**
* 自定义API主机地址用于替换默认的 https://api.weixin.qq.com
* 例如http://proxy.company.com:8080
*/
private String apiHostUrl;
/**
* 自定义获取AccessToken地址用于向自定义统一服务获取AccessToken
* 例如http://proxy.company.com:8080/oauth/token
*/
private String accessTokenUrl;
/**
* 存储策略
*/

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -139,6 +139,9 @@ public abstract class AbstractWxCpConfiguration {
if (StringUtils.isNotBlank(msgAuditLibPath)) {
config.setMsgAuditLibPath(msgAuditLibPath);
}
if (StringUtils.isNotBlank(wxCpSingleProperties.getBaseApiUrl())) {
config.setBaseApiUrl(wxCpSingleProperties.getBaseApiUrl());
}
}
private void configHttp(WxCpDefaultConfigImpl config, WxCpMultiProperties.ConfigStorage storage) {

View File

@@ -43,4 +43,10 @@ public class WxCpSingleProperties implements Serializable {
* 微信企业号应用 会话存档类库路径
*/
private String msgAuditLibPath;
/**
* 自定义企业微信服务器baseUrl用于替换默认的 https://qyapi.weixin.qq.com
* 例如http://proxy.company.com:8080
*/
private String baseApiUrl;
}

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -48,6 +48,12 @@ public class WxCpProperties {
*/
private String msgAuditLibPath;
/**
* 自定义企业微信服务器baseUrl用于替换默认的 https://qyapi.weixin.qq.com
* 例如http://proxy.company.com:8080
*/
private String baseApiUrl;
/**
* 配置存储策略,默认内存
*/

View File

@@ -37,6 +37,9 @@ public abstract class AbstractWxCpConfigStorageConfiguration {
if (StringUtils.isNotBlank(msgAuditLibPath)) {
config.setMsgAuditLibPath(msgAuditLibPath);
}
if (StringUtils.isNotBlank(properties.getBaseApiUrl())) {
config.setBaseApiUrl(properties.getBaseApiUrl());
}
WxCpProperties.ConfigStorage storage = properties.getConfigStorage();
String httpProxyHost = storage.getHttpProxyHost();

View File

@@ -0,0 +1,97 @@
# wx-java-cp-multi-spring-boot-starter
企业微信多账号配置
- 实现多 WxCpService 初始化。
- 未实现 WxCpTpService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
- 未实现 WxCpCgService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
## 快速开始
1. 引入依赖
```xml
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-cp-multi-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
```
2. 添加配置(application.properties)
```properties
# 应用 1 配置
wx.cp.corps.tenantId1.corp-id = @corp-id
wx.cp.corps.tenantId1.corp-secret = @corp-secret
## 选填
wx.cp.corps.tenantId1.agent-id = @agent-id
wx.cp.corps.tenantId1.token = @token
wx.cp.corps.tenantId1.aes-key = @aes-key
wx.cp.corps.tenantId1.msg-audit-priKey = @msg-audit-priKey
wx.cp.corps.tenantId1.msg-audit-lib-path = @msg-audit-lib-path
# 应用 2 配置
wx.cp.corps.tenantId2.corp-id = @corp-id
wx.cp.corps.tenantId2.corp-secret = @corp-secret
## 选填
wx.cp.corps.tenantId2.agent-id = @agent-id
wx.cp.corps.tenantId2.token = @token
wx.cp.corps.tenantId2.aes-key = @aes-key
wx.cp.corps.tenantId2.msg-audit-priKey = @msg-audit-priKey
wx.cp.corps.tenantId2.msg-audit-lib-path = @msg-audit-lib-path
# 公共配置
## ConfigStorage 配置(选填)
wx.cp.config-storage.type=memory # 配置类型: memory(默认), jedis, redisson, redistemplate
## http 客户端配置(选填)
## # http客户端类型: http_client(默认), ok_http, jodd_http
wx.cp.config-storage.http-client-type=http_client
wx.cp.config-storage.http-proxy-host=
wx.cp.config-storage.http-proxy-port=
wx.cp.config-storage.http-proxy-username=
wx.cp.config-storage.http-proxy-password=
## 最大重试次数默认5 次,如果小于 0则为 0
wx.cp.config-storage.max-retry-times=5
## 重试时间间隔步进默认1000 毫秒,如果小于 0则为 1000
wx.cp.config-storage.retry-sleep-millis=1000
```
3. 支持自动注入的类型: `WxCpMultiServices`
4. 使用样例
```java
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.WxCpUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DemoService {
@Autowired
private WxCpTpMultiServices wxCpTpMultiServices;
public void test() {
// 应用 1 的 WxCpService
WxCpService wxCpService1 = wxCpMultiServices.getWxCpService("tenantId1");
WxCpUserService userService1 = wxCpService1.getUserService();
userService1.getUserId("xxx");
// todo ...
// 应用 2 的 WxCpService
WxCpService wxCpService2 = wxCpMultiServices.getWxCpService("tenantId2");
WxCpUserService userService2 = wxCpService2.getUserService();
userService2.getUserId("xxx");
// todo ...
// 应用 3 的 WxCpService
WxCpService wxCpService3 = wxCpMultiServices.getWxCpService("tenantId3");
// 判断是否为空
if (wxCpService3 == null) {
// todo wxCpService3 为空,请先配置 tenantId3 企业微信应用参数
return;
}
WxCpUserService userService3 = wxCpService3.getUserService();
userService3.getUserId("xxx");
// todo ...
}
}
```

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>wx-java-cp-tp-multi-spring-boot-starter</artifactId>
<name>WxJava - Spring Boot Starter for WxCp::支持多账号配置</name>
<description>微信企业号开发的 Spring Boot Starter::支持多账号配置</description>
<dependencies>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-cp</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,16 @@
package com.binarywang.spring.starter.wxjava.cp.autoconfigure;
import com.binarywang.spring.starter.wxjava.cp.configuration.WxCpTpMultiServicesAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 企业微信自动注册
*
* @author yl
* created on 2023/10/16
*/
@Configuration
@Import(WxCpTpMultiServicesAutoConfiguration.class)
public class WxCpTpMultiAutoConfiguration {
}

View File

@@ -0,0 +1,27 @@
package com.binarywang.spring.starter.wxjava.cp.configuration;
import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpTpInJedisTpConfiguration;
import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpTpInMemoryTpConfiguration;
import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpTpInRedisTemplateTpConfiguration;
import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpTpInRedissonTpConfiguration;
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 企业微信平台相关服务自动注册
*
* @author yl
* created on 2023/10/16
*/
@Configuration
@EnableConfigurationProperties(WxCpTpMultiProperties.class)
@Import({
WxCpTpInJedisTpConfiguration.class,
WxCpTpInMemoryTpConfiguration.class,
WxCpTpInRedissonTpConfiguration.class,
WxCpTpInRedisTemplateTpConfiguration.class
})
public class WxCpTpMultiServicesAutoConfiguration {
}

View File

@@ -0,0 +1,139 @@
package com.binarywang.spring.starter.wxjava.cp.configuration.services;
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties;
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpSingleProperties;
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices;
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServicesImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceApacheHttpClientImpl;
import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceImpl;
import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceJoddHttpImpl;
import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceOkHttpImpl;
import org.apache.commons.lang3.StringUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* WxCpConfigStorage 抽象配置类
*
* @author yl
* created on 2023/10/16
*/
@RequiredArgsConstructor
@Slf4j
public abstract class AbstractWxCpTpConfiguration {
/**
*
* @param wxCpTpMultiProperties 应用列表配置
* @param services 用于支持,应用启动之后,可以调用这个接口添加新服务对象。主要是配置是从数据库中读取的
* @return
*/
public WxCpTpMultiServices wxCpMultiServices(WxCpTpMultiProperties wxCpTpMultiProperties,WxCpTpMultiServices services) {
Map<String, WxCpTpSingleProperties> corps = wxCpTpMultiProperties.getCorps();
if (corps == null || corps.isEmpty()) {
log.warn("企业微信应用参数未配置,通过 WxCpMultiServices#getWxCpTpService(\"tenantId\")获取实例将返回空");
return new WxCpTpMultiServicesImpl();
}
if (services == null) {
services = new WxCpTpMultiServicesImpl();
}
Set<Map.Entry<String, WxCpTpSingleProperties>> entries = corps.entrySet();
for (Map.Entry<String, WxCpTpSingleProperties> entry : entries) {
String tenantId = entry.getKey();
WxCpTpSingleProperties wxCpTpSingleProperties = entry.getValue();
WxCpTpDefaultConfigImpl storage = this.wxCpTpConfigStorage(wxCpTpMultiProperties);
this.configCorp(storage, wxCpTpSingleProperties);
this.configHttp(storage, wxCpTpMultiProperties.getConfigStorage());
WxCpTpService wxCpTpService = this.wxCpTpService(storage, wxCpTpMultiProperties.getConfigStorage());
if (services.getWxCpTpService(tenantId) == null) {
// 不存在的才会添加到服务列表中
services.addWxCpTpService(tenantId, wxCpTpService);
}
}
return services;
}
/**
* 配置 WxCpDefaultConfigImpl
*
* @param wxCpTpMultiProperties 参数
* @return WxCpDefaultConfigImpl
*/
protected abstract WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties);
private WxCpTpService wxCpTpService(WxCpTpConfigStorage wxCpTpConfigStorage, WxCpTpMultiProperties.ConfigStorage storage) {
WxCpTpMultiProperties.HttpClientType httpClientType = storage.getHttpClientType();
WxCpTpService cpTpService;
switch (httpClientType) {
case OK_HTTP:
cpTpService = new WxCpTpServiceOkHttpImpl();
break;
case JODD_HTTP:
cpTpService = new WxCpTpServiceJoddHttpImpl();
break;
case HTTP_CLIENT:
cpTpService = new WxCpTpServiceApacheHttpClientImpl();
break;
default:
cpTpService = new WxCpTpServiceImpl();
break;
}
cpTpService.setWxCpTpConfigStorage(wxCpTpConfigStorage);
int maxRetryTimes = storage.getMaxRetryTimes();
if (maxRetryTimes < 0) {
maxRetryTimes = 0;
}
int retrySleepMillis = storage.getRetrySleepMillis();
if (retrySleepMillis < 0) {
retrySleepMillis = 1000;
}
cpTpService.setRetrySleepMillis(retrySleepMillis);
cpTpService.setMaxRetryTimes(maxRetryTimes);
return cpTpService;
}
private void configCorp(WxCpTpDefaultConfigImpl config, WxCpTpSingleProperties wxCpTpSingleProperties) {
String corpId = wxCpTpSingleProperties.getCorpId();
String providerSecret = wxCpTpSingleProperties.getProviderSecret();
String suiteId = wxCpTpSingleProperties.getSuiteId();
String token = wxCpTpSingleProperties.getToken();
String suiteSecret = wxCpTpSingleProperties.getSuiteSecret();
// 企业微信,私钥,会话存档路径
config.setCorpId(corpId);
config.setProviderSecret(providerSecret);
config.setEncodingAESKey(wxCpTpSingleProperties.getEncodingAESKey());
config.setSuiteId(suiteId);
config.setToken(token);
config.setSuiteSecret(suiteSecret);
}
private void configHttp(WxCpTpDefaultConfigImpl config, WxCpTpMultiProperties.ConfigStorage storage) {
String httpProxyHost = storage.getHttpProxyHost();
Integer httpProxyPort = storage.getHttpProxyPort();
String httpProxyUsername = storage.getHttpProxyUsername();
String httpProxyPassword = storage.getHttpProxyPassword();
if (StringUtils.isNotBlank(httpProxyHost)) {
config.setHttpProxyHost(httpProxyHost);
if (httpProxyPort != null) {
config.setHttpProxyPort(httpProxyPort);
}
if (StringUtils.isNotBlank(httpProxyUsername)) {
config.setHttpProxyUsername(httpProxyUsername);
}
if (StringUtils.isNotBlank(httpProxyPassword)) {
config.setHttpProxyPassword(httpProxyPassword);
}
}
}
}

View File

@@ -0,0 +1,78 @@
package com.binarywang.spring.starter.wxjava.cp.configuration.services;
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties;
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiRedisProperties;
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import me.chanjar.weixin.cp.config.impl.WxCpJedisConfigImpl;
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
import me.chanjar.weixin.cp.config.impl.WxCpTpJedisConfigImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* 自动装配基于 jedis 策略配置
*
* @author yl
* created on 2023/10/16
*/
@Configuration
@ConditionalOnProperty(
prefix = WxCpTpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis"
)
@RequiredArgsConstructor
public class WxCpTpInJedisTpConfiguration extends AbstractWxCpTpConfiguration {
private final WxCpTpMultiProperties wxCpTpMultiProperties;
private final ApplicationContext applicationContext;
@Bean
public WxCpTpMultiServices wxCpMultiServices() {
return this.wxCpMultiServices(wxCpTpMultiProperties,null);
}
@Override
protected WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties) {
return this.configRedis(wxCpTpMultiProperties);
}
private WxCpTpDefaultConfigImpl configRedis(WxCpTpMultiProperties wxCpTpMultiProperties) {
WxCpTpMultiRedisProperties wxCpTpMultiRedisProperties = wxCpTpMultiProperties.getConfigStorage().getRedis();
JedisPool jedisPool;
if (wxCpTpMultiRedisProperties != null && StringUtils.isNotEmpty(wxCpTpMultiRedisProperties.getHost())) {
jedisPool = getJedisPool(wxCpTpMultiProperties);
} else {
jedisPool = applicationContext.getBean(JedisPool.class);
}
return new WxCpTpJedisConfigImpl(jedisPool, wxCpTpMultiProperties.getConfigStorage().getKeyPrefix());
}
private JedisPool getJedisPool(WxCpTpMultiProperties wxCpTpMultiProperties) {
WxCpTpMultiProperties.ConfigStorage storage = wxCpTpMultiProperties.getConfigStorage();
WxCpTpMultiRedisProperties redis = storage.getRedis();
JedisPoolConfig config = new JedisPoolConfig();
if (redis.getMaxActive() != null) {
config.setMaxTotal(redis.getMaxActive());
}
if (redis.getMaxIdle() != null) {
config.setMaxIdle(redis.getMaxIdle());
}
if (redis.getMaxWaitMillis() != null) {
config.setMaxWaitMillis(redis.getMaxWaitMillis());
}
if (redis.getMinIdle() != null) {
config.setMinIdle(redis.getMinIdle());
}
config.setTestOnBorrow(true);
config.setTestWhileIdle(true);
return new JedisPool(config, redis.getHost(), redis.getPort(),
redis.getTimeout(), redis.getPassword(), redis.getDatabase());
}
}

View File

@@ -0,0 +1,39 @@
package com.binarywang.spring.starter.wxjava.cp.configuration.services;
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties;
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 自动装配基于内存策略配置
*
* @author yl
* created on 2023/10/16
*/
@Configuration
@ConditionalOnProperty(
prefix = WxCpTpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "memory", matchIfMissing = true
)
@RequiredArgsConstructor
public class WxCpTpInMemoryTpConfiguration extends AbstractWxCpTpConfiguration {
private final WxCpTpMultiProperties wxCpTpMultiProperties;
@Bean
public WxCpTpMultiServices wxCpMultiServices() {
return this.wxCpMultiServices(wxCpTpMultiProperties,null);
}
@Override
protected WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties) {
return this.configInMemory();
}
private WxCpTpDefaultConfigImpl configInMemory() {
return new WxCpTpDefaultConfigImpl();
}
}

View File

@@ -0,0 +1,45 @@
package com.binarywang.spring.starter.wxjava.cp.configuration.services;
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties;
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import me.chanjar.weixin.cp.config.impl.WxCpRedisTemplateConfigImpl;
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
import me.chanjar.weixin.cp.config.impl.WxCpTpRedisTemplateConfigImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
* 自动装配基于 redisTemplate 策略配置
*
* @author yl
* created on 2023/10/16
*/
@Configuration
@ConditionalOnProperty(
prefix = WxCpTpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redistemplate"
)
@RequiredArgsConstructor
public class WxCpTpInRedisTemplateTpConfiguration extends AbstractWxCpTpConfiguration {
private final WxCpTpMultiProperties wxCpTpMultiProperties;
private final ApplicationContext applicationContext;
@Bean
public WxCpTpMultiServices wxCpMultiServices() {
return this.wxCpMultiServices(wxCpTpMultiProperties,null);
}
@Override
protected WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties) {
return this.configRedisTemplate(wxCpTpMultiProperties);
}
private WxCpTpDefaultConfigImpl configRedisTemplate(WxCpTpMultiProperties wxCpTpMultiProperties) {
StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
return new WxCpTpRedisTemplateConfigImpl(redisTemplate, wxCpTpMultiProperties.getConfigStorage().getKeyPrefix());
}
}

View File

@@ -0,0 +1,68 @@
package com.binarywang.spring.starter.wxjava.cp.configuration.services;
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties;
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiRedisProperties;
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
import me.chanjar.weixin.cp.config.impl.AbstractWxCpTpInRedisConfigImpl;
import me.chanjar.weixin.cp.config.impl.WxCpTpRedissonConfigImpl;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.TransportMode;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 自动装配基于 redisson 策略配置
*
* @author yl
* created on 2023/10/16
*/
@Configuration
@ConditionalOnProperty(
prefix = WxCpTpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson"
)
@RequiredArgsConstructor
public class WxCpTpInRedissonTpConfiguration extends AbstractWxCpTpConfiguration {
private final WxCpTpMultiProperties wxCpTpMultiProperties;
private final ApplicationContext applicationContext;
@Bean
public WxCpTpMultiServices wxCpMultiServices() {
return this.wxCpMultiServices(wxCpTpMultiProperties,null);
}
@Override
protected WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties) {
return this.configRedisson(wxCpTpMultiProperties);
}
private WxCpTpDefaultConfigImpl configRedisson(WxCpTpMultiProperties wxCpTpMultiProperties) {
WxCpTpMultiRedisProperties redisProperties = wxCpTpMultiProperties.getConfigStorage().getRedis();
RedissonClient redissonClient;
if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) {
redissonClient = getRedissonClient(wxCpTpMultiProperties);
} else {
redissonClient = applicationContext.getBean(RedissonClient.class);
}
return new WxCpTpRedissonConfigImpl(redissonClient, wxCpTpMultiProperties.getConfigStorage().getKeyPrefix());
}
private RedissonClient getRedissonClient(WxCpTpMultiProperties wxCpTpMultiProperties) {
WxCpTpMultiProperties.ConfigStorage storage = wxCpTpMultiProperties.getConfigStorage();
WxCpTpMultiRedisProperties redis = storage.getRedis();
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + redis.getHost() + ":" + redis.getPort())
.setDatabase(redis.getDatabase())
.setPassword(redis.getPassword());
config.setTransportMode(TransportMode.NIO);
return Redisson.create(config);
}
}

View File

@@ -0,0 +1,129 @@
package com.binarywang.spring.starter.wxjava.cp.properties;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* 企业微信多企业接入相关配置属性
*
* @author yl
* created on 2023/10/16
*/
@Data
@NoArgsConstructor
@ConfigurationProperties(prefix = WxCpTpMultiProperties.PREFIX)
public class WxCpTpMultiProperties implements Serializable {
private static final long serialVersionUID = -1569510477055668503L;
public static final String PREFIX = "wx.cp.tp";
private Map<String, WxCpTpSingleProperties> corps = new HashMap<>();
/**
* 配置存储策略,默认内存
*/
private ConfigStorage configStorage = new ConfigStorage();
@Data
@NoArgsConstructor
public static class ConfigStorage implements Serializable {
private static final long serialVersionUID = 4815731027000065434L;
/**
* 存储类型
*/
private StorageType type = StorageType.memory;
/**
* 指定key前缀
*/
private String keyPrefix = "wx:cp:tp";
/**
* redis连接配置
*/
@NestedConfigurationProperty
private WxCpTpMultiRedisProperties redis = new WxCpTpMultiRedisProperties();
/**
* http客户端类型.
*/
private HttpClientType httpClientType = HttpClientType.HTTP_CLIENT;
/**
* http代理主机
*/
private String httpProxyHost;
/**
* http代理端口
*/
private Integer httpProxyPort;
/**
* http代理用户名
*/
private String httpProxyUsername;
/**
* http代理密码
*/
private String httpProxyPassword;
/**
* http 请求最大重试次数
* <pre>
* {@link me.chanjar.weixin.cp.api.WxCpService#setMaxRetryTimes(int)}
* {@link me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl#setMaxRetryTimes(int)}
* </pre>
*/
private int maxRetryTimes = 5;
/**
* http 请求重试间隔
* <pre>
* {@link me.chanjar.weixin.cp.api.WxCpService#setRetrySleepMillis(int)}
* {@link me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl#setRetrySleepMillis(int)}
* </pre>
*/
private int retrySleepMillis = 1000;
}
public enum StorageType {
/**
* 内存
*/
memory,
/**
* jedis
*/
jedis,
/**
* redisson
*/
redisson,
/**
* redistemplate
*/
redistemplate
}
public enum HttpClientType {
/**
* HttpClient
*/
HTTP_CLIENT,
/**
* OkHttp
*/
OK_HTTP,
/**
* JoddHttp
*/
JODD_HTTP
}
}

View File

@@ -0,0 +1,48 @@
package com.binarywang.spring.starter.wxjava.cp.properties;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* Redis配置.
*
* @author yl
* created on 2023/10/16
*/
@Data
@NoArgsConstructor
public class WxCpTpMultiRedisProperties implements Serializable {
private static final long serialVersionUID = -5924815351660074401L;
/**
* 主机地址.
*/
private String host;
/**
* 端口号.
*/
private int port = 6379;
/**
* 密码.
*/
private String password;
/**
* 超时.
*/
private int timeout = 2000;
/**
* 数据库.
*/
private int database = 0;
private Integer maxActive;
private Integer maxIdle;
private Integer maxWaitMillis;
private Integer minIdle;
}

View File

@@ -0,0 +1,43 @@
package com.binarywang.spring.starter.wxjava.cp.properties;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 企业微信企业相关配置属性
*
* @author yl
* created on 2023/10/16
*/
@Data
@NoArgsConstructor
public class WxCpTpSingleProperties implements Serializable {
private static final long serialVersionUID = -7502823825007859418L;
/**
* 微信企业号 corpId
*/
private String corpId;
/**
* 微信企业号 服务商 providerSecret
*/
private String providerSecret;
/**
* 微信企业号应用 token
*/
private String token;
private String encodingAESKey;
/**
* 微信企业号 第三方 应用 ID
*/
private String suiteId;
/**
* 微信企业号应用
*/
private String suiteSecret;
}

View File

@@ -0,0 +1,29 @@
package com.binarywang.spring.starter.wxjava.cp.service;
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
/**
* 企业微信 {@link WxCpTpService} 所有实例存放类.
*
* @author yl
* created on 2023/10/16
*/
public interface WxCpTpMultiServices {
/**
* 通过租户 Id 获取 WxCpTpService
*
* @param tenantId 租户 Id
* @return WxCpTpService
*/
WxCpTpService getWxCpTpService(String tenantId);
void addWxCpTpService(String tenantId, WxCpTpService wxCpService);
/**
* 根据租户 Id从列表中移除一个 WxCpTpService 实例
*
* @param tenantId 租户 Id
*/
void removeWxCpTpService(String tenantId);
}

View File

@@ -0,0 +1,44 @@
package com.binarywang.spring.starter.wxjava.cp.service;
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 企业微信 {@link WxCpTpMultiServices} 默认实现
*
* @author yl
* created on 2023/10/16
*/
public class WxCpTpMultiServicesImpl implements WxCpTpMultiServices {
private final Map<String, WxCpTpService> services = new ConcurrentHashMap<>();
/**
* 通过租户 Id 获取 WxCpTpService
*
* @param tenantId 租户 Id
* @return WxCpTpService
*/
@Override
public WxCpTpService getWxCpTpService(String tenantId) {
return this.services.get(tenantId);
}
/**
* 根据租户 Id添加一个 WxCpTpService 到列表
*
* @param tenantId 租户 Id
* @param wxCpService WxCpTpService 实例
*/
@Override
public void addWxCpTpService(String tenantId, WxCpTpService wxCpService) {
this.services.put(tenantId, wxCpService);
}
@Override
public void removeWxCpTpService(String tenantId) {
this.services.remove(tenantId);
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.binarywang.spring.starter.wxjava.cp.autoconfigure.WxCpTpMultiAutoConfiguration

View File

@@ -0,0 +1 @@
com.binarywang.spring.starter.wxjava.cp.autoconfigure.WxCpTpMultiAutoConfiguration

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -2,6 +2,7 @@ package com.binarywang.spring.starter.wxjava.miniapp.configuration;
import com.binarywang.spring.starter.wxjava.miniapp.configuration.services.WxMaInJedisConfiguration;
import com.binarywang.spring.starter.wxjava.miniapp.configuration.services.WxMaInMemoryConfiguration;
import com.binarywang.spring.starter.wxjava.miniapp.configuration.services.WxMaInRedisTemplateConfiguration;
import com.binarywang.spring.starter.wxjava.miniapp.configuration.services.WxMaInRedissonConfiguration;
import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaMultiProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -17,9 +18,10 @@ import org.springframework.context.annotation.Import;
@Configuration
@EnableConfigurationProperties(WxMaMultiProperties.class)
@Import({
WxMaInJedisConfiguration.class,
WxMaInMemoryConfiguration.class,
WxMaInRedissonConfiguration.class,
WxMaInJedisConfiguration.class,
WxMaInMemoryConfiguration.class,
WxMaInRedissonConfiguration.class,
WxMaInRedisTemplateConfiguration.class
})
public class WxMaMultiServiceConfiguration {
}

View File

@@ -108,12 +108,12 @@ public abstract class AbstractWxMaConfiguration {
return wxMaService;
}
private void configApp(WxMaDefaultConfigImpl config, WxMaSingleProperties corpProperties) {
String appId = corpProperties.getAppId();
String appSecret = corpProperties.getAppSecret();
String token = corpProperties.getToken();
String aesKey = corpProperties.getAesKey();
boolean useStableAccessToken = corpProperties.isUseStableAccessToken();
private void configApp(WxMaDefaultConfigImpl config, WxMaSingleProperties properties) {
String appId = properties.getAppId();
String appSecret = properties.getAppSecret();
String token = properties.getToken();
String aesKey = properties.getAesKey();
boolean useStableAccessToken = properties.isUseStableAccessToken();
config.setAppid(appId);
config.setSecret(appSecret);
@@ -123,7 +123,10 @@ public abstract class AbstractWxMaConfiguration {
if (StringUtils.isNotBlank(aesKey)) {
config.setAesKey(aesKey);
}
config.setMsgDataFormat(properties.getMsgDataFormat());
config.useStableAccessToken(useStableAccessToken);
config.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl()));
config.setAccessTokenUrl(StringUtils.trimToNull(properties.getAccessTokenUrl()));
}
private void configHttp(WxMaDefaultConfigImpl config, WxMaMultiProperties.ConfigStorage storage) {

View File

@@ -0,0 +1,43 @@
package com.binarywang.spring.starter.wxjava.miniapp.configuration.services;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl;
import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaMultiProperties;
import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServices;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
* 自动装配基于 redisTemplate 策略配置
*
* @author <a href="mailto:huangbing0730@gmail">hb0730</a> 2025/9/10
*/
@Configuration
@ConditionalOnProperty(prefix = WxMaMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redis_template")
@RequiredArgsConstructor
public class WxMaInRedisTemplateConfiguration extends AbstractWxMaConfiguration {
private final WxMaMultiProperties wxMaMultiProperties;
private final ApplicationContext applicationContext;
@Bean
public WxMaMultiServices wxMaMultiServices() {
return this.wxMaMultiServices(wxMaMultiProperties);
}
@Override
protected WxMaDefaultConfigImpl wxMaConfigStorage(WxMaMultiProperties wxMaMultiProperties) {
return this.configRedisTemplate(wxMaMultiProperties);
}
private WxMaDefaultConfigImpl configRedisTemplate(WxMaMultiProperties wxMaMultiProperties) {
StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
RedisTemplateWxRedisOps wxRedisOps = new RedisTemplateWxRedisOps(redisTemplate);
return new WxMaRedisBetterConfigImpl(wxRedisOps, wxMaMultiProperties.getConfigStorage().getKeyPrefix());
}
}

View File

@@ -33,8 +33,25 @@ public class WxMaSingleProperties implements Serializable {
*/
private String aesKey;
/**
* 消息格式XML或者JSON.
*/
private String msgDataFormat;
/**
* 是否使用稳定版 Access Token
*/
private boolean useStableAccessToken = false;
/**
* 自定义API主机地址用于替换默认的 https://api.weixin.qq.com
* 例如http://proxy.company.com:8080
*/
private String apiHostUrl;
/**
* 自定义获取AccessToken地址用于向自定义统一服务获取AccessToken
* 例如http://proxy.company.com:8080/oauth/token
*/
private String accessTokenUrl;
}

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -2,6 +2,8 @@ package com.binarywang.spring.starter.wxjava.miniapp.config.storage;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
import org.apache.commons.lang3.StringUtils;
/**
@@ -10,12 +12,15 @@ import org.apache.commons.lang3.StringUtils;
public abstract class AbstractWxMaConfigStorageConfiguration {
protected WxMaDefaultConfigImpl config(WxMaDefaultConfigImpl config, WxMaProperties properties) {
WxMaProperties.ConfigStorage storage = properties.getConfigStorage();
config.setAppid(StringUtils.trimToNull(properties.getAppid()));
config.setSecret(StringUtils.trimToNull(properties.getSecret()));
config.setToken(StringUtils.trimToNull(properties.getToken()));
config.setAesKey(StringUtils.trimToNull(properties.getAesKey()));
config.setMsgDataFormat(StringUtils.trimToNull(properties.getMsgDataFormat()));
config.useStableAccessToken(properties.isUseStableAccessToken());
config.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl()));
config.setAccessTokenUrl(StringUtils.trimToNull(properties.getAccessTokenUrl()));
WxMaProperties.ConfigStorage configStorageProperties = properties.getConfigStorage();
config.setHttpProxyHost(configStorageProperties.getHttpProxyHost());
@@ -25,6 +30,19 @@ public abstract class AbstractWxMaConfigStorageConfiguration {
config.setHttpProxyPort(configStorageProperties.getHttpProxyPort());
}
// 设置自定义的HttpClient超时配置
ApacheHttpClientBuilder clientBuilder = config.getApacheHttpClientBuilder();
if (clientBuilder == null) {
clientBuilder = DefaultApacheHttpClientBuilder.get();
}
if (clientBuilder instanceof DefaultApacheHttpClientBuilder) {
DefaultApacheHttpClientBuilder defaultBuilder = (DefaultApacheHttpClientBuilder) clientBuilder;
defaultBuilder.setConnectionTimeout(storage.getConnectionTimeout());
defaultBuilder.setSoTimeout(storage.getSoTimeout());
defaultBuilder.setConnectionRequestTimeout(storage.getConnectionRequestTimeout());
config.setApacheHttpClientBuilder(defaultBuilder);
}
int maxRetryTimes = configStorageProperties.getMaxRetryTimes();
if (configStorageProperties.getMaxRetryTimes() < 0) {
maxRetryTimes = 0;

View File

@@ -49,6 +49,18 @@ public class WxMaProperties {
*/
private boolean useStableAccessToken = false;
/**
* 自定义API主机地址用于替换默认的 https://api.weixin.qq.com
* 例如http://proxy.company.com:8080
*/
private String apiHostUrl;
/**
* 自定义获取AccessToken地址用于向自定义统一服务获取AccessToken
* 例如http://proxy.company.com:8080/oauth/token
*/
private String accessTokenUrl;
/**
* 存储策略
*/
@@ -112,6 +124,21 @@ public class WxMaProperties {
* </pre>
*/
private int maxRetryTimes = 5;
/**
* 连接超时时间,单位毫秒
*/
private int connectionTimeout = 5000;
/**
* 读数据超时时间即socketTimeout单位毫秒
*/
private int soTimeout = 5000;
/**
* 从连接池获取链接的超时时间,单位毫秒
*/
private int connectionRequestTimeout = 5000;
}
}

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -44,6 +44,11 @@
<artifactId>okhttp</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>

View File

@@ -7,10 +7,7 @@ import com.binarywang.spring.starter.wxjava.mp.service.WxMpMultiServicesImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceJoddHttpImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceOkHttpImpl;
import me.chanjar.weixin.mp.api.impl.*;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import me.chanjar.weixin.mp.config.WxMpHostConfig;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
@@ -91,6 +88,9 @@ public abstract class AbstractWxMpConfiguration {
case HTTP_CLIENT:
wxMpService = new WxMpServiceHttpClientImpl();
break;
case HTTP_COMPONENTS:
wxMpService = new WxMpServiceHttpComponentsImpl();
break;
default:
wxMpService = new WxMpServiceImpl();
break;

View File

@@ -142,6 +142,10 @@ public class WxMpMultiProperties implements Serializable {
* HttpClient
*/
HTTP_CLIENT,
/**
* HttpComponents
*/
HTTP_COMPONENTS,
/**
* OkHttp
*/

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -39,6 +39,11 @@
<artifactId>okhttp</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>

View File

@@ -4,6 +4,7 @@ import com.binarywang.spring.starter.wxjava.mp.enums.HttpClientType;
import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpComponentsImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceJoddHttpImpl;
import me.chanjar.weixin.mp.api.impl.WxMpServiceOkHttpImpl;
@@ -35,6 +36,9 @@ public class WxMpServiceAutoConfiguration {
case HttpClient:
wxMpService = newWxMpServiceHttpClientImpl();
break;
case HttpComponents:
wxMpService = newWxMpServiceHttpComponentsImpl();
break;
default:
wxMpService = newWxMpServiceImpl();
break;
@@ -60,4 +64,8 @@ public class WxMpServiceAutoConfiguration {
return new WxMpServiceJoddHttpImpl();
}
private WxMpService newWxMpServiceHttpComponentsImpl() {
return new WxMpServiceHttpComponentsImpl();
}
}

View File

@@ -9,6 +9,8 @@ import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.redis.JedisWxRedisOps;
import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
import me.chanjar.weixin.common.redis.WxRedisOps;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import me.chanjar.weixin.mp.config.WxMpHostConfig;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
@@ -122,6 +124,19 @@ public class WxMpStorageAutoConfiguration {
config.setSecret(properties.getSecret());
config.setToken(properties.getToken());
config.setAesKey(properties.getAesKey());
WxMpProperties.ConfigStorage storage = properties.getConfigStorage();
// 设置自定义的HttpClient超时配置
ApacheHttpClientBuilder clientBuilder = config.getApacheHttpClientBuilder();
if (clientBuilder == null) {
clientBuilder = DefaultApacheHttpClientBuilder.get();
}
if (clientBuilder instanceof DefaultApacheHttpClientBuilder) {
DefaultApacheHttpClientBuilder defaultBuilder = (DefaultApacheHttpClientBuilder) clientBuilder;
defaultBuilder.setConnectionTimeout(storage.getConnectionTimeout());
defaultBuilder.setSoTimeout(storage.getSoTimeout());
defaultBuilder.setConnectionRequestTimeout(storage.getConnectionRequestTimeout());
config.setApacheHttpClientBuilder(defaultBuilder);
}
config.setUseStableAccessToken(wxMpProperties.isUseStableAccessToken());
config.setHttpProxyHost(configStorageProperties.getHttpProxyHost());
config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername());

View File

@@ -19,4 +19,8 @@ public enum HttpClientType {
* JoddHttp.
*/
JoddHttp,
/**
* HttpComponents (Apache HttpClient 5.x).
*/
HttpComponents,
}

View File

@@ -102,6 +102,21 @@ public class WxMpProperties {
*/
private String httpProxyPassword;
/**
* 连接超时时间,单位毫秒
*/
private int connectionTimeout = 5000;
/**
* 读数据超时时间即socketTimeout单位毫秒
*/
private int soTimeout = 5000;
/**
* 从连接池获取链接的超时时间,单位毫秒
*/
private int connectionRequestTimeout = 5000;
}
}

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -28,6 +28,7 @@ public class WxOpenServiceAutoConfiguration {
}
@Bean
@ConditionalOnMissingBean
public WxOpenMessageRouter wxOpenMessageRouter(WxOpenService wxOpenService) {
return new WxOpenMessageRouter(wxOpenService);
}

View File

@@ -1,7 +1,10 @@
package com.binarywang.spring.starter.wxjava.open.config.storage;
import com.binarywang.spring.starter.wxjava.open.properties.WxOpenProperties;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage;
import org.apache.commons.lang3.StringUtils;
/**
* @author yl
@@ -28,6 +31,24 @@ public abstract class AbstractWxOpenConfigStorageConfiguration {
}
config.setRetrySleepMillis(retrySleepMillis);
config.setMaxRetryTimes(maxRetryTimes);
// 设置URL配置
config.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl()));
config.setAccessTokenUrl(StringUtils.trimToNull(properties.getAccessTokenUrl()));
// 设置自定义的HttpClient超时配置
ApacheHttpClientBuilder clientBuilder = config.getApacheHttpClientBuilder();
if (clientBuilder == null) {
clientBuilder = DefaultApacheHttpClientBuilder.get();
}
if (clientBuilder instanceof DefaultApacheHttpClientBuilder) {
DefaultApacheHttpClientBuilder defaultBuilder = (DefaultApacheHttpClientBuilder) clientBuilder;
defaultBuilder.setConnectionTimeout(storage.getConnectionTimeout());
defaultBuilder.setSoTimeout(storage.getSoTimeout());
defaultBuilder.setConnectionRequestTimeout(storage.getConnectionRequestTimeout());
config.setApacheHttpClientBuilder(defaultBuilder);
}
return config;
}
}

View File

@@ -40,6 +40,18 @@ public class WxOpenProperties {
*/
private String aesKey;
/**
* 自定义API主机地址用于替换默认的 https://api.weixin.qq.com
* 例如http://proxy.company.com:8080
*/
private String apiHostUrl;
/**
* 自定义获取AccessToken地址用于向自定义统一服务获取AccessToken
* 例如http://proxy.company.com:8080/oauth/token
*/
private String accessTokenUrl;
/**
* 存储策略.
*/
@@ -108,6 +120,21 @@ public class WxOpenProperties {
*/
private int maxRetryTimes = 5;
/**
* 连接超时时间,单位毫秒
*/
private int connectionTimeout = 5000;
/**
* 读数据超时时间即socketTimeout单位毫秒
*/
private int soTimeout = 5000;
/**
* 从连接池获取链接的超时时间,单位毫秒
*/
private int connectionRequestTimeout = 5000;
}
public enum StorageType {

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -50,15 +50,20 @@ public class WxPayAutoConfiguration {
payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
payConfig.setUseSandboxEnv(this.properties.isUseSandboxEnv());
payConfig.setNotifyUrl(StringUtils.trimToNull(this.properties.getNotifyUrl()));
//以下是apiv3以及支付分相关
payConfig.setServiceId(StringUtils.trimToNull(this.properties.getServiceId()));
payConfig.setPayScoreNotifyUrl(StringUtils.trimToNull(this.properties.getPayScoreNotifyUrl()));
payConfig.setPayScorePermissionNotifyUrl(StringUtils.trimToNull(this.properties.getPayScorePermissionNotifyUrl()));
payConfig.setPrivateKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyPath()));
payConfig.setPrivateCertPath(StringUtils.trimToNull(this.properties.getPrivateCertPath()));
payConfig.setCertSerialNo(StringUtils.trimToNull(this.properties.getCertSerialNo()));
payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiv3Key()));
payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));
payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));
payConfig.setApiHostUrl(StringUtils.trimToNull(this.properties.getApiHostUrl()));
payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial());
payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel());
wxPayService.setConfig(payConfig);
return wxPayService;

View File

@@ -59,11 +59,21 @@ public class WxPayProperties {
*/
private String apiv3Key;
/**
* 微信支付异步回调地址通知url必须为直接可访问的url不能携带参数
*/
private String notifyUrl;
/**
* 微信支付分回调地址
*/
private String payScoreNotifyUrl;
/**
* 微信支付分授权回调地址
*/
private String payScorePermissionNotifyUrl;
/**
* apiv3 商户apiclient_key.pem
*/
@@ -90,4 +100,20 @@ public class WxPayProperties {
*/
private boolean useSandboxEnv;
/**
* 自定义API主机地址用于替换默认的 https://api.mch.weixin.qq.com
* 例如http://proxy.company.com:8080
*/
private String apiHostUrl;
/**
* 是否将全部v3接口的请求都添加Wechatpay-Serial请求头默认不添加
*/
private boolean strictlyNeedWechatPaySerial = false;
/**
* 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认不使用
*/
private boolean fullPublicKeyModel = false;
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>wx-java-spring-boot-starters</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<artifactId>weixin-graal</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java</artifactId>
<version>4.7.6.B</version>
<version>4.7.9.B</version>
</parent>
<artifactId>weixin-java-channel</artifactId>
@@ -14,7 +14,7 @@
<description>微信视频号/微信小店 Java SDK</description>
<properties>
<jackson.version>2.18.1</jackson.version>
<jackson.version>2.18.4</jackson.version>
</properties>
<dependencies>
@@ -29,6 +29,11 @@
<artifactId>jodd-http</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -56,7 +56,7 @@ public class WxChannelBasicServiceImpl implements WxChannelBasicService {
public ChannelImageInfo uploadImg(int respType, File file, int height, int width) throws WxErrorException {
String url = IMG_UPLOAD_URL + "?upload_type=0&resp_type=" + respType + "&height=" + height + "&width=" + width;
RequestExecutor<String, File> executor = ChannelFileUploadRequestExecutor.create(shopService);
String resJson = (String) shopService.execute(executor, url, file);
String resJson = shopService.execute(executor, url, file);
UploadImageResponse response = ResponseUtils.decode(resJson, UploadImageResponse.class);
return response.getImgInfo();
}
@@ -64,19 +64,19 @@ public class WxChannelBasicServiceImpl implements WxChannelBasicService {
@Override
public QualificationFileResponse uploadQualificationFile(File file) throws WxErrorException {
RequestExecutor<String, File> executor = ChannelFileUploadRequestExecutor.create(shopService);
String resJson = (String) shopService.execute(executor, UPLOAD_QUALIFICATION_FILE, file);
String resJson = shopService.execute(executor, UPLOAD_QUALIFICATION_FILE, file);
return ResponseUtils.decode(resJson, QualificationFileResponse.class);
}
@Override
public ChannelImageResponse getImg(String mediaId) throws WxErrorException {
String appId = shopService.getConfig().getAppid();
ChannelImageResponse rs = null;
ChannelImageResponse rs;
try {
String url = GET_IMG_URL + "?media_id=" + mediaId;
RequestExecutor<ChannelImageResponse, String> executor = ChannelMediaDownloadRequestExecutor.create(shopService,
Files.createTempDirectory("wxjava-channel-" + appId).toFile());
rs = (ChannelImageResponse) shopService.execute(executor, url, null);
rs = shopService.execute(executor, url, null);
} catch (IOException e) {
throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e);
}

View File

@@ -0,0 +1,113 @@
package me.chanjar.weixin.channel.api.impl;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.channel.bean.token.StableTokenParam;
import me.chanjar.weixin.channel.config.WxChannelConfig;
import me.chanjar.weixin.channel.util.JsonUtils;
import me.chanjar.weixin.common.util.http.HttpClientType;
import me.chanjar.weixin.common.util.http.apache.ApacheBasicResponseHandler;
import me.chanjar.weixin.common.util.http.hc.BasicResponseHandler;
import me.chanjar.weixin.common.util.http.hc.DefaultHttpComponentsClientBuilder;
import me.chanjar.weixin.common.util.http.hc.HttpComponentsClientBuilder;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.io.entity.StringEntity;
import java.io.IOException;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.GET_ACCESS_TOKEN_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.GET_STABLE_ACCESS_TOKEN_URL;
/**
* @author altusea
*/
@Slf4j
public class WxChannelServiceHttpComponentsImpl extends BaseWxChannelServiceImpl<HttpClient, HttpHost> {
private CloseableHttpClient httpClient;
private HttpHost httpProxy;
@Override
public void initHttp() {
WxChannelConfig config = this.getConfig();
HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();
apacheHttpClientBuilder.httpProxyHost(config.getHttpProxyHost())
.httpProxyPort(config.getHttpProxyPort())
.httpProxyUsername(config.getHttpProxyUsername())
.httpProxyPassword(config.getHttpProxyPassword().toCharArray());
if (config.getHttpProxyHost() != null && config.getHttpProxyPort() > 0) {
this.httpProxy = new HttpHost(config.getHttpProxyHost(), config.getHttpProxyPort());
}
this.httpClient = apacheHttpClientBuilder.build();
}
@Override
public CloseableHttpClient getRequestHttpClient() {
return httpClient;
}
@Override
public HttpHost getRequestHttpProxy() {
return httpProxy;
}
@Override
public HttpClientType getRequestType() {
return HttpClientType.HTTP_COMPONENTS;
}
@Override
protected String doGetAccessTokenRequest() throws IOException {
WxChannelConfig config = this.getConfig();
String url = StringUtils.isNotEmpty(config.getAccessTokenUrl()) ? config.getAccessTokenUrl() :
StringUtils.isNotEmpty(config.getApiHostUrl()) ?
GET_ACCESS_TOKEN_URL.replace("https://api.weixin.qq.com", config.getApiHostUrl()) : GET_ACCESS_TOKEN_URL;
url = String.format(url, config.getAppid(), config.getSecret());
HttpGet httpGet = new HttpGet(url);
if (this.getRequestHttpProxy() != null) {
RequestConfig requestConfig = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
httpGet.setConfig(requestConfig);
}
return getRequestHttpClient().execute(httpGet, BasicResponseHandler.INSTANCE);
}
/**
* 获取稳定版接口调用凭据
*
* @param forceRefresh false 为普通模式, true为强制刷新模式
* @return 返回json的字符串
* @throws IOException the io exception
*/
@Override
protected String doGetStableAccessTokenRequest(boolean forceRefresh) throws IOException {
WxChannelConfig config = this.getConfig();
String url = GET_STABLE_ACCESS_TOKEN_URL;
HttpPost httpPost = new HttpPost(url);
if (this.getRequestHttpProxy() != null) {
RequestConfig requestConfig = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
httpPost.setConfig(requestConfig);
}
StableTokenParam requestParam = new StableTokenParam();
requestParam.setAppId(config.getAppid());
requestParam.setSecret(config.getSecret());
requestParam.setGrantType("client_credential");
requestParam.setForceRefresh(forceRefresh);
String requestJson = JsonUtils.encode(requestParam);
assert requestJson != null;
httpPost.setEntity(new StringEntity(requestJson, ContentType.APPLICATION_JSON));
return getRequestHttpClient().execute(httpPost, BasicResponseHandler.INSTANCE);
}
}

View File

@@ -41,11 +41,11 @@ public class WxChannelServiceOkHttpImpl extends BaseWxChannelServiceImpl<OkHttpC
this.httpProxy = OkHttpProxyInfo.httpProxy(this.config.getHttpProxyHost(), this.config.getHttpProxyPort(), this.config.getHttpProxyUsername(), this.config.getHttpProxyPassword());
okhttp3.OkHttpClient.Builder clientBuilder = new okhttp3.OkHttpClient.Builder();
clientBuilder.proxy(this.getRequestHttpProxy().getProxy());
clientBuilder.authenticator(new Authenticator() {
clientBuilder.proxyAuthenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(WxChannelServiceOkHttpImpl.this.httpProxy.getProxyUsername(), WxChannelServiceOkHttpImpl.this.httpProxy.getProxyPassword());
return response.request().newBuilder().header("Authorization", credential).build();
return response.request().newBuilder().header("Proxy-Authorization", credential).build();
}
});
this.httpClient = clientBuilder.build();

View File

@@ -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;
}
}

View File

@@ -0,0 +1,35 @@
package me.chanjar.weixin.channel.bean.after;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
import me.chanjar.weixin.channel.bean.base.AddressInfo;
/**
* 换货类型的发货物流信息
*
* @author <a href="https://github.com/lixize">Zeyes</a>
*/
@Data
@NoArgsConstructor
public class AfterSaleExchangeDeliveryInfo implements Serializable {
private static final long serialVersionUID = 3039216368034112038L;
/** 快递单号 */
@JsonProperty("waybill_id")
private String waybillId;
/** 物流公司id */
@JsonProperty("delivery_id")
private String deliveryId;
/** 物流公司名称 */
@JsonProperty("delivery_name")
private String deliveryName;
/** 地址信息 */
@JsonProperty("address_info")
private AddressInfo addressInfo;
}

View File

@@ -0,0 +1,42 @@
package me.chanjar.weixin.channel.bean.after;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 换货商品信息
*
* @author <a href="https://github.com/lixize">Zeyes</a>
*/
@Data
@NoArgsConstructor
public class AfterSaleExchangeProductInfo implements Serializable {
private static final long serialVersionUID = -1341436607011117854L;
/** 商品spuid */
@JsonProperty("product_id")
private String productId;
/** 旧商品skuid */
@JsonProperty("old_sku_id")
private String oldSkuId;
/** 新商品skuid */
@JsonProperty("new_sku_id")
private String newSkuId;
/** 数量 */
@JsonProperty("product_cnt")
private String productCnt;
/** 旧商品价格 */
@JsonProperty("old_sku_price")
private Integer oldSkuPrice;
/** 新商品价格 */
@JsonProperty("new_sku_price")
private Integer newSkuPrice;
}

View File

@@ -86,4 +86,16 @@ public class AfterSaleInfo implements Serializable {
/** 仅在待商家审核退款退货申请或收货期间返回,表示操作剩余时间(秒数)*/
@JsonProperty("deadline")
private Long deadline;
/** 售后换货商品信息 */
@JsonProperty("exchange_product_info")
private AfterSaleExchangeProductInfo exchangeProductInfo;
/** 售后换货物流信息 */
@JsonProperty("exchange_delivery_info")
private AfterSaleExchangeDeliveryInfo exchangeDeliveryInfo;
/** 售后换货虚拟号码信息 */
@JsonProperty("virtual_tel_num_info")
private AfterSaleVirtualNumberInfo virtualTelNumInfo;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -36,4 +36,9 @@ public class AfterSaleRejectReason implements Serializable {
@JsonProperty("reject_reason")
private String rejectReason;
/**
* 售后拒绝原因适用场景
*/
@JsonProperty("reject_scene")
private Integer rejectScene;
}

View File

@@ -0,0 +1,26 @@
package me.chanjar.weixin.channel.bean.after;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 虚拟号码信息
*
* @author <a href="https://github.com/lixize">Zeyes</a>
*/
@Data
@NoArgsConstructor
public class AfterSaleVirtualNumberInfo implements Serializable {
private static final long serialVersionUID = -5756618937333859985L;
/** 虚拟号码 */
@JsonProperty("virtual_tel_number")
private String virtualTelNumber;
/** 虚拟号码过期时间 */
@JsonProperty("virtual_tel_expire_time")
private Long virtualTelExpireTime;
}

View File

@@ -26,7 +26,7 @@ public class ComplaintHistory implements Serializable {
/** 用户联系电话 */
@JsonProperty("phone_number")
private Integer phoneNumber;
private String phoneNumber;
/** 相关文本内容 */
@JsonProperty("content")

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

Some files were not shown because too many files have changed in this diff Show More