list = getAppList(ids);
return CollectionUtils.convertMap(list, PayAppDO::getId);
}
/**
* 支付应用的合法性
- *
+ *
* 如果不合法,抛出 {@link ServiceException} 业务异常
*
* @param id 应用编号
@@ -102,4 +102,14 @@ public interface PayAppService {
*/
PayAppDO validPayApp(Long id);
+ /**
+ * 支付应用的合法性
+ *
+ * 如果不合法,抛出 {@link ServiceException} 业务异常
+ *
+ * @param appKey 应用标识
+ * @return 应用
+ */
+ PayAppDO validPayApp(String appKey);
+
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java
index 56087ef132..c0e7558f11 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java
@@ -11,11 +11,11 @@ import cn.iocoder.yudao.module.pay.dal.mysql.app.PayAppMapper;
import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
+import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
@@ -43,6 +43,9 @@ public class PayAppServiceImpl implements PayAppService {
@Override
public Long createApp(PayAppCreateReqVO createReqVO) {
+ // 验证 appKey 是否重复
+ validateAppKeyUnique(null, createReqVO.getAppKey());
+
// 插入
PayAppDO app = PayAppConvert.INSTANCE.convert(createReqVO);
appMapper.insert(app);
@@ -54,11 +57,28 @@ public class PayAppServiceImpl implements PayAppService {
public void updateApp(PayAppUpdateReqVO updateReqVO) {
// 校验存在
validateAppExists(updateReqVO.getId());
+ // 验证 appKey 是否重复
+ validateAppKeyUnique(updateReqVO.getId(), updateReqVO.getAppKey());
+
// 更新
PayAppDO updateObj = PayAppConvert.INSTANCE.convert(updateReqVO);
appMapper.updateById(updateObj);
}
+ void validateAppKeyUnique(Long id, String appKey) {
+ PayAppDO app = appMapper.selectByAppKey(appKey);
+ if (app == null) {
+ return;
+ }
+ // 如果 id 为空,说明不用比较是否为相同 appKey 的应用
+ if (id == null) {
+ throw exception(APP_KEY_EXISTS);
+ }
+ if (!app.getId().equals(id)) {
+ throw exception(APP_KEY_EXISTS);
+ }
+ }
+
@Override
public void updateAppStatus(Long id, Integer status) {
// 校验商户存在
@@ -101,7 +121,7 @@ public class PayAppServiceImpl implements PayAppService {
@Override
public List getAppList() {
- return appMapper.selectList();
+ return appMapper.selectList();
}
@Override
@@ -110,14 +130,30 @@ public class PayAppServiceImpl implements PayAppService {
}
@Override
- public PayAppDO validPayApp(Long id) {
- PayAppDO app = appMapper.selectById(id);
+ public PayAppDO validPayApp(Long appId) {
+ PayAppDO app = appMapper.selectById(appId);
+ return validatePayApp(app);
+ }
+
+ @Override
+ public PayAppDO validPayApp(String appKey) {
+ PayAppDO app = appMapper.selectByAppKey(appKey);
+ return validatePayApp(app);
+ }
+
+ /**
+ * 校验支付应用实体的有效性:存在 + 开启
+ *
+ * @param app 待校验的支付应用实体
+ * @return 校验通过的支付应用实体
+ */
+ private PayAppDO validatePayApp(PayAppDO app) {
// 校验是否存在
if (app == null) {
throw exception(ErrorCodeConstants.APP_NOT_FOUND);
}
// 校验是否禁用
- if (CommonStatusEnum.DISABLE.getStatus().equals(app.getStatus())) {
+ if (CommonStatusEnum.isDisable(app.getStatus())) {
throw exception(ErrorCodeConstants.APP_IS_DISABLE);
}
return app;
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java
index 018dd36fd4..052032b1fa 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java
@@ -43,11 +43,11 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
public class PayDemoOrderServiceImpl implements PayDemoOrderService {
/**
- * 接入的实力应用编号
+ * 接入的支付应用标识
*
* 从 [支付管理 -> 应用信息] 里添加
*/
- private static final Long PAY_APP_ID = 7L;
+ private static final String PAY_APP_KEY = "demo";
/**
* 商品信息 Map
@@ -88,7 +88,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
// 2.1 创建支付单
Long payOrderId = payOrderApi.createOrder(new PayOrderCreateReqDTO()
- .setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用
+ .setAppKey(PAY_APP_KEY).setUserIp(getClientIP()) // 支付应用
.setMerchantOrderId(demoOrder.getId().toString()) // 业务的订单编号
.setSubject(spuName).setBody("").setPrice(price) // 价格信息
.setExpireTime(addTime(Duration.ofHours(2L)))); // 支付的过期时间
@@ -190,7 +190,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
String refundId = order.getId() + "-refund";
// 2.2 创建退款单
Long payRefundId = payRefundApi.createRefund(new PayRefundCreateReqDTO()
- .setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用
+ .setAppKey(PAY_APP_KEY).setUserIp(getClientIP()) // 支付应用
.setMerchantOrderId(String.valueOf(order.getId())) // 支付单号
.setMerchantRefundId(refundId)
.setReason("想退钱").setPrice(order.getPrice()));// 价格信息
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
index a058b95b8d..f8adb42d53 100755
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
@@ -111,11 +111,11 @@ public class PayOrderServiceImpl implements PayOrderService {
@Override
public Long createOrder(PayOrderCreateReqDTO reqDTO) {
// 校验 App
- PayAppDO app = appService.validPayApp(reqDTO.getAppId());
+ PayAppDO app = appService.validPayApp(reqDTO.getAppKey());
// 查询对应的支付交易单是否已经存在。如果是,则直接返回
PayOrderDO order = orderMapper.selectByAppIdAndMerchantOrderId(
- reqDTO.getAppId(), reqDTO.getMerchantOrderId());
+ app.getId(), reqDTO.getMerchantOrderId());
if (order != null) {
log.warn("[createOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
order.getMerchantOrderId(), toJsonString(order)); // 理论来说,不会出现这个情况
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
index 20855826d8..8df7f88615 100755
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
@@ -26,12 +26,12 @@ 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 jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -93,9 +93,9 @@ public class PayRefundServiceImpl implements PayRefundService {
@Override
public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
// 1.1 校验 App
- PayAppDO app = appService.validPayApp(reqDTO.getAppId());
+ PayAppDO app = appService.validPayApp(reqDTO.getAppKey());
// 1.2 校验支付订单
- PayOrderDO order = validatePayOrderCanRefund(reqDTO);
+ PayOrderDO order = validatePayOrderCanRefund(reqDTO, app.getId());
// 1.3 校验支付渠道是否有效
PayChannelDO channel = channelService.validPayChannel(order.getChannelId());
PayClient client = channelService.getPayClient(channel.getId());
@@ -113,7 +113,7 @@ public class PayRefundServiceImpl implements PayRefundService {
// 2.1 插入退款单
String no = noRedisDAO.generate(payProperties.getRefundNoPrefix());
refund = PayRefundConvert.INSTANCE.convert(reqDTO)
- .setNo(no).setOrderId(order.getId()).setOrderNo(order.getNo())
+ .setNo(no).setAppId(app.getId()).setOrderId(order.getId()).setOrderNo(order.getNo())
.setChannelId(order.getChannelId()).setChannelCode(order.getChannelCode())
// 商户相关的字段
.setNotifyUrl(app.getRefundNotifyUrl())
@@ -153,8 +153,8 @@ public class PayRefundServiceImpl implements PayRefundService {
* @param reqDTO 退款申请信息
* @return 支付订单
*/
- private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO) {
- PayOrderDO order = orderService.getOrder(reqDTO.getAppId(), reqDTO.getMerchantOrderId());
+ private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO, Long appId) {
+ PayOrderDO order = orderService.getOrder(appId, reqDTO.getMerchantOrderId());
if (order == null) {
throw exception(PAY_ORDER_NOT_FOUND);
}
@@ -164,11 +164,11 @@ public class PayRefundServiceImpl implements PayRefundService {
}
// 校验金额,退款金额不能大于原定的金额
- if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()){
+ if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()) {
throw exception(REFUND_PRICE_EXCEED);
}
// 是否有退款中的订单
- if (refundMapper.selectCountByAppIdAndOrderId(reqDTO.getAppId(), order.getId(),
+ if (refundMapper.selectCountByAppIdAndOrderId(appId, order.getId(),
PayRefundStatusEnum.WAITING.getStatus()) > 0) {
throw exception(REFUND_HAS_REFUNDING);
}
@@ -197,9 +197,10 @@ public class PayRefundServiceImpl implements PayRefundService {
* 通知并更新订单的退款结果
*
* @param channel 支付渠道
- * @param notify 通知
+ * @param notify 通知
*/
- @Transactional(rollbackFor = Exception.class) // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyRefund(channel, notify) 调用,否则事务不生效
+ // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyRefund(channel, notify) 调用,否则事务不生效
+ @Transactional(rollbackFor = Exception.class)
public void notifyRefund(PayChannelDO channel, PayRefundRespDTO notify) {
// 情况一:退款成功
if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) {
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java
index 73b726dcde..5ace5ab4be 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java
@@ -24,12 +24,12 @@ import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
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 jakarta.annotation.Resource;
+import jakarta.validation.Validator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import javax.annotation.Resource;
-import javax.validation.Validator;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -79,16 +79,16 @@ public class PayTransferServiceImpl implements PayTransferService {
@Override
public Long createTransfer(PayTransferCreateReqDTO reqDTO) {
// 1.1 校验 App
- PayAppDO payApp = appService.validPayApp(reqDTO.getAppId());
+ PayAppDO payApp = appService.validPayApp(reqDTO.getAppKey());
// 1.2 校验支付渠道是否有效
- PayChannelDO channel = channelService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode());
+ PayChannelDO channel = channelService.validPayChannel(payApp.getId(), reqDTO.getChannelCode());
PayClient client = channelService.getPayClient(channel.getId());
if (client == null) {
log.error("[createTransfer][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
throw exception(CHANNEL_NOT_FOUND);
}
// 1.3 校验转账单已经发起过转账。
- PayTransferDO transfer = validateTransferCanCreate(reqDTO);
+ PayTransferDO transfer = validateTransferCanCreate(reqDTO, payApp.getId());
if (transfer == null) {
// 2.不存在创建转账单. 否则允许使用相同的 no 再次发起转账
@@ -116,8 +116,8 @@ public class PayTransferServiceImpl implements PayTransferService {
return transfer.getId();
}
- private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO dto) {
- PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(dto.getAppId(), dto.getMerchantTransferId());
+ private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO dto, Long appId) {
+ PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(appId, dto.getMerchantTransferId());
if (transfer != null) {
// 已经存在,并且状态不为等待状态。说明已经调用渠道转账并返回结果.
if (!PayTransferStatusEnum.isWaiting(transfer.getStatus())) {
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java
index d2af17984c..c50cb19d42 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java
@@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletRechargeMapper;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
+import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
@@ -51,11 +52,6 @@ import static cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum.*;
@Slf4j
public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
- /**
- * TODO 芋艿:放到 payconfig
- */
- private static final Long WALLET_PAY_APP_ID = 8L;
-
private static final String WALLET_RECHARGE_ORDER_SUBJECT = "钱包余额充值";
@Resource
@@ -68,9 +64,13 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
private PayRefundService payRefundService;
@Resource
private PayWalletRechargePackageService payWalletRechargePackageService;
+
@Resource
public SocialClientApi socialClientApi;
+ @Resource
+ private PayProperties payProperties;
+
@Override
@Transactional(rollbackFor = Exception.class)
public PayWalletRechargeDO createWalletRecharge(Long userId, Integer userType, String userIp,
@@ -92,7 +92,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
// 2.1 创建支付单
Long payOrderId = payOrderService.createOrder(new PayOrderCreateReqDTO()
- .setAppId(WALLET_PAY_APP_ID).setUserIp(userIp)
+ .setAppKey(payProperties.getWalletPayAppKey()).setUserIp(userIp)
.setMerchantOrderId(recharge.getId().toString()) // 业务的订单编号
.setSubject(WALLET_RECHARGE_ORDER_SUBJECT).setBody("")
.setPrice(recharge.getPayPrice())
@@ -174,7 +174,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
String walletRechargeId = String.valueOf(id);
String refundId = walletRechargeId + "-refund";
Long payRefundId = payRefundService.createPayRefund(new PayRefundCreateReqDTO()
- .setAppId(WALLET_PAY_APP_ID).setUserIp(userIp)
+ .setAppKey(payProperties.getWalletPayAppKey()).setUserIp(userIp)
.setMerchantOrderId(walletRechargeId)
.setMerchantRefundId(refundId)
.setReason("想退钱").setPrice(walletRecharge.getPayPrice()));
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java
index 44a8c7c86a..a2f3d92d67 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.pay.service.wallet;
+import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionPageReqVO;
import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
@@ -11,12 +12,11 @@ import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletTransactionMapper;
import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO;
+import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
-
import java.time.LocalDateTime;
import static cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO.TYPE_EXPENSE;
@@ -53,6 +53,16 @@ public class PayWalletTransactionServiceImpl implements PayWalletTransactionServ
@Override
public PageResult getWalletTransactionPage(PayWalletTransactionPageReqVO pageVO) {
+ // 基于 userId + userType 查询钱包
+ if (pageVO.getWalletId() == null
+ && ObjectUtil.isAllNotEmpty(pageVO.getUserId(), pageVO.getUserType())) {
+ PayWalletDO wallet = payWalletService.getOrCreateWallet(pageVO.getUserId(), pageVO.getUserType());
+ if (wallet != null) {
+ pageVO.setWalletId(wallet.getId());
+ }
+ }
+
+ // 查询分页
return payWalletTransactionMapper.selectPage(pageVO.getWalletId(), null, pageVO, null);
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java
index 2c9db46590..60a3839f25 100755
--- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java
@@ -218,11 +218,11 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
public void testCreateOrder_success() {
// mock 参数
PayOrderCreateReqDTO reqDTO = randomPojo(PayOrderCreateReqDTO.class,
- o -> o.setAppId(1L).setMerchantOrderId("10")
+ o -> o.setAppKey("demo").setMerchantOrderId("10")
.setSubject(randomString()).setBody(randomString()));
// mock 方法
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L).setOrderNotifyUrl("http://127.0.0.1"));
- when(appService.validPayApp(eq(reqDTO.getAppId()))).thenReturn(app);
+ when(appService.validPayApp(eq(reqDTO.getAppKey()))).thenReturn(app);
// 调用
Long orderId = orderService.createOrder(reqDTO);
@@ -239,10 +239,13 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
public void testCreateOrder_exists() {
// mock 参数
PayOrderCreateReqDTO reqDTO = randomPojo(PayOrderCreateReqDTO.class,
- o -> o.setAppId(1L).setMerchantOrderId("10"));
+ o -> o.setAppKey("demo").setMerchantOrderId("10"));
// mock 数据
PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> o.setAppId(1L).setMerchantOrderId("10"));
orderMapper.insert(dbOrder);
+ // mock 方法
+ PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L).setOrderNotifyUrl("http://127.0.0.1"));
+ when(appService.validPayApp(eq(reqDTO.getAppKey()))).thenReturn(app);
// 调用
Long orderId = orderService.createOrder(reqDTO);
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java
index a9296f93f1..c413958ea5 100755
--- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java
@@ -209,10 +209,10 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
@Test
public void testCreateRefund_orderNotFound() {
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
- o -> o.setAppId(1L));
+ o -> o.setAppKey("demo"));
// mock 方法(app)
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
- when(appService.validPayApp(eq(1L))).thenReturn(app);
+ when(appService.validPayApp(eq("demo"))).thenReturn(app);
// 调用,并断言异常
assertServiceException(() -> refundService.createPayRefund(reqDTO),
@@ -232,10 +232,10 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
private void testCreateRefund_orderWaitingOrClosed(Integer status) {
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
- o -> o.setAppId(1L).setMerchantOrderId("100"));
+ o -> o.setAppKey("demo").setMerchantOrderId("100"));
// mock 方法(app)
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
- when(appService.validPayApp(eq(1L))).thenReturn(app);
+ when(appService.validPayApp(eq("demo"))).thenReturn(app);
// mock 数据(order)
PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(status));
when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
@@ -249,10 +249,10 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
public void testCreateRefund_refundPriceExceed() {
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
- o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(10));
+ o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(10));
// mock 方法(app)
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
- when(appService.validPayApp(eq(1L))).thenReturn(app);
+ when(appService.validPayApp(eq("demo"))).thenReturn(app);
// mock 数据(order)
PayOrderDO order = randomPojo(PayOrderDO.class, o ->
o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
@@ -268,10 +268,10 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
public void testCreateRefund_orderHasRefunding() {
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
- o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(10));
+ o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(10));
// mock 方法(app)
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
- when(appService.validPayApp(eq(1L))).thenReturn(app);
+ when(appService.validPayApp(eq("demo"))).thenReturn(app);
// mock 数据(order)
PayOrderDO order = randomPojo(PayOrderDO.class, o ->
o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
@@ -291,10 +291,10 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
public void testCreateRefund_channelNotFound() {
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
- o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9));
+ o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9));
// mock 方法(app)
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
- when(appService.validPayApp(eq(1L))).thenReturn(app);
+ when(appService.validPayApp(eq("demo"))).thenReturn(app);
// mock 数据(order)
PayOrderDO order = randomPojo(PayOrderDO.class, o ->
o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
@@ -315,11 +315,11 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
public void testCreateRefund_refundExists() {
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
- o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
+ o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9)
.setMerchantRefundId("200").setReason("测试退款"));
// mock 方法(app)
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
- when(appService.validPayApp(eq(1L))).thenReturn(app);
+ when(appService.validPayApp(eq("demo"))).thenReturn(app);
// mock 数据(order)
PayOrderDO order = randomPojo(PayOrderDO.class, o ->
o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
@@ -347,11 +347,11 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
public void testCreateRefund_invokeException() {
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
- o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
+ o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9)
.setMerchantRefundId("200").setReason("测试退款"));
// mock 方法(app)
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
- when(appService.validPayApp(eq(1L))).thenReturn(app);
+ when(appService.validPayApp(eq("demo"))).thenReturn(app);
// mock 数据(order)
PayOrderDO order = randomPojo(PayOrderDO.class, o ->
o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
@@ -391,11 +391,11 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
// 准备参数
PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
- o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
+ o -> o.setAppKey("demo").setMerchantOrderId("100").setPrice(9)
.setMerchantRefundId("200").setReason("测试退款"));
// mock 方法(app)
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
- when(appService.validPayApp(eq(1L))).thenReturn(app);
+ when(appService.validPayApp(eq("demo"))).thenReturn(app);
// mock 数据(order)
PayOrderDO order = randomPojo(PayOrderDO.class, o ->
o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql b/yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql
index 6ae2ce2d44..3f9f764179 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql
+++ b/yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql
@@ -1,5 +1,6 @@
CREATE TABLE IF NOT EXISTS "pay_app" (
"id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "app_key" varchar(64) NOT NULL,
"name" varchar(64) NOT NULL,
"status" tinyint NOT NULL,
"remark" varchar(255) DEFAULT NULL,
diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java
index 066ff0122d..6e44ec8a1d 100644
--- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java
+++ b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayClientConfig.java
@@ -28,6 +28,11 @@ public class AlipayPayClientConfig implements PayClientConfig {
*/
public static final Integer MODE_CERTIFICATE = 2;
+ /**
+ * 接口内容加密方式 - AES 加密
+ */
+ public static final String ENC_TYPE_AES = "AES";
+
/**
* 签名算法类型 - RSA
*/
@@ -94,6 +99,22 @@ public class AlipayPayClientConfig implements PayClientConfig {
@NotBlank(message = "指定根证书内容字符串不能为空", groups = {ModeCertificate.class})
private String rootCertContent;
+ /**
+ * 接口内容加密方式
+ *
+ * 1. 如果为空,将使用无加密方式
+ * 2. 如果要加密,目前支付宝只有 AES 一种加密方式
+ *
+ * @see 支付宝开放平台
+ * @see AlipayPayClientConfig#ENC_TYPE_AES
+ */
+ private String encryptType;
+
+ /**
+ * 接口内容加密的私钥
+ */
+ private String encryptKey;
+
public interface ModePublicKey {
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java
index ed6dd7a8d7..f8158cdf26 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java
@@ -102,8 +102,6 @@ public class AliyunSmsClient extends AbstractSmsClient {
queryParam.put("TemplateCode", apiTemplateId);
JSONObject response = request("QuerySmsTemplate", queryParam);
- System.out.println("getSmsTemplate response is =====" + response.toString());
-
// 2.1 请求失败
String code = response.getStr("Code");
if (ObjectUtil.notEqual(code, RESPONSE_CODE_SUCCESS)) {
@@ -170,7 +168,6 @@ public class AliyunSmsClient extends AbstractSmsClient {
// 4. 构建 Authorization 签名
String canonicalRequest = "POST" + "\n" + "/" + "\n" + queryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody;
String hashedCanonicalRequest = DigestUtil.sha256Hex(canonicalRequest);
-
String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest;
String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名
headers.put("Authorization", "ACS3-HMAC-SHA256" + " " + "Credential=" + properties.getApiKey()
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java
index 4df8208617..fdf2faa1a2 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java
@@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
-
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
@@ -31,13 +30,12 @@ import java.util.*;
import java.time.LocalDateTime;
-
import static cn.hutool.crypto.digest.DigestUtil.sha256Hex;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
-
+// todo @scholar:参考阿里云在优化下
/**
* 华为短信客户端的实现类
*
@@ -56,7 +54,6 @@ public class HuaweiSmsClient extends AbstractSmsClient {
@Override
protected void doInit() {
-
}
public HuaweiSmsClient(SmsChannelProperties properties) {
@@ -68,6 +65,7 @@ public class HuaweiSmsClient extends AbstractSmsClient {
@Override
public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
List> templateParams) throws Throwable {
+ // 参考链接 https://support.huaweicloud.com/api-msgsms/sms_05_0001.html
// 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构
// 所以将 通道号 拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"。空格为分隔符。
String sender = StrUtil.subAfter(apiTemplateId, " ", true); //中国大陆短信签名通道号或全球短信通道号
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java
index c0982fa4dd..23a01db247 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java
@@ -2,38 +2,29 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
-import cn.hutool.http.HttpRequest;
-import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
-import lombok.Data;
+import jakarta.xml.bind.DatatypeConverter;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
-import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
-import java.time.LocalDateTime;
import java.util.*;
import static cn.hutool.crypto.digest.DigestUtil.sha256Hex;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
-// TODO @scholar 建议参考 AliyunSmsClient 优化下
/**
* 腾讯云短信功能实现
*
@@ -43,6 +34,9 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE
*/
public class TencentSmsClient extends AbstractSmsClient {
+ private static final String VERSION = "2021-01-11";
+ private static final String REGION = "ap-guangzhou";
+
/**
* 调用成功 code
*/
@@ -56,7 +50,6 @@ public class TencentSmsClient extends AbstractSmsClient {
*/
private static final long INTERNATIONAL_CHINA = 0L;
-
public TencentSmsClient(SmsChannelProperties properties) {
super(properties);
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
@@ -65,7 +58,6 @@ public class TencentSmsClient extends AbstractSmsClient {
@Override
protected void doInit() {
-
}
/**
@@ -95,31 +87,96 @@ public class TencentSmsClient extends AbstractSmsClient {
@Override
public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
String apiTemplateId, List> templateParams) throws Throwable {
- // 构建请求
+ // 1. 执行请求
+ // 参考链接 https://cloud.tencent.com/document/product/382/55981
TreeMap body = new TreeMap<>();
- String[] phones = {mobile};
- body.put("PhoneNumberSet",phones);
- body.put("SmsSdkAppId",getSdkAppId());
- body.put("SignName",properties.getSignature());
+ body.put("PhoneNumberSet", new String[]{mobile});
+ body.put("SmsSdkAppId", getSdkAppId());
+ body.put("SignName", properties.getSignature());
body.put("TemplateId",apiTemplateId);
- body.put("TemplateParamSet",ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue())));
-
- JSONObject JsonResponse = sendSmsRequest(body,"SendSms","2021-01-11","ap-guangzhou");
- SmsResponse smsResponse = getSmsSendResponse(JsonResponse);
-
- return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString());
+ body.put("TemplateParamSet", ArrayUtils.toArray(templateParams, param -> String.valueOf(param.getValue())));
+ JSONObject response = request("SendSms", body);
+ // 2. 解析请求
+ JSONObject responseResult = response.getJSONObject("Response");
+ JSONObject error = responseResult.getJSONObject("Error");
+ if (error != null) {
+ return new SmsSendRespDTO().setSuccess(false)
+ .setApiRequestId(responseResult.getStr("RequestId"))
+ .setApiCode(error.getStr("Code"))
+ .setApiMsg(error.getStr("Message"));
+ }
+ JSONObject responseData = responseResult.getJSONArray("SendStatusSet").getJSONObject(0);
+ return new SmsSendRespDTO().setSuccess(Objects.equals(API_CODE_SUCCESS, responseData.getStr("Code")))
+ .setApiRequestId(responseResult.getStr("RequestId"))
+ .setSerialNo(responseData.getStr("SerialNo"))
+ .setApiMsg(responseData.getStr("Message"));
}
- JSONObject sendSmsRequest(TreeMap body,String action,String version,String region) throws Exception {
+ @Override
+ public List parseSmsReceiveStatus(String text) {
+ JSONArray statuses = JSONUtil.parseArray(text);
+ // 字段参考
+ return convertList(statuses, status -> {
+ JSONObject statusObj = (JSONObject) status;
+ return new SmsReceiveRespDTO()
+ .setSuccess("SUCCESS".equals(statusObj.getStr("report_status"))) // 是否接收成功
+ .setErrorCode(statusObj.getStr("errmsg")) // 状态报告编码
+ .setMobile(statusObj.getStr("mobile")) // 手机号
+ .setReceiveTime(statusObj.getLocalDateTime("user_receive_time", null)) // 状态报告时间
+ .setSerialNo(statusObj.getStr("sid")); // 发送序列号
+ });
+ }
+ @Override
+ public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
+ // 1. 构建请求
+ // 参考链接 https://cloud.tencent.com/document/product/382/52067
+ TreeMap body = new TreeMap<>();
+ body.put("International", INTERNATIONAL_CHINA);
+ body.put("TemplateIdSet", new Integer[]{Integer.valueOf(apiTemplateId)});
+ JSONObject response = request("DescribeSmsTemplateList", body);
+
+ // TODO @scholar:会有请求失败的情况么?类似发送的(那块逻辑我补充了)
+ JSONObject TemplateStatusSet = response.getJSONObject("Response").getJSONArray("DescribeTemplateStatusSet").getJSONObject(0);
+ String content = TemplateStatusSet.get("TemplateContent").toString();
+ int templateStatus = Integer.parseInt(TemplateStatusSet.get("StatusCode").toString());
+ String auditReason = TemplateStatusSet.get("ReviewReply").toString();
+
+ return new SmsTemplateRespDTO().setId(apiTemplateId).setContent(content)
+ .setAuditStatus(convertSmsTemplateAuditStatus(templateStatus)).setAuditReason(auditReason);
+ }
+
+ @VisibleForTesting
+ Integer convertSmsTemplateAuditStatus(int templateStatus) {
+ switch (templateStatus) {
+ case 1: return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
+ case 0: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
+ case -1: return SmsTemplateAuditStatusEnum.FAIL.getStatus();
+ default: throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
+ }
+ }
+
+ /**
+ * 请求腾讯云短信
+ *
+ * @see 签名方法 v3
+ *
+ * @param action 请求的 API 名称
+ * @param body 请求参数
+ * @return 请求结果
+ */
+ private JSONObject request(String action, TreeMap body) throws Exception {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+ // TODO @scholar:这个 format,看看怎么写的可以简化点
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 注意时区,否则容易出错
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
+ // TODO @scholar:这个步骤,看看怎么参考阿里云 client,归类下;1. 2.1 2.2 这种
// ************* 步骤 1:拼接规范请求串 *************
+ // TODO @scholar:这个 hsot 枚举下;
String host = "sms.tencentcloudapi.com"; //APP接入地址+接口访问URI
String httpMethod = "POST"; // 请求方式
String canonicalUri = "/";
@@ -129,6 +186,7 @@ public class TencentSmsClient extends AbstractSmsClient {
+ "host:" + host + "\n" + "x-tc-action:" + action.toLowerCase() + "\n";
String signedHeaders = "content-type;host;x-tc-action";
String hashedRequestBody = sha256Hex(JSONUtil.toJsonStr(body));
+ // TODO @scholar:换行下,不然单行太长了
String canonicalRequest = httpMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody;
// ************* 步骤 2:拼接待签名字符串 *************
@@ -153,205 +211,19 @@ public class TencentSmsClient extends AbstractSmsClient {
headers.put("Host", host);
headers.put("X-TC-Action", action);
headers.put("X-TC-Timestamp", timestamp);
- headers.put("X-TC-Version", version);
- headers.put("X-TC-Region", region);
+ headers.put("X-TC-Version", VERSION);
+ headers.put("X-TC-Region", REGION);
- HttpResponse response = HttpRequest.post("https://"+host)
- .addHeaders(headers)
- .body(JSONUtil.toJsonStr(body))
- .execute();
+ String responseBody = HttpUtils.post("https://" + host, headers, JSONUtil.toJsonStr(body));
- return JSONUtil.parseObj(response.body());
+ return JSONUtil.parseObj(responseBody);
}
- public static byte[] hmac256(byte[] key, String msg) throws Exception {
+ // TODO @scholar:使用 hutool 简化下
+ private static byte[] hmac256(byte[] key, String msg) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
mac.init(secretKeySpec);
return mac.doFinal(msg.getBytes(StandardCharsets.UTF_8));
}
-
- private SmsResponse getSmsSendResponse(JSONObject resJson) {
- SmsResponse smsResponse = new SmsResponse();
- JSONArray statusJson =resJson.getJSONObject("Response").getJSONArray("SendStatusSet");
- smsResponse.setSuccess("Ok".equals(statusJson.getJSONObject(0).getStr("Code")));
- smsResponse.setData(resJson);
- return smsResponse;
- }
-
- @Override
- public List parseSmsReceiveStatus(String text) {
- List callback = JsonUtils.parseArray(text, SmsReceiveStatus.class);
- return convertList(callback, status -> new SmsReceiveRespDTO()
- .setSuccess(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus()))
- .setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription())
- .setMobile(status.getMobile()).setReceiveTime(status.getReceiveTime())
- .setSerialNo(status.getSerialNo()).setLogId(status.getSessionContext().getLogId()));
- }
-
- @Override
- public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
-
- // 构建请求
- TreeMap body = new TreeMap<>();
- body.put("International",0);
- Integer[] templateIds = {Integer.valueOf(apiTemplateId)};
- body.put("TemplateIdSet",templateIds);
-
- JSONObject JsonResponse = sendSmsRequest(body,"DescribeSmsTemplateList","2021-01-11","ap-guangzhou");
- QuerySmsTemplateResponse smsTemplateResponse = getSmsTemplateResponse(JsonResponse);
- String templateId = Integer.toString(smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getTemplateId());
- String content = smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getTemplateContent();
- Integer templateStatus = smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getStatusCode();
- String auditReason = smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getReviewReply();
-
- return new SmsTemplateRespDTO().setId(templateId).setContent(content)
- .setAuditStatus(convertSmsTemplateAuditStatus(templateStatus)).setAuditReason(auditReason);
- }
-
- private QuerySmsTemplateResponse getSmsTemplateResponse(JSONObject resJson) {
-
- QuerySmsTemplateResponse smsTemplateResponse = new QuerySmsTemplateResponse();
-
- smsTemplateResponse.setRequestId(resJson.getJSONObject("Response").getStr("RequestId"));
-
- smsTemplateResponse.setDescribeTemplateStatusSet(new ArrayList<>());
-
- QuerySmsTemplateResponse.TemplateInfo templateInfo = new QuerySmsTemplateResponse.TemplateInfo();
-
- Object statusObject = resJson.getJSONObject("Response").getJSONArray("DescribeTemplateStatusSet").get(0);
-
- JSONObject statusJSON = new JSONObject(statusObject);
-
- templateInfo.setTemplateContent(statusJSON.get("TemplateContent").toString());
-
- templateInfo.setStatusCode(Integer.parseInt(statusJSON.get("StatusCode").toString()));
-
- templateInfo.setReviewReply(statusJSON.get("ReviewReply").toString());
-
- templateInfo.setTemplateId(Integer.parseInt(statusJSON.get("TemplateId").toString()));
-
- smsTemplateResponse.getDescribeTemplateStatusSet().add(templateInfo);
-
- return smsTemplateResponse;
- }
-
- @VisibleForTesting
- Integer convertSmsTemplateAuditStatus(int templateStatus) {
- switch (templateStatus) {
- case 1: return SmsTemplateAuditStatusEnum.CHECKING.getStatus();
- case 0: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();
- case -1: return SmsTemplateAuditStatusEnum.FAIL.getStatus();
- default: throw new IllegalArgumentException(String.format("未知审核状态(%d)", templateStatus));
- }
- }
-
- @Data
- public static class SmsResponse {
-
- /**
- * 是否成功
- */
- private boolean success;
-
- /**
- * 厂商原返回体
- */
- private Object data;
-
- }
-
-
- /**
- * 类名: QuerySmsTemplateResponse
- *
说明: sms模板查询返回信息
- *
- * @author :scholar
- * 2024/07/17 0:25
- **/
- @Data
- public static class QuerySmsTemplateResponse {
- private List DescribeTemplateStatusSet;
- private String RequestId;
- @Data
- static class TemplateInfo {
- private String TemplateName;
- private Integer TemplateId;
- private Integer International;
- private String ReviewReply;
- private long CreateTime;
- private String TemplateContent;
- private Integer StatusCode;
- }
- }
-
- @Data
- private static class SmsReceiveStatus {
-
- /**
- * 短信接受成功 code
- */
- public static final String SUCCESS_CODE = "SUCCESS";
-
- /**
- * 用户实际接收到短信的时间
- */
- @JsonProperty("user_receive_time")
- @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
- private LocalDateTime receiveTime;
-
- /**
- * 国家(或地区)码
- */
- @JsonProperty("nationcode")
- private String nationCode;
-
- /**
- * 手机号码
- */
- private String mobile;
-
- /**
- * 实际是否收到短信接收状态,SUCCESS(成功)、FAIL(失败)
- */
- @JsonProperty("report_status")
- private String status;
-
- /**
- * 用户接收短信状态码错误信息
- */
- @JsonProperty("errmsg")
- private String errCode;
-
- /**
- * 用户接收短信状态描述
- */
- @JsonProperty("description")
- private String description;
-
- /**
- * 本次发送标识 ID(与发送接口返回的SerialNo对应)
- */
- @JsonProperty("sid")
- private String serialNo;
-
- /**
- * 用户的 session 内容(与发送接口的请求参数 SessionContext 一致)
- */
- @JsonProperty("ext")
- private SessionContext sessionContext;
-
- }
-
- @VisibleForTesting
- @Data
- static class SessionContext {
-
- /**
- * 发送短信记录id
- */
- private Long logId;
-
- }
-
-}
+}
\ No newline at end of file
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java
index c6e015d817..093060e844 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java
@@ -38,15 +38,6 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
@InjectMocks
private final AliyunSmsClient smsClient = new AliyunSmsClient(properties);
- @Test
- public void testDoInit() {
- // 准备参数
- // mock 方法
-
- // 调用
- smsClient.doInit();
- }
-
@Test
public void tesSendSms_success() throws Throwable {
try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java
index 5fc3631cd6..6eb22af1b8 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java
@@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
-import cn.hutool.core.collection.ListUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
@@ -18,24 +17,6 @@ import java.util.List;
*/
public class SmsClientTests {
- @Test
- @Disabled
- public void testHuaweiSmsClient_sendSms() throws Throwable {
- SmsChannelProperties properties = new SmsChannelProperties()
- .setApiKey("123")
- .setApiSecret("456");
- HuaweiSmsClient client = new HuaweiSmsClient(properties);
- // 准备参数
- Long sendLogId = System.currentTimeMillis();
- String mobile = "15601691323";
- String apiTemplateId = "xx test01";
- List> templateParams = ListUtil.of(new KeyValue<>("code", "1024"));
- // 调用
- SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
- // 打印结果
- System.out.println(smsSendRespDTO);
- }
-
// ========== 阿里云 ==========
@Test
@@ -59,14 +40,14 @@ public class SmsClientTests {
SmsChannelProperties properties = new SmsChannelProperties()
.setApiKey("LTAI5tAicJAxaSFiZuGGeXHR")
.setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz")
- .setSignature("Ballcat");
+ .setSignature("runpu");
AliyunSmsClient client = new AliyunSmsClient(properties);
// 准备参数
Long sendLogId = System.currentTimeMillis();
- String mobile = "173213154791";
+ String mobile = "15601691323";
String apiTemplateId = "SMS_207945135";
// 调用
- SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, ListUtil.of(new KeyValue<>("code", "1024")));
+ SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024")));
// 打印结果
System.out.println(sendRespDTO);
}
@@ -100,4 +81,62 @@ public class SmsClientTests {
System.out.println(statuses);
}
+ // ========== 腾讯云 ==========
+
+ @Test
+ @Disabled
+ public void testTencentSmsClient_sendSms() throws Throwable {
+ SmsChannelProperties properties = new SmsChannelProperties()
+ .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR 1428926523")
+ .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz")
+ .setSignature("芋道源码");
+ TencentSmsClient client = new TencentSmsClient(properties);
+ // 准备参数
+ Long sendLogId = System.currentTimeMillis();
+ String mobile = "15601691323";
+ String apiTemplateId = "2136358";
+ // 调用
+ SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024")));
+ // 打印结果
+ System.out.println(sendRespDTO);
+ }
+
+ @Test
+ @Disabled
+ public void testTencentSmsClient_getSmsTemplate() throws Throwable {
+ SmsChannelProperties properties = new SmsChannelProperties()
+ .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR 1428926523")
+ .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz")
+ .setSignature("芋道源码");
+ TencentSmsClient client = new TencentSmsClient(properties);
+ // 准备参数
+ String apiTemplateId = "2136358";
+ // 调用
+ SmsTemplateRespDTO template = client.getSmsTemplate(apiTemplateId);
+ // 打印结果
+ System.out.println(template);
+ }
+
+ // ========== 华为云 ==========
+
+ @Test
+ @Disabled
+ public void testHuaweiSmsClient_sendSms() throws Throwable {
+ SmsChannelProperties properties = new SmsChannelProperties()
+ .setApiKey("123")
+ .setApiSecret("456")
+ .setSignature("runpu");
+ HuaweiSmsClient client = new HuaweiSmsClient(properties);
+ // 准备参数
+ Long sendLogId = System.currentTimeMillis();
+ String mobile = "15601691323";
+ String apiTemplateId = "xx test01";
+ List> templateParams = List.of(new KeyValue<>("code", "1024"));
+ // 调用
+ SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
+ // 打印结果
+ System.out.println(smsSendRespDTO);
+ }
+
}
+
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java
index 6d621e1709..b25540b44f 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java
@@ -1,22 +1,28 @@
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
-import cn.hutool.core.util.ReflectUtil;
+import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
-import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
+import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
+import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
+import com.google.common.collect.Lists;
+
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
-import org.mockito.Mock;
+import org.mockito.MockedStatic;
import java.time.LocalDateTime;
import java.util.List;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mockStatic;
-// TODO @芋艿:补全单测
/**
* {@link TencentSmsClient} 的单元测试
*
@@ -32,115 +38,85 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
@InjectMocks
private TencentSmsClient smsClient = new TencentSmsClient(properties);
- @Mock
- private SmsClient client;
-
@Test
- public void testDoInit() {
- // 准备参数
- // mock 方法
+ public void testDoSendSms_success() throws Throwable {
+ try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
+ // 准备参数
+ Long sendLogId = randomLongId();
+ String mobile = randomString();
+ String apiTemplateId = randomString();
+ List> templateParams = Lists.newArrayList(
+ new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
+ // mock 方法
+ httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
+ .thenReturn("{\n" +
+ " \"Response\": {\n" +
+ " \"SendStatusSet\": [\n" +
+ " {\n" +
+ " \"SerialNo\": \"5000:1045710669157053657849499619\",\n" +
+ " \"PhoneNumber\": \"+8618511122233\",\n" +
+ " \"Fee\": 1,\n" +
+ " \"SessionContext\": \"test\",\n" +
+ " \"Code\": \"Ok\",\n" +
+ " \"Message\": \"send success\",\n" +
+ " \"IsoCode\": \"CN\"\n" +
+ " },\n" +
+ " ],\n" +
+ " \"RequestId\": \"a0aabda6-cf91-4f3e-a81f-9198114a2279\"\n" +
+ " }\n" +
+ "}");
- // 调用
- smsClient.doInit();
- // 断言
- assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
+ // 调用
+ SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
+ apiTemplateId, templateParams);
+ // 断言
+ assertTrue(result.getSuccess());
+ assertEquals("5000:1045710669157053657849499619", result.getSerialNo());
+ assertEquals("a0aabda6-cf91-4f3e-a81f-9198114a2279", result.getApiRequestId());
+ assertEquals("send success", result.getApiMsg());
+ }
}
@Test
- public void testRefresh() {
- // 准备参数
- SmsChannelProperties p = new SmsChannelProperties()
- .setApiKey(randomString() + " " + randomString()) // 随机一个 apiKey,避免构建报错
- .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
- .setSignature("芋道源码");
- // 调用
- smsClient.refresh(p);
- // 断言
- assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
+ public void testDoSendSms_fail() throws Throwable {
+ try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
+ // 准备参数
+ Long sendLogId = randomLongId();
+ String mobile = randomString();
+ String apiTemplateId = randomString();
+ List> templateParams = Lists.newArrayList(
+ new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
+
+ // mock 方法
+ httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
+ .thenReturn("{\n" +
+ " \"Response\": {\n" +
+ " \"SendStatusSet\": [\n" +
+ " {\n" +
+ " \"SerialNo\": \"5000:1045710669157053657849499619\",\n" +
+ " \"PhoneNumber\": \"+8618511122233\",\n" +
+ " \"Fee\": 1,\n" +
+ " \"SessionContext\": \"test\",\n" +
+ " \"Code\": \"ERROR\",\n" +
+ " \"Message\": \"send success\",\n" +
+ " \"IsoCode\": \"CN\"\n" +
+ " },\n" +
+ " ],\n" +
+ " \"RequestId\": \"a0aabda6-cf91-4f3e-a81f-9198114a2279\"\n" +
+ " }\n" +
+ "}");
+
+ // 调用
+ SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
+ apiTemplateId, templateParams);
+ // 断言
+ assertFalse(result.getSuccess());
+ assertEquals("5000:1045710669157053657849499619", result.getSerialNo());
+ assertEquals("a0aabda6-cf91-4f3e-a81f-9198114a2279", result.getApiRequestId());
+ assertEquals("send success", result.getApiMsg());
+ }
}
-// @Test
-// public void testDoSendSms_success() throws Throwable {
-// // 准备参数
-// Long sendLogId = randomLongId();
-// String mobile = randomString();
-// String apiTemplateId = randomString();
-// List> templateParams = Lists.newArrayList(
-// new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
-// String requestId = randomString();
-// String serialNo = randomString();
-// // mock 方法
-// SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
-// o.setRequestId(requestId);
-// SendStatus[] sendStatuses = new SendStatus[1];
-// o.setSendStatusSet(sendStatuses);
-// SendStatus sendStatus = new SendStatus();
-// sendStatuses[0] = sendStatus;
-// sendStatus.setCode(TencentSmsClient.API_CODE_SUCCESS);
-// sendStatus.setMessage("send success");
-// sendStatus.setSerialNo(serialNo);
-// });
-// when(client.SendSms(argThat(request -> {
-// assertEquals(mobile, request.getPhoneNumberSet()[0]);
-// assertEquals(properties.getSignature(), request.getSignName());
-// assertEquals(apiTemplateId, request.getTemplateId());
-// assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
-// toJsonString(request.getTemplateParamSet()));
-// assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
-// return true;
-// }))).thenReturn(response);
-//
-// // 调用
-// SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
-// // 断言
-// assertTrue(result.getSuccess());
-// assertEquals(response.getRequestId(), result.getApiRequestId());
-// assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
-// assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
-// assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
-// }
-
-// @Test
-// public void testDoSendSms_fail() throws Throwable {
-// // 准备参数
-// Long sendLogId = randomLongId();
-// String mobile = randomString();
-// String apiTemplateId = randomString();
-// List> templateParams = Lists.newArrayList(
-// new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
-// String requestId = randomString();
-// String serialNo = randomString();
-// // mock 方法
-// SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
-// o.setRequestId(requestId);
-// SendStatus[] sendStatuses = new SendStatus[1];
-// o.setSendStatusSet(sendStatuses);
-// SendStatus sendStatus = new SendStatus();
-// sendStatuses[0] = sendStatus;
-// sendStatus.setCode("ERROR");
-// sendStatus.setMessage("send success");
-// sendStatus.setSerialNo(serialNo);
-// });
-// when(client.SendSms(argThat(request -> {
-// assertEquals(mobile, request.getPhoneNumberSet()[0]);
-// assertEquals(properties.getSignature(), request.getSignName());
-// assertEquals(apiTemplateId, request.getTemplateId());
-// assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
-// toJsonString(request.getTemplateParamSet()));
-// assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
-// return true;
-// }))).thenReturn(response);
-//
-// // 调用
-// SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
-// // 断言
-// assertFalse(result.getSuccess());
-// assertEquals(response.getRequestId(), result.getApiRequestId());
-// assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
-// assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
-// assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
-// }
-
@Test
public void testParseSmsReceiveStatus() {
// 准备参数
@@ -156,7 +132,6 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
" \"ext\": {\"logId\":\"67890\"}\n" +
" }\n" +
"]";
- // mock 方法
// 调用
List statuses = smsClient.parseSmsReceiveStatus(text);
@@ -164,42 +139,44 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
assertEquals(1, statuses.size());
assertTrue(statuses.get(0).getSuccess());
assertEquals("DELIVRD", statuses.get(0).getErrorCode());
- assertEquals("用户短信送达成功", statuses.get(0).getErrorMsg());
assertEquals("13900000001", statuses.get(0).getMobile());
assertEquals(LocalDateTime.of(2015, 10, 17, 8, 3, 4), statuses.get(0).getReceiveTime());
assertEquals("12345", statuses.get(0).getSerialNo());
- assertEquals(67890L, statuses.get(0).getLogId());
}
-// @Test
-// public void testGetSmsTemplate() throws Throwable {
-// // 准备参数
-// Long apiTemplateId = randomLongId();
-// String requestId = randomString();
-//
-// // mock 方法
-// DescribeSmsTemplateListResponse response = randomPojo(DescribeSmsTemplateListResponse.class, o -> {
-// DescribeTemplateListStatus[] describeTemplateListStatuses = new DescribeTemplateListStatus[1];
-// DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
-// templateStatus.setTemplateId(apiTemplateId);
-// templateStatus.setStatusCode(0L);// 设置模板通过
-// describeTemplateListStatuses[0] = templateStatus;
-// o.setDescribeTemplateStatusSet(describeTemplateListStatuses);
-// o.setRequestId(requestId);
-// });
-// when(client.DescribeSmsTemplateList(argThat(request -> {
-// assertEquals(apiTemplateId, request.getTemplateIdSet()[0]);
-// return true;
-// }))).thenReturn(response);
-//
-// // 调用
-// SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId.toString());
-// // 断言
-// assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getId());
-// assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getContent());
-// assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
-// assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getAuditReason());
-// }
+ @Test
+ public void testGetSmsTemplate() throws Throwable {
+ try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
+ // 准备参数
+ String apiTemplateId = "1122";
+
+ // mock 方法
+ httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
+ .thenReturn("{ \"Response\": {\n" +
+ " \"DescribeTemplateStatusSet\": [\n" +
+ " {\n" +
+ " \"TemplateName\": \"验证码\",\n" +
+ " \"TemplateId\": 1122,\n" +
+ " \"International\": 0,\n" +
+ " \"ReviewReply\": \"审批备注\",\n" +
+ " \"CreateTime\": 1617379200,\n" +
+ " \"TemplateContent\": \"您的验证码是{1}\",\n" +
+ " \"StatusCode\": 0\n" +
+ " },\n" +
+ " \n" +
+ " ],\n" +
+ " \"RequestId\": \"f36e4f00-605e-49b1-ad0d-bfaba81c7325\"\n" +
+ " }}");
+
+ // 调用
+ SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId);
+ // 断言
+ assertEquals("1122", result.getId());
+ assertEquals("您的验证码是{1}", result.getContent());
+ assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
+ assertEquals("审批备注", result.getAuditReason());
+ }
+ }
@Test
public void testConvertSmsTemplateAuditStatus() {
diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml
index 80568517fe..7a42906f9d 100644
--- a/yudao-server/src/main/resources/application.yaml
+++ b/yudao-server/src/main/resources/application.yaml
@@ -147,14 +147,22 @@ spring:
spring:
ai:
+ vectorstore: # 向量存储
+ redis:
+ index: default-index
+ prefix: "default:"
qianfan: # 文心一言
api-key: x0cuLZ7XsaTCU08vuJWO87Lg
secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK
zhipuai: # 智谱 AI
api-key: 32f84543e54eee31f8d56b2bd6020573.3vh9idLJZ2ZhxDEs
- openai:
+ openai: # OpenAI 官方
api-key: sk-yzKea6d8e8212c3bdd99f9f44ced1cae37c097e5aa3BTS7z
base-url: https://api.gptsapi.net
+ azure: # OpenAI 微软
+ openai:
+ endpoint: https://eastusprejade.openai.azure.com
+ api-key: xxx
ollama:
base-url: http://127.0.0.1:11434
chat:
@@ -301,7 +309,6 @@ yudao:
end-code: 9999 # 这里配置 9999 的原因是,测试方便。
trade:
order:
- app-id: 1 # 商户编号
pay-expire-time: 2h # 支付的过期时间
receive-expire-time: 14d # 收货的过期时间
comment-expire-time: 7d # 评论的过期时间