Преглед на файлове

by gateway: 支付渠道的代码优化

zhijiantianya@gmail.com преди 1 година
родител
ревизия
47ba5b7b44
променени са 18 файла, в които са добавени 360 реда и са изтрити 418 реда
  1. 3 15
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java
  2. 3 3
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java
  3. 4 4
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPayClientConfig.java
  4. 3 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java
  5. 3 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java
  6. 10 33
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/PayChannelController.java
  7. 7 8
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelBaseVO.java
  8. 5 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java
  9. 0 42
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelPageReqVO.java
  10. 6 2
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelRespVO.java
  11. 1 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java
  12. 7 38
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java
  13. 3 6
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java
  14. 3 18
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java
  15. 89 54
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java
  16. 117 5
      yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java
  17. 96 180
      yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java
  18. 0 9
      yudao-ui-admin/src/api/pay/channel.js

+ 3 - 15
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.framework.pay.core.client;
 
+import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 
 import javax.validation.ConstraintViolation;
@@ -19,24 +20,11 @@ import java.util.Set;
 // 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型
 public interface PayClientConfig {
 
-    /**
-     * 配置验证参数是
-     *
-     * @param validator 校验对象
-     * @return 配置好的验证参数
-     */
-    Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator);
-
-    // TODO @aquan:貌似抽象一个 validation group 就好了!
     /**
      * 参数校验
      *
      * @param validator 校验对象
      */
-    default void validate(Validator validator) {
-        Set<ConstraintViolation<PayClientConfig>> violations = verifyParam(validator);
-        if (!violations.isEmpty()) {
-            throw new ConstraintViolationException(violations);
-        }
-    }
+    void validate(Validator validator);
+
 }

+ 3 - 3
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
 
+import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import lombok.Data;
 
@@ -100,9 +101,8 @@ public class AlipayPayClientConfig implements PayClientConfig {
     }
 
     @Override
-    public Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator) {
-        // TODO 芋艿:参数校验
-        return validator.validate(this,
+    public void validate(Validator validator) {
+        ValidationUtils.validate(validator, this,
                 MODE_PUBLIC_KEY.equals(this.getMode()) ? ModePublicKey.class : ModeCertificate.class);
     }
 

+ 4 - 4
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPayClientConfig.java

@@ -1,15 +1,14 @@
 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 lombok.Data;
 
-import javax.validation.ConstraintViolation;
 import javax.validation.Validator;
 import javax.validation.constraints.NotBlank;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.util.Set;
 
 /**
  * 微信支付的 PayClientConfig 实现类
@@ -100,8 +99,9 @@ public class WxPayClientConfig implements PayClientConfig {
     }
 
     @Override
-    public Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator) {
-        return validator.validate(this, this.getApiVersion().equals(API_VERSION_V2) ? V2.class : V3.class);
+    public void validate(Validator validator) {
+        ValidationUtils.validate(validator, this,
+                API_VERSION_V2.equals(this.getApiVersion()) ? V2.class : V3.class);
     }
 
     public static void main(String[] args) throws FileNotFoundException {

+ 3 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java

@@ -22,9 +22,11 @@ import static org.mockito.Mockito.when;
 
 public class AlipayQrPayClientTest extends BaseMockitoUnitTest {
 
+    private static final String SERVER_URL_SANDBOX = "https://openapi.alipaydev.com/gateway.do";
+
     private final AlipayPayClientConfig config = new AlipayPayClientConfig()
         .setAppId("2021000118634035")
-        .setServerUrl(AlipayPayClientConfig.SERVER_URL_SANDBOX)
+        .setServerUrl(SERVER_URL_SANDBOX)
         .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT)
         // TODO @tina:key 可以随机就好,简洁一点哈。
         .setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHsEV1cDupwJ" +

+ 3 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java

@@ -1,4 +1,6 @@
 package cn.iocoder.yudao.module.pay.controller.admin.app.vo;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
 import org.hibernate.validator.constraints.URL;
@@ -18,6 +20,7 @@ public class PayAppBaseVO {
 
     @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
     @NotNull(message = "开启状态不能为空")
+    @InEnum(CommonStatusEnum.class)
     private Integer status;
 
     @Schema(description = "备注", example = "我是一个测试应用")

+ 10 - 33
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/PayChannelController.java

@@ -1,9 +1,7 @@
 package cn.iocoder.yudao.module.pay.controller.admin.channel;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO;
-import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelPageReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelRespVO;
 import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO;
 import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert;
@@ -11,7 +9,6 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.Parameters;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
@@ -59,39 +56,19 @@ public class PayChannelController {
     }
 
     @GetMapping("/get")
-    @Operation(summary = "获得支付渠道 ")
+    @Operation(summary = "获得支付渠道")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('pay:channel:query')")
-    public CommonResult<PayChannelRespVO> getChannel(@RequestParam("id") Long id) {
-        PayChannelDO channel = channelService.getChannel(id);
-        return success(PayChannelConvert.INSTANCE.convert(channel));
-    }
-
-    @GetMapping("/page")
-    @Operation(summary = "获得支付渠道分页")
-    @PreAuthorize("@ss.hasPermission('pay:channel:query')")
-    public CommonResult<PageResult<PayChannelRespVO>> getChannelPage(@Valid PayChannelPageReqVO pageVO) {
-        PageResult<PayChannelDO> pageResult = channelService.getChannelPage(pageVO);
-        return success(PayChannelConvert.INSTANCE.convertPage(pageResult));
-    }
-
-    // TODO 芋艿:需要 review 下实现
-    @GetMapping("/get-channel")
-    @Operation(summary = "根据条件查询微信支付渠道")
-    @Parameters({
-            @Parameter(name = "appId", description = "应用编号", required = true, example = "1"),
-            @Parameter(name = "code", description = "支付渠道编码", required = true, example = "wx_pub")
-    })
-    @PreAuthorize("@ss.hasPermission('pay:channel:query')")
-    public CommonResult<PayChannelRespVO> getChannel(@RequestParam Long appId, @RequestParam String code) {
-        // 獲取渠道
-        PayChannelDO channel = channelService.getChannelByConditions(appId, code);
-        if (channel == null) {
-            return success(new PayChannelRespVO());
+    public CommonResult<PayChannelRespVO> getChannel(@RequestParam(value = "id", required = false) Long id,
+                                                     @RequestParam(value = "appId", required = false) Long appId,
+                                                     @RequestParam(value = "code", required = false) String code) {
+        PayChannelDO channel = null;
+        if (id != null) {
+            channel = channelService.getChannel(id);
+        } else if (appId != null && code != null) {
+            channel = channelService.getChannelByAppIdAndCode(appId, code);
         }
-        // 拼凑数据
-        PayChannelRespVO respVo = PayChannelConvert.INSTANCE.convert(channel);
-        return success(respVo);
+        return success(PayChannelConvert.INSTANCE.convert(channel));
     }
 
     @GetMapping("/get-enable-code-list")

+ 7 - 8
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelBaseVO.java

@@ -1,4 +1,6 @@
 package cn.iocoder.yudao.module.pay.controller.admin.channel.vo;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
 import javax.validation.constraints.*;
@@ -10,22 +12,19 @@ import javax.validation.constraints.*;
 @Data
 public class PayChannelBaseVO {
 
-    @Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "渠道编码不能为空")
-    private String code;
-
-    @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "开启状态不能为空")
+    @InEnum(CommonStatusEnum.class)
     private Integer status;
 
-    @Schema(description = "备注")
+    @Schema(description = "备注", example = "我是小备注")
     private String remark;
 
-    @Schema(description = "渠道费率,单位:百分比", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "渠道费率,单位:百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     @NotNull(message = "渠道费率,单位:百分比不能为空")
     private Double feeRate;
 
-    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     @NotNull(message = "应用编号不能为空")
     private Long appId;
 

+ 5 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java

@@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode;
 import lombok.ToString;
 
 import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 
 @Schema(description = "管理后台 - 支付渠道 创建 Request VO")
 @Data
@@ -13,6 +14,10 @@ import javax.validation.constraints.NotBlank;
 @ToString(callSuper = true)
 public class PayChannelCreateReqVO extends PayChannelBaseVO {
 
+    @Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "alipay_pc")
+    @NotNull(message = "渠道编码不能为空")
+    private String code;
+
     @Schema(description = "渠道配置的 json 字符串")
     @NotBlank(message = "渠道配置不能为空")
     private String config;

+ 0 - 42
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelPageReqVO.java

@@ -1,42 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.admin.channel.vo;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import java.time.LocalDateTime;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-
-@Schema(description = "管理后台 - 支付渠道 分页 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class PayChannelPageReqVO extends PageParam {
-
-    @Schema(description = "渠道编码")
-    private String code;
-
-    @Schema(description = "开启状态")
-    private Integer status;
-
-    @Schema(description = "备注")
-    private String remark;
-
-    @Schema(description = "渠道费率,单位:百分比")
-    private Double feeRate;
-
-    @Schema(description = "应用编号")
-    private Long appId;
-
-    @Schema(description = "支付渠道配置")
-    private String config;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "创建时间")
-    private LocalDateTime[] createTime;
-
-}

+ 6 - 2
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelRespVO.java

@@ -10,12 +10,16 @@ import java.time.LocalDateTime;
 @ToString(callSuper = true)
 public class PayChannelRespVO extends PayChannelBaseVO {
 
-    @Schema(description = "商户编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "商户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;
 
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private LocalDateTime createTime;
 
+    @Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "alipay_pc")
+    private String code;
+
     @Schema(description = "配置", requiredMode = Schema.RequiredMode.REQUIRED)
     private String config;
+
 }

+ 1 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java

@@ -16,4 +16,5 @@ public class PayChannelUpdateReqVO extends PayChannelBaseVO {
     @Schema(description = "渠道配置的json字符串")
     @NotBlank(message = "渠道配置不能为空")
     private String config;
+
 }

+ 7 - 38
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/channel/PayChannelMapper.java

@@ -1,14 +1,13 @@
 package cn.iocoder.yudao.module.pay.dal.mysql.channel;
 
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
-import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelPageReqVO;
 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;
 
@@ -19,41 +18,8 @@ public interface PayChannelMapper extends BaseMapperX<PayChannelDO> {
         return selectOne(PayChannelDO::getAppId, appId, PayChannelDO::getCode, code);
     }
 
-    default PageResult<PayChannelDO> selectPage(PayChannelPageReqVO reqVO) {
-        return selectPage(reqVO, new QueryWrapperX<PayChannelDO>()
-                .eqIfPresent("code", reqVO.getCode())
-                .eqIfPresent("status", reqVO.getStatus())
-                .eqIfPresent("remark", reqVO.getRemark())
-                .eqIfPresent("fee_rate", reqVO.getFeeRate())
-                .eqIfPresent("app_id", reqVO.getAppId())
-                .betweenIfPresent("create_time", reqVO.getCreateTime())
-                .orderByDesc("id"));
-    }
-
-    /**
-     * 根据条件获取渠道
-     *
-     * @param appId      应用编号
-     * @param code       渠道编码
-     * @return 数量
-     */
-    default PayChannelDO selectOne(Long appId, String code) {
-        return this.selectOne((new QueryWrapper<PayChannelDO>().lambda()
-                .eq(PayChannelDO::getAppId, appId)
-                .eq(PayChannelDO::getCode, code)
-        ));
-    }
-
-    // TODO @aquan:select 命名
-    /**
-     * 根据支付应用ID集合获得支付渠道列表
-     *
-     * @param appIds 应用编号集合
-     * @return 支付渠道列表
-     */
-    default List<PayChannelDO> getChannelListByAppIds(Collection<Long> appIds){
-        return this.selectList(new QueryWrapper<PayChannelDO>().lambda()
-                .in(PayChannelDO::getAppId, appIds));
+    default List<PayChannelDO> selectListByAppIds(Collection<Long> appIds){
+        return selectList(PayChannelDO::getAppId, appIds);
     }
 
     default List<PayChannelDO> selectListByAppId(Long appId, Integer status) {
@@ -62,4 +28,7 @@ public interface PayChannelMapper extends BaseMapperX<PayChannelDO> {
                 .eq(PayChannelDO::getStatus, status));
     }
 
+    @Select("SELECT COUNT(*) FROM pay_channel WHERE update_time > #{maxUpdateTime}")
+    Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
+
 }

+ 3 - 6
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java

@@ -68,10 +68,7 @@ public class PayAppServiceImpl implements PayAppService {
         // 校验商户存在
         validateAppExists(id);
         // 更新状态
-        PayAppDO app = new PayAppDO();
-        app.setId(id);
-        app.setStatus(status);
-        appMapper.updateById(app);
+        appMapper.updateById(new PayAppDO().setId(id).setStatus(status));
     }
 
     @Override
@@ -116,11 +113,11 @@ public class PayAppServiceImpl implements PayAppService {
         PayAppDO app = appMapper.selectById(id);
         // 校验是否存在
         if (app == null) {
-            throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_APP_NOT_FOUND);
+            throw exception(ErrorCodeConstants.PAY_APP_NOT_FOUND);
         }
         // 校验是否禁用
         if (CommonStatusEnum.DISABLE.getStatus().equals(app.getStatus())) {
-            throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_APP_IS_DISABLE);
+            throw exception(ErrorCodeConstants.PAY_APP_IS_DISABLE);
         }
         return app;
     }

+ 3 - 18
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelService.java

@@ -1,9 +1,7 @@
 package cn.iocoder.yudao.module.pay.service.channel;
 
 import cn.iocoder.yudao.framework.common.exception.ServiceException;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO;
-import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelPageReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
 
@@ -18,11 +16,6 @@ import java.util.List;
  */
 public interface PayChannelService {
 
-    /**
-     * 初始化支付客户端
-     */
-    void initLocalCache();
-
     /**
      * 创建支付渠道
      *
@@ -54,15 +47,7 @@ public interface PayChannelService {
     PayChannelDO getChannel(Long id);
 
     /**
-     * 获得支付渠道分页
-     *
-     * @param pageReqVO 分页查询
-     * @return 支付渠道分页
-     */
-    PageResult<PayChannelDO> getChannelPage(PayChannelPageReqVO pageReqVO);
-
-    /**
-     * 根据支付应用ID集合获得支付渠道列表
+     * 根据支付应用 ID 集合,获得支付渠道列表
      *
      * @param appIds 应用编号集合
      * @return 支付渠道列表
@@ -72,11 +57,11 @@ public interface PayChannelService {
     /**
      * 根据条件获取渠道
      *
-     * @param appid      应用编号
+     * @param appId      应用编号
      * @param code       渠道编码
      * @return 数量
      */
-    PayChannelDO getChannelByConditions(Long appid, String code);
+    PayChannelDO getChannelByAppIdAndCode(Long appId, String code);
 
     /**
      * 支付渠道的合法性

+ 89 - 54
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java

@@ -1,30 +1,35 @@
 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.hutool.json.JSONUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
+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.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.PayChannelPageReqVO;
 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 cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
+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.util.Collection;
 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.module.pay.enums.ErrorCodeConstants.CHANNEL_EXIST_SAME_CHANNEL_ERROR;
@@ -40,6 +45,10 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_E
 @Validated
 public class PayChannelServiceImpl implements PayChannelService {
 
+    @Getter // 为了方便测试,这里提供 getter 方法
+    @Setter
+    private volatile List<PayChannelDO> channelCache;
+
     @Resource
     private PayClientFactory payClientFactory;
 
@@ -52,7 +61,6 @@ public class PayChannelServiceImpl implements PayChannelService {
     /**
      * 初始化 {@link #payClientFactory} 缓存
      */
-    @Override
     @PostConstruct
     public void initLocalCache() {
         // 注意:忽略自动多租户,因为要全局初始化缓存
@@ -64,49 +72,101 @@ public class PayChannelServiceImpl implements PayChannelService {
             // 第二步:构建缓存:创建或更新支付 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() {
+        // 情况一:如果缓存里没有数据,则直接刷新缓存
+        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) {
         // 断言是否有重复的
-        PayChannelDO channelDO = this.getChannelByConditions(reqVO.getAppId(), reqVO.getCode());
-        if (ObjectUtil.isNotNull(channelDO)) {
+        PayChannelDO dbChannel = getChannelByAppIdAndCode(reqVO.getAppId(), reqVO.getCode());
+        if (dbChannel != null) {
             throw exception(CHANNEL_EXIST_SAME_CHANNEL_ERROR);
         }
 
         // 新增渠道
-        PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO);
-        settingConfigAndCheckParam(channel, reqVO.getConfig());
+        PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO)
+                .setConfig(parseConfig(reqVO.getCode(), reqVO.getConfig()));
         channelMapper.insert(channel);
-        // TODO 芋艿:缺少刷新本地缓存的机制
+
+        // 刷新缓存
+        refreshLocalCache();
         return channel.getId();
     }
 
     @Override
     public void updateChannel(PayChannelUpdateReqVO updateReqVO) {
         // 校验存在
-        this.validateChannelExists(updateReqVO.getId());
+        PayChannelDO dbChannel = validateChannelExists(updateReqVO.getId());
+
         // 更新
-        PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO);
-        settingConfigAndCheckParam(channel, updateReqVO.getConfig());
+        PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO)
+                .setConfig(parseConfig(dbChannel.getCode(), updateReqVO.getConfig()));
         channelMapper.updateById(channel);
-        // TODO 芋艿:缺少刷新本地缓存的机制
+
+        // 刷新缓存
+        refreshLocalCache();
+    }
+
+    /**
+     * 解析并校验配置
+     *
+     * @param code      渠道编码
+     * @param configStr 配置
+     * @return 支付配置
+     */
+    private PayClientConfig parseConfig(String code, String configStr) {
+        // 解析配置
+        Class<? extends PayClientConfig> payClass = PayChannelEnum.getByCode(code).getConfigClass();
+        if (ObjectUtil.isNull(payClass)) {
+            throw exception(CHANNEL_NOT_EXISTS);
+        }
+        PayClientConfig config = JsonUtils.parseObject2(configStr, payClass);
+        Assert.notNull(config);
+
+        // 验证参数
+        config.validate(validator);
+        return config;
     }
 
     @Override
     public void deleteChannel(Long id) {
         // 校验存在
-        this.validateChannelExists(id);
+        validateChannelExists(id);
+
         // 删除
         channelMapper.deleteById(id);
-        // TODO 芋艿:缺少刷新本地缓存的机制
+
+        // 刷新缓存
+        refreshLocalCache();
     }
 
-    private void validateChannelExists(Long id) {
-        if (channelMapper.selectById(id) == null) {
+    private PayChannelDO validateChannelExists(Long id) {
+        PayChannelDO channel = channelMapper.selectById(id);
+        if (channel == null) {
             throw exception(CHANNEL_NOT_EXISTS);
         }
+        return channel;
     }
 
     @Override
@@ -114,45 +174,20 @@ public class PayChannelServiceImpl implements PayChannelService {
         return channelMapper.selectById(id);
     }
 
-    @Override
-    public PageResult<PayChannelDO> getChannelPage(PayChannelPageReqVO pageReqVO) {
-        return channelMapper.selectPage(pageReqVO);
-    }
-
     @Override
     public List<PayChannelDO> getChannelListByAppIds(Collection<Long> appIds) {
-        return channelMapper.getChannelListByAppIds(appIds);
+        return channelMapper.selectListByAppIds(appIds);
     }
 
     @Override
-    public PayChannelDO getChannelByConditions(Long appid, String code) {
-        return this.channelMapper.selectOne(appid, code);
-    }
-
-    /**
-     * 设置渠道配置以及参数校验
-     *
-     * @param channel   渠道
-     * @param configStr 配置
-     */
-    private void settingConfigAndCheckParam(PayChannelDO channel, String configStr) {
-        // 得到这个渠道是微信的还是支付宝的
-        Class<? extends PayClientConfig> payClass = PayChannelEnum.getByCode(channel.getCode()).getConfigClass();
-        if (ObjectUtil.isNull(payClass)) {
-            throw exception(CHANNEL_NOT_EXISTS);
-        }
-        // TODO @芋艿:不要使用 hutool 的 json 工具,用项目的
-        PayClientConfig config = JSONUtil.toBean(configStr, payClass);
-
-        // 验证参数
-        config.validate(validator);
-        channel.setConfig(config);
+    public PayChannelDO getChannelByAppIdAndCode(Long appId, String code) {
+        return channelMapper.selectByAppIdAndCode(appId, code);
     }
 
     @Override
     public PayChannelDO validPayChannel(Long id) {
         PayChannelDO channel = channelMapper.selectById(id);
-        this.validPayChannel(channel);
+        validPayChannel(channel);
         return channel;
     }
 
@@ -163,18 +198,18 @@ public class PayChannelServiceImpl implements PayChannelService {
         return channel;
     }
 
-    @Override
-    public List<PayChannelDO> getEnableChannelList(Long appId) {
-        return channelMapper.selectListByAppId(appId, CommonStatusEnum.ENABLE.getStatus());
-    }
-
     private void validPayChannel(PayChannelDO channel) {
         if (channel == null) {
-            throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_CHANNEL_NOT_FOUND);
+            throw exception(ErrorCodeConstants.PAY_CHANNEL_NOT_FOUND);
         }
         if (CommonStatusEnum.DISABLE.getStatus().equals(channel.getStatus())) {
-            throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_CHANNEL_IS_DISABLE);
+            throw exception(ErrorCodeConstants.PAY_CHANNEL_IS_DISABLE);
         }
     }
 
+    @Override
+    public List<PayChannelDO> getEnableChannelList(Long appId) {
+        return channelMapper.selectListByAppId(appId, CommonStatusEnum.ENABLE.getStatus());
+    }
+
 }

+ 117 - 5
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java

@@ -19,6 +19,8 @@ import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
@@ -26,8 +28,11 @@ import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgn
 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.PAY_APP_NOT_FOUND;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
+import static java.util.Collections.singleton;
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
 
 /**
  * {@link PayAppServiceImpl} 的单元测试
@@ -67,8 +72,7 @@ public class PayAppServiceTest extends BaseDbUnitTest {
     @Test
     public void testUpdateApp_success() {
         // mock 数据
-        PayAppDO dbApp = randomPojo(PayAppDO.class, o ->
-                o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
+        PayAppDO dbApp = randomPojo(PayAppDO.class);
         appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
         // 准备参数
         PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o -> {
@@ -94,10 +98,26 @@ public class PayAppServiceTest extends BaseDbUnitTest {
     }
 
     @Test
-    public void testDeleteApp_success() {
+    public void testUpdateAppStatus() {
         // mock 数据
         PayAppDO dbApp = randomPojo(PayAppDO.class, o ->
-                o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus())));
+                o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
+        appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
+
+        // 准备参数
+        Long id = dbApp.getId();
+        Integer status = CommonStatusEnum.ENABLE.getStatus();
+        // 调用
+        appService.updateAppStatus(id, status);
+        // 断言
+        PayAppDO app = appMapper.selectById(id); // 获取最新的
+        assertEquals(status, app.getStatus());
+    }
+
+    @Test
+    public void testDeleteApp_success() {
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class);
         appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
         // 准备参数
         Long id = dbApp.getId();
@@ -117,6 +137,65 @@ public class PayAppServiceTest extends BaseDbUnitTest {
         assertServiceException(() -> appService.deleteApp(id), PAY_APP_NOT_FOUND);
     }
 
+    @Test
+    public void testDeleteApp_existOrder() {
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class);
+        appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbApp.getId();
+        // mock 订单有订单
+        when(orderService.getOrderCountByAppId(eq(id))).thenReturn(10L);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> appService.deleteApp(id), PAY_APP_EXIST_ORDER_CANT_DELETE);
+    }
+
+    @Test
+    public void testDeleteApp_existRefund() {
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class);
+        appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbApp.getId();
+        // mock 订单有订单
+        when(refundService.getRefundCountByAppId(eq(id))).thenReturn(10L);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> appService.deleteApp(id), PAY_APP_EXIST_REFUND_CANT_DELETE);
+    }
+
+    @Test
+    public void testApp() {
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class);
+        appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbApp.getId();
+
+        // 调用
+        PayAppDO app = appService.getApp(id);
+        // 校验数据一致
+        assertPojoEquals(app, dbApp);
+    }
+
+    @Test
+    public void testAppMap() {
+        // mock 数据
+        PayAppDO dbApp01 = randomPojo(PayAppDO.class);
+        appMapper.insert(dbApp01);// @Sql: 先插入出一条存在的数据
+        PayAppDO dbApp02 = randomPojo(PayAppDO.class);
+        appMapper.insert(dbApp02);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbApp01.getId();
+
+        // 调用
+        Map<Long, PayAppDO> appMap = appService.getAppMap(singleton(id));
+        // 校验数据一致
+        assertEquals(1, appMap.size());
+        assertPojoEquals(dbApp01, appMap.get(id));
+    }
+
     @Test
     public void testGetAppPage() {
         // mock 数据
@@ -147,4 +226,37 @@ public class PayAppServiceTest extends BaseDbUnitTest {
         assertPojoEquals(dbApp, pageResult.getList().get(0));
     }
 
+    @Test
+    public void testValidPayApp_success() {
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class,
+                o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbApp.getId();
+
+        // 调用
+        PayAppDO app = appService.validPayApp(id);
+        // 校验数据一致
+        assertPojoEquals(app, dbApp);
+    }
+
+    @Test
+    public void testValidPayApp_notFound() {
+        assertServiceException(() -> appService.validPayApp(randomLongId()), PAY_APP_NOT_FOUND);
+    }
+
+    @Test
+    public void testValidPayApp_disable() {
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class,
+                o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
+        appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbApp.getId();
+
+        // 调用,并断言异常
+        assertServiceException(() -> appService.validPayApp(id), PAY_APP_IS_DISABLE);
+    }
+
 }

+ 96 - 180
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java

@@ -1,32 +1,32 @@
 package cn.iocoder.yudao.module.pay.service.channel;
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 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;
 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO;
-import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelPageReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO;
 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.LocalDateTime;
 
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import java.util.Collections;
+import java.util.List;
+
 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.randomLongId;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_EXIST_SAME_CHANNEL_ERROR;
 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_EXISTS;
 import static org.junit.jupiter.api.Assertions.*;
 
@@ -46,109 +46,62 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
     @MockBean
     private Validator validator;
 
-    @Test
-    public void testCreateWechatVersion2Channel_success() {
-        // 准备参数
-        WxPayClientConfig v2Config = getV2Config();
-        PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
-            o.setCode(PayChannelEnum.WX_PUB.getCode());
-            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-            o.setConfig(JSON.toJSONString(v2Config));
-        });
-
-        // 调用
-        Long channelId = channelService.createChannel(reqVO);
-        // 断言
-        assertNotNull(channelId);
-        // 校验记录的属性是否正确
-        PayChannelDO channel = channelMapper.selectById(channelId);
-        assertPojoEquals(reqVO, channel, "config");
-        // 关于config 对象应该拿出来重新对比
-        assertPojoEquals(v2Config, channel.getConfig());
+    @BeforeEach
+    public void setUp() {
+        channelService.setChannelCache(null);
     }
 
     @Test
-    public void testCreateWechatVersion3Channel_success() {
+    public void testCreateChannel_success() {
         // 准备参数
-        WxPayClientConfig v3Config = getV3Config();
+        WxPayClientConfig config = randomWxPayClientConfig();
         PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
+            o.setStatus(randomCommonStatus());
             o.setCode(PayChannelEnum.WX_PUB.getCode());
-            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-            o.setConfig(JSON.toJSONString(v3Config));
-        });
-
-        // 调用
-        Long channelId = channelService.createChannel(reqVO);
-        // 断言
-        assertNotNull(channelId);
-        // 校验记录的属性是否正确
-        PayChannelDO channel = channelMapper.selectById(channelId);
-        assertPojoEquals(reqVO, channel, "config");
-        // 关于config 对象应该拿出来重新对比
-        assertPojoEquals(v3Config, channel.getConfig());
-    }
-
-    @Test
-    public void testCreateAliPayPublicKeyChannel_success() {
-        // 准备参数
-
-        AlipayPayClientConfig payClientConfig = getPublicKeyConfig();
-        PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
-            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
-            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-            o.setConfig(JSON.toJSONString(payClientConfig));
+            o.setConfig(JsonUtils.toJsonString(config));
         });
 
         // 调用
         Long channelId = channelService.createChannel(reqVO);
-        // 断言
-        assertNotNull(channelId);
         // 校验记录的属性是否正确
         PayChannelDO channel = channelMapper.selectById(channelId);
         assertPojoEquals(reqVO, channel, "config");
-        // 关于config 对象应该拿出来重新对比
-        assertPojoEquals(payClientConfig, channel.getConfig());
+        assertPojoEquals(config, channel.getConfig());
+        // 校验缓存
+        assertEquals(1, channelService.getChannelCache().size());
+        assertEquals(channel, channelService.getChannelCache().get(0));
     }
 
     @Test
-    public void testCreateAliPayCertificateChannel_success() {
+    public void testCreateChannel_exists() {
+        // mock 数据
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class,
+                o -> o.setConfig(randomWxPayClientConfig()));
+        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
         // 准备参数
-
-        AlipayPayClientConfig payClientConfig = getCertificateConfig();
         PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
-            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
-            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-            o.setConfig(JSON.toJSONString(payClientConfig));
+            o.setAppId(dbChannel.getAppId());
+            o.setCode(dbChannel.getCode());
         });
 
-        // 调用
-        Long channelId = channelService.createChannel(reqVO);
-        // 断言
-        assertNotNull(channelId);
-        // 校验记录的属性是否正确
-        PayChannelDO channel = channelMapper.selectById(channelId);
-        assertPojoEquals(reqVO, channel, "config");
-        // 关于config 对象应该拿出来重新对比
-        assertPojoEquals(payClientConfig, channel.getConfig());
+        // 调用, 并断言异常
+        assertServiceException(() -> channelService.createChannel(reqVO), CHANNEL_EXIST_SAME_CHANNEL_ERROR);
     }
 
     @Test
     public void testUpdateChannel_success() {
         // mock 数据
-        AlipayPayClientConfig payClientConfig = getCertificateConfig();
         PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
             o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
-            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-            o.setConfig(payClientConfig);
+            o.setConfig(randomAlipayPayClientConfig());
         });
         channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
         // 准备参数
-        AlipayPayClientConfig payClientPublicKeyConfig = getPublicKeyConfig();
+        AlipayPayClientConfig config = randomAlipayPayClientConfig();
         PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> {
-            o.setCode(dbChannel.getCode());
-            o.setStatus(dbChannel.getStatus());
-            o.setConfig(JSON.toJSONString(payClientPublicKeyConfig));
             o.setId(dbChannel.getId()); // 设置更新的 ID
+            o.setStatus(randomCommonStatus());
+            o.setConfig(JsonUtils.toJsonString(config));
         });
 
         // 调用
@@ -156,15 +109,17 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
         // 校验是否更新正确
         PayChannelDO channel = channelMapper.selectById(reqVO.getId()); // 获取最新的
         assertPojoEquals(reqVO, channel, "config");
-        assertPojoEquals(payClientPublicKeyConfig, channel.getConfig());
+        assertPojoEquals(config, channel.getConfig());
+        // 校验缓存
+        assertEquals(1, channelService.getChannelCache().size());
+        assertEquals(channel, channelService.getChannelCache().get(0));
     }
 
     @Test
     public void testUpdateChannel_notExists() {
         // 准备参数
-        AlipayPayClientConfig payClientPublicKeyConfig = getPublicKeyConfig();
+        AlipayPayClientConfig payClientPublicKeyConfig = randomAlipayPayClientConfig();
         PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> {
-            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
             o.setStatus(CommonStatusEnum.ENABLE.getStatus());
             o.setConfig(JSON.toJSONString(payClientPublicKeyConfig));
         });
@@ -176,11 +131,9 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
     @Test
     public void testDeleteChannel_success() {
         // mock 数据
-        AlipayPayClientConfig payClientConfig = getCertificateConfig();
         PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
             o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
-            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-            o.setConfig(payClientConfig);
+            o.setConfig(randomAlipayPayClientConfig());
         });
         channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
         // 准备参数
@@ -190,6 +143,8 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
         channelService.deleteChannel(id);
         // 校验数据不存在了
         assertNull(channelMapper.selectById(id));
+        // 校验缓存
+        assertEquals(0, channelService.getChannelCache().size());
     }
 
     @Test
@@ -201,119 +156,80 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
         assertServiceException(() -> channelService.deleteChannel(id), CHANNEL_NOT_EXISTS);
     }
 
-    @Test // TODO 请修改 null 为需要的值
-    public void testGetChannelPage() {
+    @Test
+    public void testGetChannel() {
         // mock 数据
-        AlipayPayClientConfig payClientConfig = getCertificateConfig();
-        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { // 等会查询到
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
             o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
-            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-            o.setRemark("灿灿子的支付渠道");
-            o.setFeeRate(0.03);
-            o.setAppId(1L);
-            o.setConfig(payClientConfig);
-            o.setCreateTime(buildTime(2021,11,20));
+            o.setConfig(randomAlipayPayClientConfig());
         });
-        channelMapper.insert(dbChannel);
-        // 执行拷贝的时候会出现异常,所以在插入后要重置为null 后续在写入新的
-        dbChannel.setConfig(null);
-        // 测试 code 不匹配
-        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
-            o.setConfig(payClientConfig);
-            o.setCode(PayChannelEnum.WX_PUB.getCode());
-        }));
-        // 测试 status 不匹配
-        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
-            o.setConfig(payClientConfig);
-            o.setStatus(CommonStatusEnum.DISABLE.getStatus());
-        }));
-        // 测试 remark 不匹配
-        channelMapper.insert(cloneIgnoreId(dbChannel, o ->{
-            o.setConfig(payClientConfig);
-            o.setRemark("敏敏子的渠道");
-        }));
-        // 测试 feeRate 不匹配
-        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
-            o.setConfig(payClientConfig);
-            o.setFeeRate(1.23);
-        }));
-        // 测试 appId 不匹配
-        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
-            o.setConfig(payClientConfig);
-            o.setAppId(2L);
-        }));
-        // 测试 createTime 不匹配
-        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
-            o.setConfig(payClientConfig);
-            o.setCreateTime(buildTime(2021, 10, 20));
-        }));
+        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
         // 准备参数
-        PayChannelPageReqVO reqVO = new PayChannelPageReqVO();
-        reqVO.setCode(PayChannelEnum.ALIPAY_APP.getCode());
-        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
-        reqVO.setRemark("灿灿子的支付渠道");
-        reqVO.setFeeRate(0.03);
-        reqVO.setAppId(1L);
-        reqVO.setConfig(JSON.toJSONString(payClientConfig));
-        reqVO.setCreateTime((new LocalDateTime[]{buildTime(2021,11,19),buildTime(2021,11,21)}));
+        Long id = dbChannel.getId();
 
         // 调用
-        PageResult<PayChannelDO> pageResult = channelService.getChannelPage(reqVO);
-        // 断言
-        assertEquals(1, pageResult.getTotal());
-        assertEquals(1, pageResult.getList().size());
-        assertPojoEquals(dbChannel, pageResult.getList().get(0), "config");
-        assertPojoEquals(payClientConfig, pageResult.getList().get(0).getConfig());
-
+        PayChannelDO channel = channelService.getChannel(id);
+        // 校验是否更新正确
+        assertPojoEquals(dbChannel, channel);
     }
 
-    public WxPayClientConfig getV2Config() {
-        return new WxPayClientConfig()
-                .setAppId("APP00001")
-                .setMchId("MCH00001")
-                .setApiVersion(WxPayClientConfig.API_VERSION_V2)
-                .setMchKey("dsa1d5s6a1d6sa16d1sa56d15a61das6")
-                .setApiV3Key("")
-                .setPrivateCertContent("")
-                .setPrivateKeyContent("");
+    @Test
+    public void testGetChannelListByAppIds() {
+        // mock 数据
+        PayChannelDO dbChannel01 = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setConfig(randomAlipayPayClientConfig());
+        });
+        channelMapper.insert(dbChannel01);// @Sql: 先插入出一条存在的数据
+        PayChannelDO dbChannel02 = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.WX_PUB.getCode());
+            o.setConfig(randomWxPayClientConfig());
+        });
+        channelMapper.insert(dbChannel02);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long appId = dbChannel01.getAppId();
+
+        // 调用
+        List<PayChannelDO> channels = channelService.getChannelListByAppIds(Collections.singleton(appId));
+        // 校验是否更新正确
+        assertEquals(1, channels.size());
+        assertPojoEquals(dbChannel01, channels.get(0));
     }
 
-    public WxPayClientConfig getV3Config() {
-        return new WxPayClientConfig()
-                .setAppId("APP00001")
-                .setMchId("MCH00001")
-                .setApiVersion(WxPayClientConfig.API_VERSION_V3)
-                .setMchKey("")
-                .setApiV3Key("sdadasdsadadsa")
-                .setPrivateKeyContent("dsa445das415d15asd16ad156as")
-                .setPrivateCertContent("dsadasd45asd4s5a");
+    @Test
+    public void testGetChannelByAppIdAndCode() {
+        // mock 数据
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setConfig(randomAlipayPayClientConfig());
+        });
+        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long appId = dbChannel.getAppId();
+        String code = dbChannel.getCode();;
 
+        // 调用
+        PayChannelDO channel = channelService.getChannelByAppIdAndCode(appId, code);
+        // 断言
+        assertPojoEquals(channel, dbChannel);
     }
 
-    public AlipayPayClientConfig getPublicKeyConfig() {
-        return new AlipayPayClientConfig()
-                .setServerUrl(ALIPAY_SERVER_URL)
-                .setAppId("APP00001")
-                .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT)
-                .setMode(AlipayPayClientConfig.MODE_PUBLIC_KEY)
-                .setPrivateKey("13131321312")
-                .setAlipayPublicKey("13321321321")
-                .setAppCertContent("")
-                .setAlipayPublicCertContent("")
-                .setRootCertContent("");
+    public WxPayClientConfig randomWxPayClientConfig() {
+        return new WxPayClientConfig()
+                .setAppId(randomString())
+                .setMchId(randomString())
+                .setApiVersion(WxPayClientConfig.API_VERSION_V2)
+                .setMchKey(randomString());
     }
 
-    public AlipayPayClientConfig getCertificateConfig() {
+    public AlipayPayClientConfig randomAlipayPayClientConfig() {
         return new AlipayPayClientConfig()
-                .setServerUrl(ALIPAY_SERVER_URL)
-                .setAppId("APP00001")
+                .setServerUrl(randomURL())
+                .setAppId(randomString())
                 .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT)
-                .setMode(AlipayPayClientConfig.MODE_CERTIFICATE)
-                .setPrivateKey("")
-                .setAlipayPublicKey("")
-                .setAppCertContent("13321321321sda")
-                .setAlipayPublicCertContent("13321321321aqeqw")
-                .setRootCertContent("13321321321dsad");
+                .setMode(AlipayPayClientConfig.MODE_PUBLIC_KEY)
+                .setPrivateKey(randomString())
+                .setAlipayPublicKey(randomString());
     }
 
 }

+ 0 - 9
yudao-ui-admin/src/api/pay/channel.js

@@ -27,15 +27,6 @@ export function deleteChannel(id) {
   })
 }
 
-// 获得支付渠道分页
-export function getChannelPage(query) {
-  return request({
-    url: '/pay/channel/page',
-    method: 'get',
-    params: query
-  })
-}
-
 // 获得支付渠道
 export function getChannel(appId,code) {
   return request({