Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
61 changes: 61 additions & 0 deletions docs/WEIXIN_JAVA_STORE_MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# 微信小店模块迁移指南

从本版本开始,微信小店能力由独立的 `weixin-java-store` 模块提供。原有
`weixin-java-channel` 小店 API 仍可继续使用,且行为不变;它们已标记为
`@Deprecated`,以便在后续版本中引导迁移。

## 依赖替换

将:

```xml
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-channel</artifactId>
</dependency>
```

替换或并行增加为:

```xml
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-store</artifactId>
</dependency>
```

新模块不依赖 `weixin-java-channel`;两者可以同时引入,适合按业务逐步迁移。

## 包名与入口替换

| 旧入口 | 新入口 |
| --- | --- |
| `me.chanjar.weixin.channel.api.WxChannelService` | `com.binarywang.wxjava.store.api.WxStoreService` |
| `WxChannelProductService` | `WxStoreProductService` |
| `WxChannelOrderService` | `WxStoreOrderService` |
| `WxChannelAfterSaleService` | `WxStoreAfterSaleService` |
| `WxChannelFundService` | `WxStoreFundService` |
| `WxChannelWarehouseService` | `WxStoreWarehouseService` |

所有新模型均位于 `com.binarywang.wxjava.store.bean.*`。旧模型不会改包或删除;
由于新旧模型是独立类型,业务层应在迁移边界显式完成类型转换。

## 框架集成

微信小店提供独立的 Spring Boot 与 Solon 集成模块,并统一使用 `wx.store`
作为配置前缀:

| 场景 | 模块 |
| --- | --- |
| Spring Boot 单账号 | `wx-java-store-spring-boot-starter` |
| Spring Boot 多账号 | `wx-java-store-multi-spring-boot-starter` |
| Solon 单账号 | `wx-java-store-solon-plugin` |
| Solon 多账号 | `wx-java-store-multi-solon-plugin` |

原有 Channel Starter/Plugin 及其 `wx.channel` 配置保持不变,可与新的
Store Starter/Plugin 并存。

## 不属于 store 的能力

视频号直播、Finder、联盟分销、留资组件与达人罗盘继续使用
`weixin-java-channel`,不会被弃用,也不会由 `WxStoreService` 暴露。
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
<module>weixin-java-qidian</module>
<module>weixin-java-aispeech</module>
<module>weixin-java-channel</module>
<module>weixin-java-store</module>
<module>spring-boot-starters</module>
<module>solon-plugins</module>
<module>wx-java-bom</module>
Expand Down
2 changes: 2 additions & 0 deletions solon-plugins/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
<module>wx-java-cp-solon-plugin</module>
<module>wx-java-channel-solon-plugin</module>
<module>wx-java-channel-multi-solon-plugin</module>
<module>wx-java-store-solon-plugin</module>
<module>wx-java-store-multi-solon-plugin</module>
</modules>

<dependencies>
Expand Down
111 changes: 111 additions & 0 deletions solon-plugins/wx-java-store-multi-solon-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# wx-java-store-multi-solon-plugin

## 快速开始

1. 引入依赖
```xml
<dependencies>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-store-multi-solon-plugin</artifactId>
<version>${version}</version>
</dependency>

<!-- 配置存储方式为jedis 则引入jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>

<!-- 配置存储方式为redisson 则引入redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>
</dependencies>
```
2. 添加配置(app.properties)
```properties
# 视频号配置
## 应用 1 配置(必填)
wx.store.apps.tenantId1.app-id=@appId
wx.store.apps.tenantId1.secret=@secret
## 选填
wx.store.apps.tenantId1.use-stable-access-token=false
wx.store.apps.tenantId1.token=
wx.store.apps.tenantId1.aes-key=
## 应用 2 配置(必填)
wx.store.apps.tenantId2.app-id=@appId
wx.store.apps.tenantId2.secret=@secret
## 选填
wx.store.apps.tenantId2.use-stable-access-token=false
wx.store.apps.tenantId2.token=
wx.store.apps.tenantId2.aes-key=

# ConfigStorage 配置(选填)
## 配置类型: memory(默认), jedis, redisson, redis_template
wx.store.config-storage.type=memory
## 相关redis前缀配置: wx:store:multi(默认)
wx.store.config-storage.key-prefix=wx:store:multi
wx.store.config-storage.redis.host=127.0.0.1
wx.store.config-storage.redis.port=6379
wx.store.config-storage.redis.password=123456

# http 客户端配置(选填)
## # http客户端类型: http_client(默认)
wx.store.config-storage.http-client-type=http_client
wx.store.config-storage.http-proxy-host=
wx.store.config-storage.http-proxy-port=
wx.store.config-storage.http-proxy-username=
wx.store.config-storage.http-proxy-password=
## 最大重试次数,默认:5 次,如果小于 0,则为 0
wx.store.config-storage.max-retry-times=5
## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000
wx.store.config-storage.retry-sleep-millis=1000
```
3. 自动注入的类型:`WxStoreMultiServices`

4. 使用样例

```java
import com.binarywang.solon.wxjava.store.service.WxStoreMultiServices;
import com.binarywang.wxjava.store.api.WxStoreService;
import com.binarywang.wxjava.store.api.WxFinderLiveService;
import com.binarywang.wxjava.store.bean.lead.component.response.FinderAttrResponse;
import me.chanjar.weixin.common.error.WxErrorException;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;

@Component
public class DemoService {
@Inject
private WxStoreMultiServices wxStoreMultiServices;

public void test() throws WxErrorException {
// 应用 1 的 WxStoreService
WxStoreService wxStoreService1 = wxStoreMultiServices.getWxStoreService("tenantId1");
WxFinderLiveService finderLiveService = wxStoreService1.getFinderLiveService();
FinderAttrResponse response1 = finderLiveService.getFinderAttrByAppid();
// todo ...

// 应用 2 的 WxStoreService
WxStoreService wxStoreService2 = wxStoreMultiServices.getWxStoreService("tenantId2");
WxFinderLiveService finderLiveService2 = wxStoreService2.getFinderLiveService();
FinderAttrResponse response2 = finderLiveService2.getFinderAttrByAppid();
// todo ...

// 应用 3 的 WxStoreService
WxStoreService wxStoreService3 = wxStoreMultiServices.getWxStoreService("tenantId3");
// 判断是否为空
if (wxStoreService3 == null) {
// todo wxStoreService3 为空,请先配置 tenantId3 微信小店应用参数
return;
}
WxFinderLiveService finderLiveService3 = wxStoreService3.getFinderLiveService();
FinderAttrResponse response3 = finderLiveService3.getFinderAttrByAppid();
// todo ...
}
}
```
43 changes: 43 additions & 0 deletions solon-plugins/wx-java-store-multi-solon-plugin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?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-solon-plugins</artifactId>
<groupId>com.github.binarywang</groupId>
<version>4.8.6.B</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>wx-java-store-multi-solon-plugin</artifactId>
<name>WxJava - Solon Plugin for Store::支持多账号配置</name>
<description>微信小店开发的 Solon Plugin::支持多账号配置</description>

<dependencies>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-store</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.jodd</groupId>
<artifactId>jodd-http</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.binarywang.solon.wxjava.store.configuration.services;

import com.binarywang.solon.wxjava.store.enums.HttpClientType;
import com.binarywang.solon.wxjava.store.properties.WxStoreMultiProperties;
import com.binarywang.solon.wxjava.store.properties.WxStoreSingleProperties;
import com.binarywang.solon.wxjava.store.service.WxStoreMultiServices;
import com.binarywang.solon.wxjava.store.service.WxStoreMultiServicesImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.binarywang.wxjava.store.api.WxStoreService;
import com.binarywang.wxjava.store.api.impl.WxStoreServiceHttpComponentsImpl;
import com.binarywang.wxjava.store.api.impl.WxStoreServiceHttpClientImpl;
import com.binarywang.wxjava.store.api.impl.WxStoreServiceImpl;
import com.binarywang.wxjava.store.config.WxStoreConfig;
import com.binarywang.wxjava.store.config.impl.WxStoreDefaultConfigImpl;
import org.apache.commons.lang3.StringUtils;

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
* WxStoreConfigStorage 抽象配置类
*
* @author <a href="https://github.com/Winnie-by996">Winnie</a> 2024/9/13
* @author noear
*/
@RequiredArgsConstructor
@Slf4j
public abstract class AbstractWxStoreConfiguration {
protected WxStoreMultiServices wxStoreMultiServices(WxStoreMultiProperties wxStoreMultiProperties) {
Map<String, WxStoreSingleProperties> appsMap = wxStoreMultiProperties.getApps();
if (appsMap == null || appsMap.isEmpty()) {
log.warn("微信小店应用参数未配置,通过 WxStoreMultiServices#getWxStoreService(\"tenantId\")获取实例将返回空");
return new WxStoreMultiServicesImpl();
}
/**
* 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
*
* 查看 {@link com.binarywang.wxjava.store.config.impl.WxStoreRedisConfigImpl#setAppid(String)}
*/
Collection<WxStoreSingleProperties> apps = appsMap.values();
if (apps.size() > 1) {
// 校验 appId 是否唯一
boolean multi = apps.stream()
// 没有 appId,如果不判断是否为空,这里会报 NPE 异常
.collect(Collectors.groupingBy(c -> c.getAppId() == null ? 0 : c.getAppId(), Collectors.counting()))
.entrySet().stream().anyMatch(e -> e.getValue() > 1);
if (multi) {
throw new RuntimeException("请确保微信小店配置 appId 的唯一性");
}
}
WxStoreMultiServicesImpl services = new WxStoreMultiServicesImpl();

Set<Map.Entry<String, WxStoreSingleProperties>> entries = appsMap.entrySet();
for (Map.Entry<String, WxStoreSingleProperties> entry : entries) {
String tenantId = entry.getKey();
WxStoreSingleProperties wxStoreSingleProperties = entry.getValue();
WxStoreDefaultConfigImpl storage = this.wxStoreConfigStorage(wxStoreMultiProperties);
this.configApp(storage, wxStoreSingleProperties);
this.configHttp(storage, wxStoreMultiProperties.getConfigStorage());
WxStoreService wxStoreService = this.wxStoreService(storage, wxStoreMultiProperties);
services.addWxStoreService(tenantId, wxStoreService);
}
return services;
}

/**
* 配置 WxStoreDefaultConfigImpl
*
* @param wxStoreMultiProperties 参数
* @return WxStoreDefaultConfigImpl
*/
protected abstract WxStoreDefaultConfigImpl wxStoreConfigStorage(WxStoreMultiProperties wxStoreMultiProperties);

public WxStoreService wxStoreService(WxStoreConfig wxStoreConfig, WxStoreMultiProperties wxStoreMultiProperties) {
WxStoreMultiProperties.ConfigStorage storage = wxStoreMultiProperties.getConfigStorage();
HttpClientType httpClientType = storage.getHttpClientType();
WxStoreService wxStoreService;
switch (httpClientType) {
// case OK_HTTP:
// wxStoreService = new WxStoreServiceOkHttpImpl(false, false);
// break;
case HTTP_CLIENT:
wxStoreService = new WxStoreServiceHttpClientImpl();
break;
case HTTP_COMPONENTS:
wxStoreService = new WxStoreServiceHttpComponentsImpl();
break;
default:
wxStoreService = new WxStoreServiceImpl();
break;
}

wxStoreService.setConfig(wxStoreConfig);
int maxRetryTimes = storage.getMaxRetryTimes();
if (maxRetryTimes < 0) {
maxRetryTimes = 0;
}
int retrySleepMillis = storage.getRetrySleepMillis();
if (retrySleepMillis < 0) {
retrySleepMillis = 1000;
}
wxStoreService.setRetrySleepMillis(retrySleepMillis);
wxStoreService.setMaxRetryTimes(maxRetryTimes);
return wxStoreService;
}

private void configApp(WxStoreDefaultConfigImpl config, WxStoreSingleProperties wxStoreSingleProperties) {
String appId = wxStoreSingleProperties.getAppId();
String appSecret = wxStoreSingleProperties.getSecret();
String token = wxStoreSingleProperties.getToken();
String aesKey = wxStoreSingleProperties.getAesKey();
boolean useStableAccessToken = wxStoreSingleProperties.isUseStableAccessToken();

config.setAppid(appId);
config.setSecret(appSecret);
if (StringUtils.isNotBlank(token)) {
config.setToken(token);
}
if (StringUtils.isNotBlank(aesKey)) {
config.setAesKey(aesKey);
}
config.setStableAccessToken(useStableAccessToken);
}

private void configHttp(WxStoreDefaultConfigImpl config, WxStoreMultiProperties.ConfigStorage storage) {
String httpProxyHost = storage.getHttpProxyHost();
Integer httpProxyPort = storage.getHttpProxyPort();
String httpProxyUsername = storage.getHttpProxyUsername();
String httpProxyPassword = storage.getHttpProxyPassword();
if (StringUtils.isNotBlank(httpProxyHost)) {
config.setHttpProxyHost(httpProxyHost);
if (httpProxyPort != null) {
config.setHttpProxyPort(httpProxyPort);
}
if (StringUtils.isNotBlank(httpProxyUsername)) {
config.setHttpProxyUsername(httpProxyUsername);
}
if (StringUtils.isNotBlank(httpProxyPassword)) {
config.setHttpProxyPassword(httpProxyPassword);
}
}
}
}
Loading