pay 缓存,使用 guava 替代 job 扫描,目的:提升启动速度,加快缓存失效
							parent
							
								
									0162933ce7
								
							
						
					
					
						commit
						3af88326f9
					
				|  | @ -0,0 +1,50 @@ | |||
| package cn.iocoder.yudao.framework.common.util.number; | ||||
| 
 | ||||
| import cn.hutool.core.util.NumberUtil; | ||||
| 
 | ||||
| import java.math.BigDecimal; | ||||
| import java.math.RoundingMode; | ||||
| 
 | ||||
| /** | ||||
|  * 金额工具类 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| public class MoneyUtils { | ||||
| 
 | ||||
|     /** | ||||
|      * 计算百分比金额,四舍五入 | ||||
|      * | ||||
|      * @param price 金额 | ||||
|      * @param rate 百分比,例如说 56.77% 则传入 56.77 | ||||
|      * @return 百分比金额 | ||||
|      */ | ||||
|     public static Integer calculateRatePrice(Integer price, Double rate) { | ||||
|         return calculateRatePrice(price, rate, 0, RoundingMode.HALF_UP).intValue(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 计算百分比金额,向下传入 | ||||
|      * | ||||
|      * @param price 金额 | ||||
| 	 * @param rate 百分比,例如说 56.77% 则传入 56.77 | ||||
|      * @return 百分比金额 | ||||
|      */ | ||||
|     public static Integer calculateRatePriceFloor(Integer price, Double rate) { | ||||
|         return calculateRatePrice(price, rate, 0, RoundingMode.FLOOR).intValue(); | ||||
|     } | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 计算百分比金额 | ||||
| 	 * | ||||
| 	 * @param price 金额 | ||||
| 	 * @param rate 百分比,例如说 56.77% 则传入 56.77 | ||||
| 	 * @param scale 保留小数位数 | ||||
| 	 * @param roundingMode 舍入模式 | ||||
| 	 */ | ||||
| 	public static BigDecimal calculateRatePrice(Number price, Number rate, int scale, RoundingMode roundingMode) { | ||||
| 		return NumberUtil.toBigDecimal(price).multiply(NumberUtil.toBigDecimal(rate)) // 乘以
 | ||||
| 				.divide(BigDecimal.valueOf(100), scale, roundingMode); // 除以 100
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClient; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; | ||||
| import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO; | ||||
|  | @ -16,6 +15,7 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; | |||
| import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO; | ||||
| import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO; | ||||
| import cn.iocoder.yudao.module.pay.service.app.PayAppService; | ||||
| import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; | ||||
| import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; | ||||
| import cn.iocoder.yudao.module.pay.service.order.PayOrderService; | ||||
| import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; | ||||
|  | @ -53,9 +53,8 @@ public class PayNotifyController { | |||
|     private PayNotifyService notifyService; | ||||
|     @Resource | ||||
|     private PayAppService appService; | ||||
| 
 | ||||
|     @Resource | ||||
|     private PayClientFactory payClientFactory; | ||||
|     private PayChannelService channelService; | ||||
| 
 | ||||
|     @PostMapping(value = "/order/{channelId}") | ||||
|     @Operation(summary = "支付渠道的统一【支付】回调") | ||||
|  | @ -66,7 +65,7 @@ public class PayNotifyController { | |||
|                               @RequestBody(required = false) String body) { | ||||
|         log.info("[notifyOrder][channelId({}) 回调数据({}/{})]", channelId, params, body); | ||||
|         // 1. 校验支付渠道是否存在
 | ||||
|         PayClient payClient = payClientFactory.getPayClient(channelId); | ||||
|         PayClient payClient = channelService.getPayClient(channelId); | ||||
|         if (payClient == null) { | ||||
|             log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId); | ||||
|             throw exception(CHANNEL_NOT_FOUND); | ||||
|  | @ -87,7 +86,7 @@ public class PayNotifyController { | |||
|                                @RequestBody(required = false) String body) { | ||||
|         log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body); | ||||
|         // 1. 校验支付渠道是否存在
 | ||||
|         PayClient payClient = payClientFactory.getPayClient(channelId); | ||||
|         PayClient payClient = channelService.getPayClient(channelId); | ||||
|         if (payClient == null) { | ||||
|             log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId); | ||||
|             throw exception(CHANNEL_NOT_FOUND); | ||||
|  |  | |||
|  | @ -3,11 +3,8 @@ package cn.iocoder.yudao.module.pay.dal.mysql.channel; | |||
| import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | ||||
| import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import org.apache.ibatis.annotations.Mapper; | ||||
| import org.apache.ibatis.annotations.Select; | ||||
| 
 | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| 
 | ||||
|  | @ -28,7 +25,4 @@ public interface PayChannelMapper extends BaseMapperX<PayChannelDO> { | |||
|                 .eq(PayChannelDO::getStatus, status)); | ||||
|     } | ||||
| 
 | ||||
|     @Select("SELECT COUNT(*) FROM pay_channel WHERE update_time > #{maxUpdateTime}") | ||||
|     Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime); | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -37,6 +37,10 @@ public interface PayRefundMapper extends BaseMapperX<PayRefundDO> { | |||
|                 .eq(PayRefundDO::getNo, no)); | ||||
|     } | ||||
| 
 | ||||
|     default PayRefundDO selectByNo(String no) { | ||||
|         return selectOne(PayRefundDO::getNo, no); | ||||
|     } | ||||
| 
 | ||||
|     default int updateByIdAndStatus(Long id, Integer status, PayRefundDO update) { | ||||
|         return update(update, new LambdaQueryWrapper<PayRefundDO>() | ||||
|                 .eq(PayRefundDO::getId, id).eq(PayRefundDO::getStatus, status)); | ||||
|  | @ -71,5 +75,4 @@ public interface PayRefundMapper extends BaseMapperX<PayRefundDO> { | |||
|     default List<PayRefundDO> selectListByStatus(Integer status) { | ||||
|         return selectList(PayRefundDO::getStatus, status); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| package cn.iocoder.yudao.module.pay.service.channel; | ||||
| 
 | ||||
| import cn.iocoder.yudao.framework.common.exception.ServiceException; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClient; | ||||
| import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO; | ||||
| import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO; | ||||
| import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; | ||||
|  | @ -92,4 +93,12 @@ public interface PayChannelService { | |||
|      */ | ||||
|     List<PayChannelDO> getEnableChannelList(Long appId); | ||||
| 
 | ||||
|     /** | ||||
|      * 获得指定编号的支付客户端 | ||||
|      * | ||||
|      * @param id 编号 | ||||
|      * @return 支付客户端 | ||||
|      */ | ||||
|     PayClient getPayClient(Long id); | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,37 +1,33 @@ | |||
| package cn.iocoder.yudao.module.pay.service.channel; | ||||
| 
 | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.lang.Assert; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||
| import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClient; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; | ||||
| import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; | ||||
| import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | ||||
| import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO; | ||||
| import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO; | ||||
| 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 com.google.common.cache.CacheLoader; | ||||
| import com.google.common.cache.LoadingCache; | ||||
| import lombok.Getter; | ||||
| import lombok.Setter; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.scheduling.annotation.Scheduled; | ||||
| 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.LocalDateTime; | ||||
| import java.time.Duration; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| 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.*; | ||||
| 
 | ||||
| /** | ||||
|  | @ -44,68 +40,34 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; | |||
| @Validated | ||||
| public class PayChannelServiceImpl implements PayChannelService { | ||||
| 
 | ||||
|     @Getter // 为了方便测试,这里提供 getter 方法
 | ||||
|     @Setter | ||||
|     private volatile List<PayChannelDO> channelCache; | ||||
|     /** | ||||
|      * {@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; | ||||
| 
 | ||||
|     @Resource | ||||
|     private PayChannelMapper channelMapper; | ||||
|     private PayChannelMapper payChannelMapper; | ||||
| 
 | ||||
|     @Resource | ||||
|     private Validator validator; | ||||
| 
 | ||||
|     /** | ||||
|      * 初始化 {@link #payClientFactory} 缓存 | ||||
|      */ | ||||
|     @PostConstruct | ||||
|     public void initLocalCache() { | ||||
|         // 注意:忽略自动多租户,因为要全局初始化缓存
 | ||||
|         TenantUtils.executeIgnore(() -> { | ||||
|             // 第一步:查询数据
 | ||||
|             List<PayChannelDO> channels = Collections.emptyList(); | ||||
|             try { | ||||
|                 channels = channelMapper.selectList(); | ||||
|             } catch (Throwable ex) { | ||||
|                 if (!ex.getMessage().contains("doesn't exist")) { | ||||
|                     throw ex; | ||||
|                 } | ||||
|                 log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]"); | ||||
|             } | ||||
|             log.info("[initLocalCache][缓存支付渠道,数量为:{}]", channels.size()); | ||||
| 
 | ||||
|             // 第二步:构建缓存:创建或更新支付 Client
 | ||||
|             channels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(), | ||||
|                     payChannel.getCode(), payChannel.getConfig())); | ||||
|             this.channelCache = channels; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 通过定时任务轮询,刷新缓存 | ||||
|      * | ||||
|      * 目的:多节点部署时,通过轮询”通知“所有节点,进行刷新 | ||||
|      */ | ||||
|     @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS) | ||||
|     public void refreshLocalCache() { | ||||
|         // 注意:忽略自动多租户,因为要全局初始化缓存
 | ||||
|         TenantUtils.executeIgnore(() -> { | ||||
|             // 情况一:如果缓存里没有数据,则直接刷新缓存
 | ||||
|             if (CollUtil.isEmpty(channelCache)) { | ||||
|                 initLocalCache(); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
 | ||||
|             LocalDateTime maxTime = CollectionUtils.getMaxValue(channelCache, PayChannelDO::getUpdateTime); | ||||
|             if (channelMapper.selectCountByUpdateTimeGt(maxTime) > 0) { | ||||
|                 initLocalCache(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Long createChannel(PayChannelCreateReqVO reqVO) { | ||||
|         // 断言是否有重复的
 | ||||
|  | @ -117,10 +79,7 @@ public class PayChannelServiceImpl implements PayChannelService { | |||
|         // 新增渠道
 | ||||
|         PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO) | ||||
|                 .setConfig(parseConfig(reqVO.getCode(), reqVO.getConfig())); | ||||
|         channelMapper.insert(channel); | ||||
| 
 | ||||
|         // 刷新缓存
 | ||||
|         initLocalCache(); | ||||
|         payChannelMapper.insert(channel); | ||||
|         return channel.getId(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -132,10 +91,10 @@ public class PayChannelServiceImpl implements PayChannelService { | |||
|         // 更新
 | ||||
|         PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO) | ||||
|                 .setConfig(parseConfig(dbChannel.getCode(), updateReqVO.getConfig())); | ||||
|         channelMapper.updateById(channel); | ||||
|         payChannelMapper.updateById(channel); | ||||
| 
 | ||||
|         // 刷新缓存
 | ||||
|         initLocalCache(); | ||||
|         // 清空缓存
 | ||||
|         clearCache(channel.getId()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -165,14 +124,23 @@ public class PayChannelServiceImpl implements PayChannelService { | |||
|         validateChannelExists(id); | ||||
| 
 | ||||
|         // 删除
 | ||||
|         channelMapper.deleteById(id); | ||||
|         payChannelMapper.deleteById(id); | ||||
| 
 | ||||
|         // 刷新缓存
 | ||||
|         initLocalCache(); | ||||
|         // 清空缓存
 | ||||
|         clearCache(id); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 删除缓存 | ||||
|      * | ||||
|      * @param id 渠道编号 | ||||
|      */ | ||||
|     private void clearCache(Long id) { | ||||
|         clientCache.invalidate(id); | ||||
|     } | ||||
| 
 | ||||
|     private PayChannelDO validateChannelExists(Long id) { | ||||
|         PayChannelDO channel = channelMapper.selectById(id); | ||||
|         PayChannelDO channel = payChannelMapper.selectById(id); | ||||
|         if (channel == null) { | ||||
|             throw exception(CHANNEL_NOT_FOUND); | ||||
|         } | ||||
|  | @ -181,29 +149,29 @@ public class PayChannelServiceImpl implements PayChannelService { | |||
| 
 | ||||
|     @Override | ||||
|     public PayChannelDO getChannel(Long id) { | ||||
|         return channelMapper.selectById(id); | ||||
|         return payChannelMapper.selectById(id); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public List<PayChannelDO> getChannelListByAppIds(Collection<Long> appIds) { | ||||
|         return channelMapper.selectListByAppIds(appIds); | ||||
|         return payChannelMapper.selectListByAppIds(appIds); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public PayChannelDO getChannelByAppIdAndCode(Long appId, String code) { | ||||
|         return channelMapper.selectByAppIdAndCode(appId, code); | ||||
|         return payChannelMapper.selectByAppIdAndCode(appId, code); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public PayChannelDO validPayChannel(Long id) { | ||||
|         PayChannelDO channel = channelMapper.selectById(id); | ||||
|         PayChannelDO channel = payChannelMapper.selectById(id); | ||||
|         validPayChannel(channel); | ||||
|         return channel; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public PayChannelDO validPayChannel(Long appId, String code) { | ||||
|         PayChannelDO channel = channelMapper.selectByAppIdAndCode(appId, code); | ||||
|         PayChannelDO channel = payChannelMapper.selectByAppIdAndCode(appId, code); | ||||
|         validPayChannel(channel); | ||||
|         return channel; | ||||
|     } | ||||
|  | @ -219,7 +187,12 @@ public class PayChannelServiceImpl implements PayChannelService { | |||
| 
 | ||||
|     @Override | ||||
|     public List<PayChannelDO> getEnableChannelList(Long appId) { | ||||
|         return channelMapper.selectListByAppId(appId, CommonStatusEnum.ENABLE.getStatus()); | ||||
|         return payChannelMapper.selectListByAppId(appId, CommonStatusEnum.ENABLE.getStatus()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public PayClient getPayClient(Long id) { | ||||
|         return clientCache.getUnchecked(id); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ public interface PayOrderService { | |||
|     /** | ||||
|      * 获得支付订单 | ||||
|      * | ||||
|      * @param appId 应用编号 | ||||
|      * @param appId           应用编号 | ||||
|      * @param merchantOrderId 商户订单编号 | ||||
|      * @return 支付订单 | ||||
|      */ | ||||
|  | @ -75,7 +75,7 @@ public interface PayOrderService { | |||
|      * 提交支付 | ||||
|      * 此时,会发起支付渠道的调用 | ||||
|      * | ||||
|      * @param reqVO 提交请求 | ||||
|      * @param reqVO  提交请求 | ||||
|      * @param userIp 提交 IP | ||||
|      * @return 提交结果 | ||||
|      */ | ||||
|  | @ -93,11 +93,19 @@ public interface PayOrderService { | |||
|     /** | ||||
|      * 更新支付订单的退款金额 | ||||
|      * | ||||
|      * @param id 编号 | ||||
|      * @param id              编号 | ||||
|      * @param incrRefundPrice 增加的退款金额 | ||||
|      */ | ||||
|     void updateOrderRefundPrice(Long id, Integer incrRefundPrice); | ||||
| 
 | ||||
|     /** | ||||
|      * 更新支付订单价格 | ||||
|      * | ||||
|      * @param payOrderId 支付单编号 | ||||
|      * @param payPrice   支付单价格 | ||||
|      */ | ||||
|     void updatePayOrderPriceById(Long payOrderId, Integer payPrice); | ||||
| 
 | ||||
|     /** | ||||
|      * 获得支付订单 | ||||
|      * | ||||
|  | @ -106,6 +114,14 @@ public interface PayOrderService { | |||
|      */ | ||||
|     PayOrderExtensionDO getOrderExtension(Long id); | ||||
| 
 | ||||
|     /** | ||||
|      * 获得支付订单 | ||||
|      * | ||||
|      * @param no 支付订单 no | ||||
|      * @return 支付订单 | ||||
|      */ | ||||
|     PayOrderExtensionDO getOrderExtensionByNo(String no); | ||||
| 
 | ||||
|     /** | ||||
|      * 同步订单的支付状态 | ||||
|      * | ||||
|  |  | |||
|  | @ -6,8 +6,8 @@ import cn.hutool.core.util.StrUtil; | |||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClient; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; | ||||
| import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; | ||||
|  | @ -31,7 +31,6 @@ import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; | |||
| import cn.iocoder.yudao.module.pay.service.app.PayAppService; | ||||
| import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; | ||||
| import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; | ||||
| import cn.iocoder.yudao.module.pay.util.MoneyUtils; | ||||
| import com.google.common.annotations.VisibleForTesting; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.stereotype.Service; | ||||
|  | @ -60,9 +59,6 @@ public class PayOrderServiceImpl implements PayOrderService { | |||
|     @Resource | ||||
|     private PayProperties payProperties; | ||||
| 
 | ||||
|     @Resource | ||||
|     private PayClientFactory payClientFactory; | ||||
| 
 | ||||
|     @Resource | ||||
|     private PayOrderMapper orderMapper; | ||||
|     @Resource | ||||
|  | @ -134,7 +130,7 @@ public class PayOrderServiceImpl implements PayOrderService { | |||
|         PayOrderDO order = validateOrderCanSubmit(reqVO.getId()); | ||||
|         // 1.32 校验支付渠道是否有效
 | ||||
|         PayChannelDO channel = validateChannelCanSubmit(order.getAppId(), reqVO.getChannelCode()); | ||||
|         PayClient client = payClientFactory.getPayClient(channel.getId()); | ||||
|         PayClient client = channelService.getPayClient(channel.getId()); | ||||
| 
 | ||||
|         // 2. 插入 PayOrderExtensionDO
 | ||||
|         String no = noRedisDAO.generate(payProperties.getOrderNoPrefix()); | ||||
|  | @ -205,7 +201,7 @@ public class PayOrderServiceImpl implements PayOrderService { | |||
|                 throw exception(ORDER_EXTENSION_IS_PAID); | ||||
|             } | ||||
|             // 情况二:调用三方接口,查询支付单状态,是不是已支付
 | ||||
|             PayClient payClient = payClientFactory.getPayClient(orderExtension.getChannelId()); | ||||
|             PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); | ||||
|             if (payClient == null) { | ||||
|                 log.error("[validateOrderCanSubmit][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); | ||||
|                 return; | ||||
|  | @ -224,7 +220,7 @@ public class PayOrderServiceImpl implements PayOrderService { | |||
|         appService.validPayApp(appId); | ||||
|         // 校验支付渠道是否有效
 | ||||
|         PayChannelDO channel = channelService.validPayChannel(appId, channelCode); | ||||
|         PayClient client = payClientFactory.getPayClient(channel.getId()); | ||||
|         PayClient client = channelService.getPayClient(channel.getId()); | ||||
|         if (client == null) { | ||||
|             log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); | ||||
|             throw exception(CHANNEL_NOT_FOUND); | ||||
|  | @ -324,7 +320,7 @@ public class PayOrderServiceImpl implements PayOrderService { | |||
|      * @return 是否之前已经成功回调 | ||||
|      */ | ||||
|     private Boolean updateOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension, | ||||
|                                                          PayOrderRespDTO notify) { | ||||
|                                        PayOrderRespDTO notify) { | ||||
|         // 1. 判断 PayOrderDO 是否处于待支付
 | ||||
|         PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId()); | ||||
|         if (order == null) { | ||||
|  | @ -345,7 +341,8 @@ public class PayOrderServiceImpl implements PayOrderService { | |||
|                         .channelId(channel.getId()).channelCode(channel.getCode()) | ||||
|                         .successTime(notify.getSuccessTime()).extensionId(orderExtension.getId()).no(orderExtension.getNo()) | ||||
|                         .channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId()) | ||||
|                         .channelFeeRate(channel.getFeeRate()).channelFeePrice(MoneyUtils.calculateRatePrice(order.getPrice(), channel.getFeeRate())) | ||||
|                         .channelFeeRate(channel.getFeeRate()) | ||||
|                         .channelFeePrice(MoneyUtils.calculateRatePrice(order.getPrice(), channel.getFeeRate())) | ||||
|                         .build()); | ||||
|         if (updateCounts == 0) { // 校验状态,必须是待支付
 | ||||
|             throw exception(ORDER_STATUS_IS_NOT_WAITING); | ||||
|  | @ -410,11 +407,28 @@ public class PayOrderServiceImpl implements PayOrderService { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updatePayOrderPriceById(Long payOrderId, Integer payPrice) { | ||||
|         // TODO @puhui999:不能直接这样修改哈;应该只有未支付状态的订单才可以改;另外,如果价格如果没变,可以直接 return 哈;
 | ||||
|         PayOrderDO order = orderMapper.selectById(payOrderId); | ||||
|         if (order == null) { | ||||
|             throw exception(ORDER_NOT_FOUND); | ||||
|         } | ||||
| 
 | ||||
|         order.setPrice(payPrice); | ||||
|         orderMapper.updateById(order); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public PayOrderExtensionDO getOrderExtension(Long id) { | ||||
|         return orderExtensionMapper.selectById(id); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public PayOrderExtensionDO getOrderExtensionByNo(String no) { | ||||
|         return orderExtensionMapper.selectByNo(no); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int syncOrder(LocalDateTime minCreateTime) { | ||||
|         // 1. 查询指定创建时间内的待支付订单
 | ||||
|  | @ -440,7 +454,7 @@ public class PayOrderServiceImpl implements PayOrderService { | |||
|     private boolean syncOrder(PayOrderExtensionDO orderExtension) { | ||||
|         try { | ||||
|             // 1.1 查询支付订单信息
 | ||||
|             PayClient payClient = payClientFactory.getPayClient(orderExtension.getChannelId()); | ||||
|             PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); | ||||
|             if (payClient == null) { | ||||
|                 log.error("[syncOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); | ||||
|                 return false; | ||||
|  | @ -495,7 +509,7 @@ public class PayOrderServiceImpl implements PayOrderService { | |||
|                     return false; | ||||
|                 } | ||||
|                 // 情况二:调用三方接口,查询支付单状态,是不是已支付/已退款
 | ||||
|                 PayClient payClient = payClientFactory.getPayClient(orderExtension.getChannelId()); | ||||
|                 PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); | ||||
|                 if (payClient == null) { | ||||
|                     log.error("[expireOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); | ||||
|                     return false; | ||||
|  |  | |||
|  | @ -24,6 +24,14 @@ public interface PayRefundService { | |||
|      */ | ||||
|     PayRefundDO getRefund(Long id); | ||||
| 
 | ||||
|     /** | ||||
|      * 获得退款订单 | ||||
|      * | ||||
|      * @param no 外部退款单号 | ||||
|      * @return 退款订单 | ||||
|      */ | ||||
|     PayRefundDO getRefundByNo(String no); | ||||
| 
 | ||||
|     /** | ||||
|      * 获得指定应用的退款数量 | ||||
|      * | ||||
|  |  | |||
|  | @ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil; | |||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClient; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; | ||||
| import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; | ||||
|  | @ -52,9 +51,6 @@ public class PayRefundServiceImpl implements PayRefundService { | |||
|     @Resource | ||||
|     private PayProperties payProperties; | ||||
| 
 | ||||
|     @Resource | ||||
|     private PayClientFactory payClientFactory; | ||||
| 
 | ||||
|     @Resource | ||||
|     private PayRefundMapper refundMapper; | ||||
|     @Resource | ||||
|  | @ -74,6 +70,11 @@ public class PayRefundServiceImpl implements PayRefundService { | |||
|         return refundMapper.selectById(id); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public PayRefundDO getRefundByNo(String no) { | ||||
|         return refundMapper.selectByNo(no); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Long getRefundCountByAppId(Long appId) { | ||||
|         return refundMapper.selectCountByAppId(appId); | ||||
|  | @ -97,7 +98,7 @@ public class PayRefundServiceImpl implements PayRefundService { | |||
|         PayOrderDO order = validatePayOrderCanRefund(reqDTO); | ||||
|         // 1.3 校验支付渠道是否有效
 | ||||
|         PayChannelDO channel = channelService.validPayChannel(order.getChannelId()); | ||||
|         PayClient client = payClientFactory.getPayClient(channel.getId()); | ||||
|         PayClient client = channelService.getPayClient(channel.getId()); | ||||
|         if (client == null) { | ||||
|             log.error("[refund][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); | ||||
|             throw exception(CHANNEL_NOT_FOUND); | ||||
|  | @ -300,7 +301,7 @@ public class PayRefundServiceImpl implements PayRefundService { | |||
|     private boolean syncRefund(PayRefundDO refund) { | ||||
|         try { | ||||
|             // 1.1 查询退款订单信息
 | ||||
|             PayClient payClient = payClientFactory.getPayClient(refund.getChannelId()); | ||||
|             PayClient payClient = channelService.getPayClient(refund.getChannelId()); | ||||
|             if (payClient == null) { | ||||
|                 log.error("[syncRefund][渠道编号({}) 找不到对应的支付客户端]", refund.getChannelId()); | ||||
|                 return false; | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.service.channel; | |||
| 
 | ||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||
| import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClient; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig; | ||||
|  | @ -12,30 +13,26 @@ import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateR | |||
| import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; | ||||
| import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper; | ||||
| import com.alibaba.fastjson.JSON; | ||||
| import org.junit.jupiter.api.BeforeEach; | ||||
| 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 java.time.Duration; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime; | ||||
| import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; | ||||
| import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; | ||||
| import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; | ||||
| import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; | ||||
| import static org.junit.jupiter.api.Assertions.*; | ||||
| import static org.mockito.ArgumentMatchers.eq; | ||||
| import static org.mockito.Mockito.*; | ||||
| 
 | ||||
| @Import({PayChannelServiceImpl.class}) | ||||
| public class PayChannelServiceTest extends BaseDbUnitTest { | ||||
| 
 | ||||
|     private static final String ALIPAY_SERVER_URL = "https://openapi.alipay.com/gateway.do"; | ||||
| 
 | ||||
|     @Resource | ||||
|     private PayChannelServiceImpl channelService; | ||||
| 
 | ||||
|  | @ -47,45 +44,6 @@ public class PayChannelServiceTest extends BaseDbUnitTest { | |||
|     @MockBean | ||||
|     private Validator validator; | ||||
| 
 | ||||
|     @BeforeEach | ||||
|     public void setUp() { | ||||
|         channelService.setChannelCache(null); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testInitLocalCache() { | ||||
|         // mock 数据
 | ||||
|         PayChannelDO dbChannel = randomPojo(PayChannelDO.class, | ||||
|                 o -> o.setConfig(randomWxPayClientConfig())); | ||||
|         channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
 | ||||
| 
 | ||||
|         // 调用
 | ||||
|         channelService.initLocalCache(); | ||||
|         // 校验缓存
 | ||||
|         assertEquals(1, channelService.getChannelCache().size()); | ||||
|         assertEquals(dbChannel, channelService.getChannelCache().get(0)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testRefreshLocalCache() { | ||||
|         // mock 数据 01
 | ||||
|         PayChannelDO dbChannel = randomPojo(PayChannelDO.class, | ||||
|                 o -> o.setConfig(randomWxPayClientConfig()).setUpdateTime(addTime(Duration.ofMinutes(-2)))); | ||||
|         channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
 | ||||
|         channelService.initLocalCache(); | ||||
|         // mock 数据 02
 | ||||
|         PayChannelDO dbChannel02 = randomPojo(PayChannelDO.class, | ||||
|                 o -> o.setConfig(randomWxPayClientConfig())); | ||||
|         channelMapper.insert(dbChannel02);// @Sql: 先插入出一条存在的数据
 | ||||
| 
 | ||||
|         // 调用
 | ||||
|         channelService.refreshLocalCache(); | ||||
|         // 校验缓存
 | ||||
|         assertEquals(2, channelService.getChannelCache().size()); | ||||
|         assertEquals(dbChannel, channelService.getChannelCache().get(0)); | ||||
|         assertEquals(dbChannel02, channelService.getChannelCache().get(1)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCreateChannel_success() { | ||||
|         // 准备参数
 | ||||
|  | @ -103,8 +61,7 @@ public class PayChannelServiceTest extends BaseDbUnitTest { | |||
|         assertPojoEquals(reqVO, channel, "config"); | ||||
|         assertPojoEquals(config, channel.getConfig()); | ||||
|         // 校验缓存
 | ||||
|         assertEquals(1, channelService.getChannelCache().size()); | ||||
|         assertEquals(channel, channelService.getChannelCache().get(0)); | ||||
|         assertNull(channelService.getClientCache().getIfPresent(channelId)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|  | @ -146,8 +103,7 @@ public class PayChannelServiceTest extends BaseDbUnitTest { | |||
|         assertPojoEquals(reqVO, channel, "config"); | ||||
|         assertPojoEquals(config, channel.getConfig()); | ||||
|         // 校验缓存
 | ||||
|         assertEquals(1, channelService.getChannelCache().size()); | ||||
|         assertEquals(channel, channelService.getChannelCache().get(0)); | ||||
|         assertNull(channelService.getClientCache().getIfPresent(channel.getId())); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|  | @ -179,7 +135,7 @@ public class PayChannelServiceTest extends BaseDbUnitTest { | |||
|         // 校验数据不存在了
 | ||||
|         assertNull(channelMapper.selectById(id)); | ||||
|         // 校验缓存
 | ||||
|         assertEquals(0, channelService.getChannelCache().size()); | ||||
|         assertNull(channelService.getClientCache().getIfPresent(id)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|  | @ -344,6 +300,28 @@ public class PayChannelServiceTest extends BaseDbUnitTest { | |||
|         assertPojoEquals(channel, dbChannel03); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetPayClient() { | ||||
|         // mock 数据
 | ||||
|         PayChannelDO channel = randomPojo(PayChannelDO.class, o -> { | ||||
|             o.setCode(PayChannelEnum.ALIPAY_APP.getCode()); | ||||
|             o.setConfig(randomAlipayPayClientConfig()); | ||||
|         }); | ||||
|         channelMapper.insert(channel); | ||||
|         // mock 参数
 | ||||
|         Long id = channel.getId(); | ||||
|         // mock 方法
 | ||||
|         PayClient mockClient = mock(PayClient.class); | ||||
|         when(payClientFactory.getPayClient(eq(id))).thenReturn(mockClient); | ||||
| 
 | ||||
|         // 调用
 | ||||
|         PayClient client = channelService.getPayClient(id); | ||||
|         // 断言
 | ||||
|         assertSame(client, mockClient); | ||||
|         verify(payClientFactory).createOrUpdatePayClient(eq(id), eq(channel.getCode()), | ||||
|                 eq(channel.getConfig())); | ||||
|     } | ||||
| 
 | ||||
|     public WxPayClientConfig randomWxPayClientConfig() { | ||||
|         return new WxPayClientConfig() | ||||
|                 .setAppId(randomString()) | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ import lombok.Getter; | |||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.scheduling.annotation.Scheduled; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
| 
 | ||||
| import javax.annotation.PostConstruct; | ||||
|  | @ -42,6 +43,11 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_ | |||
| @Validated | ||||
| public class SensitiveWordServiceImpl implements SensitiveWordService { | ||||
| 
 | ||||
|     /** | ||||
|      * 是否开启敏感词功能 | ||||
|      */ | ||||
|     private static final Boolean ENABLED = false; | ||||
| 
 | ||||
|     /** | ||||
|      * 敏感词列表缓存 | ||||
|      */ | ||||
|  | @ -75,6 +81,10 @@ public class SensitiveWordServiceImpl implements SensitiveWordService { | |||
|      */ | ||||
|     @PostConstruct | ||||
|     public void initLocalCache() { | ||||
|         if (!ENABLED) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // 第一步:查询数据
 | ||||
|         List<SensitiveWordDO> sensitiveWords = sensitiveWordMapper.selectList(); | ||||
|         log.info("[initLocalCache][缓存敏感词,数量为:{}]", sensitiveWords.size()); | ||||
|  | @ -216,6 +226,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService { | |||
| 
 | ||||
|     @Override | ||||
|     public List<String> validateText(String text, List<String> tags) { | ||||
|         Assert.isTrue(ENABLED, "敏感词功能未开启,请将 ENABLED 设置为 true"); | ||||
| 
 | ||||
|         // 无标签时,默认所有
 | ||||
|         if (CollUtil.isEmpty(tags)) { | ||||
|             return defaultSensitiveWordTrie.validate(text); | ||||
|         } | ||||
|  | @ -233,6 +246,9 @@ public class SensitiveWordServiceImpl implements SensitiveWordService { | |||
| 
 | ||||
|     @Override | ||||
|     public boolean isTextValid(String text, List<String> tags) { | ||||
|         Assert.isTrue(ENABLED, "敏感词功能未开启,请将 ENABLED 设置为 true"); | ||||
| 
 | ||||
|         // 无标签时,默认所有
 | ||||
|         if (CollUtil.isEmpty(tags)) { | ||||
|             return defaultSensitiveWordTrie.isValid(text); | ||||
|         } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 YunaiV
						YunaiV