# Conflicts:
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java
#	yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java
#	yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPayClientConfig.java
pull/125/MERGE
YunaiV 2024-07-26 09:25:01 +08:00
commit b026d186a6
10 changed files with 55 additions and 97 deletions

View File

@ -157,7 +157,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
// 拼接条件
return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
// Parenthesis 的目的,是提供 (1,2,3) 的 () 左右括号
new Parenthesis(new ExpressionList<>(CollectionUtils.convertList(deptIds, LongValue::new))));
new Parenthesis(new ExpressionList<LongValue>(CollectionUtils.convertList(deptIds, LongValue::new))));
}
private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {

View File

@ -2,9 +2,11 @@ package cn.iocoder.yudao.framework.web.core.handler;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@ -208,8 +210,17 @@ public class GlobalExceptionHandler {
// 不包含的时候,才进行打印,避免 ex 堆栈过多
if (!IGNORE_ERROR_MESSAGES.contains(ex.getMessage())) {
// 即使打印,也只打印第一层 StackTraceElement并且使用 warn 在控制台输出,更容易看到
StackTraceElement[] stackTrace = ex.getStackTrace();
log.warn("[serviceExceptionHandler]\n\t{}", stackTrace[0]);
try {
StackTraceElement[] stackTraces = ex.getStackTrace();
for (StackTraceElement stackTrace : stackTraces) {
if (ObjUtil.notEqual(stackTrace.getClassName(), ServiceExceptionUtil.class.getName())) {
log.warn("[serviceExceptionHandler]\n\t{}", stackTrace);
break;
}
}
} catch (Exception ignored) {
// 忽略日志,避免影响主流程
}
}
return CommonResult.error(ex.getCode(), ex.getMessage());
}

View File

@ -22,9 +22,9 @@ import java.time.LocalDateTime;
public class PayOrderSyncJob {
/**
* N
* N
*
* 10
* 10
* 10
* 3060
*/

View File

@ -14,22 +14,17 @@ import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper;
import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.Getter;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.validation.Validator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.validation.Validator;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
/**
@ -42,25 +37,6 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
@Validated
public class PayChannelServiceImpl implements PayChannelService {
/**
* {@link PayClient} smsClientFactory
*/
@Getter
private final LoadingCache<Long, PayClient> clientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
new CacheLoader<Long, PayClient>() {
@Override
public PayClient load(Long id) {
// 查询,然后尝试清空
PayChannelDO channel = payChannelMapper.selectById(id);
if (channel != null) {
payClientFactory.createOrUpdatePayClient(channel.getId(), channel.getCode(), channel.getConfig());
}
return payClientFactory.getPayClient(id);
}
});
@Resource
private PayClientFactory payClientFactory;
@ -102,9 +78,6 @@ public class PayChannelServiceImpl implements PayChannelService {
PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO)
.setConfig(parseConfig(dbChannel.getCode(), updateReqVO.getConfig()));
payChannelMapper.updateById(channel);
// 清空缓存
clearCache(channel.getId());
}
/**
@ -135,18 +108,6 @@ public class PayChannelServiceImpl implements PayChannelService {
// 删除
payChannelMapper.deleteById(id);
// 清空缓存
clearCache(id);
}
/**
*
*
* @param id
*/
private void clearCache(Long id) {
clientCache.invalidate(id);
}
private PayChannelDO validateChannelExists(Long id) {
@ -202,7 +163,8 @@ public class PayChannelServiceImpl implements PayChannelService {
@Override
public PayClient getPayClient(Long id) {
return clientCache.getUnchecked(id);
PayChannelDO channel = validPayChannel(id);
return payClientFactory.createOrUpdatePayClient(id, channel.getCode(), channel.getConfig());
}
}

View File

@ -17,8 +17,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import javax.validation.Validator;
import jakarta.annotation.Resource;
import jakarta.validation.Validator;
import java.util.Collections;
import java.util.List;
@ -60,8 +60,6 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
PayChannelDO channel = channelMapper.selectById(channelId);
assertPojoEquals(reqVO, channel, "config");
assertPojoEquals(config, channel.getConfig());
// 校验缓存
assertNull(channelService.getClientCache().getIfPresent(channelId));
}
@Test
@ -102,8 +100,6 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
PayChannelDO channel = channelMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, channel, "config");
assertPojoEquals(config, channel.getConfig());
// 校验缓存
assertNull(channelService.getClientCache().getIfPresent(channel.getId()));
}
@Test
@ -134,8 +130,6 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
channelService.deleteChannel(id);
// 校验数据不存在了
assertNull(channelMapper.selectById(id));
// 校验缓存
assertNull(channelService.getClientCache().getIfPresent(id));
}
@Test
@ -306,20 +300,20 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> {
o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
o.setConfig(randomAlipayPayClientConfig());
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
});
channelMapper.insert(channel);
// mock 参数
Long id = channel.getId();
// mock 方法
PayClient mockClient = mock(PayClient.class);
when(payClientFactory.getPayClient(eq(id))).thenReturn(mockClient);
when(payClientFactory.createOrUpdatePayClient(eq(id), eq(channel.getCode()), eq(channel.getConfig())))
.thenReturn(mockClient);
// 调用
PayClient client = channelService.getPayClient(id);
// 断言
assertSame(client, mockClient);
verify(payClientFactory).createOrUpdatePayClient(eq(id), eq(channel.getCode()),
eq(channel.getConfig()));
}
public WxPayClientConfig randomWxPayClientConfig() {

View File

@ -23,9 +23,10 @@ public interface PayClientFactory {
* @param channelId
* @param channelCode
* @param config
* @return
*/
<Config extends PayClientConfig> void createOrUpdatePayClient(Long channelId, String channelCode,
Config config);
<Config extends PayClientConfig> PayClient createOrUpdatePayClient(Long channelId, String channelCode,
Config config);
/**
* Class PayClient

View File

@ -71,8 +71,8 @@ public class PayClientFactoryImpl implements PayClientFactory {
@Override
@SuppressWarnings("unchecked")
public <Config extends PayClientConfig> void createOrUpdatePayClient(Long channelId, String channelCode,
Config config) {
public <Config extends PayClientConfig> PayClient createOrUpdatePayClient(Long channelId, String channelCode,
Config config) {
AbstractPayClient<Config> client = (AbstractPayClient<Config>) clients.get(channelId);
if (client == null) {
client = this.createPayClient(channelId, channelCode, config);
@ -81,6 +81,7 @@ public class PayClientFactoryImpl implements PayClientFactory {
} else {
client.refresh(config);
}
return client;
}
@SuppressWarnings("unchecked")

View File

@ -36,6 +36,7 @@ import java.util.Objects;
import static cn.hutool.core.date.DatePattern.*;
import static cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig.API_VERSION_V2;
import static cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig.API_VERSION_V3;
/**
* 退
@ -59,19 +60,14 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
protected void doInit(String tradeType) {
// 创建 config 配置
WxPayConfig payConfig = new WxPayConfig();
BeanUtil.copyProperties(config, payConfig, "keyContent", "privateKeyContent", "privateCertContent");
BeanUtil.copyProperties(config, payConfig, "keyContent", "privateKeyContent");
payConfig.setTradeType(tradeType);
// weixin-pay-java 无法设置内容,只允许读取文件,所以这里要创建临时文件来解决
if (Base64.isBase64(config.getKeyContent())) {
if (Objects.equals(config.getApiVersion(), API_VERSION_V2)) {
payConfig.setKeyPath(FileUtils.createTempFile(Base64.decode(config.getKeyContent())).getPath());
}
if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) {
} else if (Objects.equals(config.getApiVersion(), API_VERSION_V3)) {
payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
}
if (StrUtil.isNotEmpty(config.getPrivateCertContent())) {
payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath());
}
// payConfig.setCertSerialNo();
// 创建 client 客户端
client = new WxPayServiceImpl();
@ -86,7 +82,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
switch (config.getApiVersion()) {
case API_VERSION_V2:
return doUnifiedOrderV2(reqDTO);
case WxPayClientConfig.API_VERSION_V3:
case API_VERSION_V3:
return doUnifiedOrderV3(reqDTO);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
@ -157,7 +153,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
switch (config.getApiVersion()) {
case API_VERSION_V2:
return doParseOrderNotifyV2(body);
case WxPayClientConfig.API_VERSION_V3:
case API_VERSION_V3:
return doParseOrderNotifyV3(body);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
@ -192,7 +188,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
switch (config.getApiVersion()) {
case API_VERSION_V2:
return doGetOrderV2(outTradeNo);
case WxPayClientConfig.API_VERSION_V3:
case API_VERSION_V3:
return doGetOrderV3(outTradeNo);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
@ -261,7 +257,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
switch (config.getApiVersion()) {
case API_VERSION_V2:
return doUnifiedRefundV2(reqDTO);
case WxPayClientConfig.API_VERSION_V3:
case API_VERSION_V3:
return doUnifiedRefundV3(reqDTO);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
@ -321,7 +317,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
switch (config.getApiVersion()) {
case API_VERSION_V2:
return doParseRefundNotifyV2(body);
case WxPayClientConfig.API_VERSION_V3:
case API_VERSION_V3:
return parseRefundNotifyV3(body);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
@ -358,7 +354,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
switch (config.getApiVersion()) {
case API_VERSION_V2:
return doGetRefundV2(outTradeNo, outRefundNo);
case WxPayClientConfig.API_VERSION_V3:
case API_VERSION_V3:
return doGetRefundV3(outTradeNo, outRefundNo);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
@ -431,7 +427,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
@Override
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
throw new UnsupportedOperationException("待实现");
throw new UnsupportedOperationException("待实现");
}
@Override

View File

@ -34,7 +34,8 @@ public class WxNativePayClient extends AbstractWxPayClient {
@Override
protected PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
// 构建 WxPayUnifiedOrderRequest 对象
WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO);
WxPayUnifiedOrderRequest request = buildPayUnifiedOrderRequestV2(reqDTO)
.setProductId(reqDTO.getOutTradeNo()); // V2 必须传递 productId无需在微信配置。该参数在 V3 简化,无需传递!
// 执行请求
WxPayNativeOrderResult response = client.createOrder(request);

View File

@ -1,15 +1,11 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.weixin;
import cn.hutool.core.io.IoUtil;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import jakarta.validation.Validator;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import javax.validation.Validator;
import javax.validation.constraints.NotBlank;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/**
* PayClientConfig
* {@link com.github.binarywang.wxpay.config.WxPayConfig}
@ -71,16 +67,19 @@ public class WxPayClientConfig implements PayClientConfig {
*/
@NotBlank(message = "apiclient_key 不能为空", groups = V3.class)
private String privateKeyContent;
/**
* apiclient_cert.pem
*/
@NotBlank(message = "apiclient_cert 不能为空", groups = V3.class)
private String privateCertContent;
/**
* apiV3
*/
@NotBlank(message = "apiV3 密钥值不能为空", groups = V3.class)
private String apiV3Key;
/**
*
*/
@NotBlank(message = "证书序列号不能为空", groups = V3.class)
private String certSerialNo;
@Deprecated // TODO 芋艿V2.3.0 进行移除
private String privateCertContent;
/**
* v2
@ -100,11 +99,4 @@ public class WxPayClientConfig implements PayClientConfig {
API_VERSION_V2.equals(this.getApiVersion()) ? V2.class : V3.class);
}
public static void main(String[] args) throws FileNotFoundException {
String path = "/Users/yunai/Downloads/wx_pay/apiclient_cert.p12";
/// String path = "/Users/yunai/Downloads/wx_pay/apiclient_key.pem";
/// String path = "/Users/yunai/Downloads/wx_pay/apiclient_cert.pem";
System.out.println(IoUtil.readUtf8(new FileInputStream(path)));
}
}