diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql index ccc8dfcde0..274e17a5eb 100644 --- a/sql/mysql/ruoyi-vue-pro.sql +++ b/sql/mysql/ruoyi-vue-pro.sql @@ -1961,7 +1961,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2793, '写作管理', '', 2, 13, 2760, 'write', 'fa:bookmark-o', 'ai/write/manager/index.vue', 'AiWriteManager', 0, b'1', b'1', b'1', '', '2024-07-10 13:24:34', '1', '2024-07-10 21:31:59', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2794, 'AI 写作查询', 'ai:write:query', 3, 1, 2793, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-07-10 13:24:34', '', '2024-07-10 13:24:34', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2795, 'AI 写作删除', 'ai:write:delete', 3, 4, 2793, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-07-10 13:24:34', '', '2024-07-10 13:24:34', b'0'); -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2796, 'AI 音乐', '', 2, 4, 2758, 'music', 'fa:music', 'ai/music/index/index.vue', 'AiWrite', 0, b'1', b'1', b'1', '1', '2024-07-17 09:21:12', '1', '2024-07-17 09:36:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2796, 'AI 音乐', '', 2, 4, 2758, 'music', 'fa:music', 'ai/music/index/index.vue', 'AiMusic', 0, b'1', b'1', b'1', '1', '2024-07-17 09:21:12', '1', '2024-07-17 09:36:12', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2797, '客服中心', '', 2, 100, 2362, 'kefu', 'fa-solid:user-alt', 'mall/promotion/kefu/index', 'KeFu', 0, b'1', b'1', b'1', '1', '2024-07-17 23:49:05', '1', '2024-07-17 23:49:16', b'0'); COMMIT; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java index 313a24b1fb..c9b04dc1ef 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java @@ -188,7 +188,7 @@ private static String buildClientCacheKey(Class clazz, Object... params) { * 可参考 {@link TongYiAutoConfiguration#tongYiChatClient(Generation, TongYiChatProperties, TongYiConnectionProperties)} */ private static TongYiChatModel buildTongYiChatModel(String key) { - Generation generation = SpringUtil.getBean(Generation.class); + com.alibaba.dashscope.aigc.generation.Generation generation = SpringUtil.getBean(Generation.class); TongYiChatProperties chatOptions = SpringUtil.getBean(TongYiChatProperties.class); // TODO @芋艿:貌似 apiKey 是全局唯一的???得测试下 // TODO @芋艿:貌似阿里云不是增量返回的 diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java index 6eca705874..d688981db3 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java @@ -148,5 +148,4 @@ public Integer calculateVipPrice(Integer price, MemberLevelRespDTO memberLevel) return price - newPrice; } - // TODO 芋艿:商品的浏览记录; } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/brand/ProductBrandDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/brand/ProductBrandDO.java index 9775f36a5d..e2178d5c4c 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/brand/ProductBrandDO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/brand/ProductBrandDO.java @@ -48,6 +48,4 @@ public class ProductBrandDO extends BaseDO { */ private Integer status; - // TODO 芋艿:firstLetter 首字母 - } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java index ea9528d15d..267756a506 100755 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java @@ -130,11 +130,5 @@ public static class Property { } - // TODO 芋艿:integral from y - // TODO 芋艿:pinkPrice from y - // TODO 芋艿:seckillPrice from y - // TODO 芋艿:pinkStock from y - // TODO 芋艿:seckillStock from y - } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java index 7901eb1dbb..8a3ea838e6 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java @@ -10,23 +10,20 @@ import cn.iocoder.yudao.module.promotion.controller.app.combination.vo.record.AppCombinationRecordSummaryRespVO; import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert; import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO; -import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum; import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService; -import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi; 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.context.annotation.Lazy; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import javax.annotation.Resource; -import javax.validation.Valid; -import javax.validation.constraints.Max; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -43,9 +40,6 @@ public class AppCombinationRecordController { @Resource private CombinationRecordService combinationRecordService; - @Resource - @Lazy - private TradeOrderApi tradeOrderApi; @GetMapping("/get-summary") @Operation(summary = "获得拼团记录的概要信息", description = "用于小程序首页") @@ -117,26 +111,4 @@ public CommonResult getCombinationRecordDetail return success(CombinationActivityConvert.INSTANCE.convert(getLoginUserId(), headRecord, memberRecords)); } - @GetMapping("/cancel") - @Operation(summary = "取消拼团") - @Parameter(name = "id", description = "拼团记录编号", required = true, example = "1024") - public CommonResult cancelCombinationRecord(@RequestParam("id") Long id) { - Long userId = getLoginUserId(); - // 1、查找这条拼团记录 - CombinationRecordDO record = combinationRecordService.getCombinationRecordByIdAndUser(userId, id); - if (record == null) { - return success(Boolean.FALSE); - } - // 1.1、需要先校验拼团记录未完成; - if (!CombinationRecordStatusEnum.isInProgress(record.getStatus())) { - return success(Boolean.FALSE); - } - - // 2. 取消已支付的订单 - tradeOrderApi.cancelPaidOrder(userId, record.getOrderId()); - // 3. 取消拼团记录 - combinationRecordService.cancelCombinationRecord(userId, record.getId(), record.getHeadId()); - return success(Boolean.TRUE); - } - } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java index ca40e76029..0b68609c9d 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java @@ -72,7 +72,6 @@ default int updateStockIncr(Long id, int count) { default PageResult selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) { return selectPage(pageReqVO, new LambdaQueryWrapperX() .eqIfPresent(SeckillActivityDO::getStatus, status) - // TODO 芋艿:对 find in set 的想法; .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0")); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java index 55f5b2ebc8..d5b53e1fe3 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java @@ -139,24 +139,6 @@ Map getCombinationRecordCountMapByActivity(Collection activ @Nullable Integer status, @Nullable Long headId); - /** - * 获取拼团记录 - * - * @param userId 用户编号 - * @param id 拼团记录编号 - * @return 拼团记录 - */ - CombinationRecordDO getCombinationRecordByIdAndUser(Long userId, Long id); - - /** - * 取消拼团 - * - * @param userId 用户编号 - * @param id 拼团记录编号 - * @param headId 团长编号 - */ - void cancelCombinationRecord(Long userId, Long id, Long headId); - /** * 处理过期拼团 * diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java index 09319064b5..b252d27772 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java @@ -37,7 +37,10 @@ import javax.annotation.Nullable; import javax.annotation.Resource; import java.time.LocalDateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; @@ -69,7 +72,6 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { private ProductSpuApi productSpuApi; @Resource private ProductSkuApi productSkuApi; - @Resource @Lazy // 延迟加载,避免循环依赖 private TradeOrderApi tradeOrderApi; @@ -289,61 +291,6 @@ public Map getCombinationRecordCountMapByActivity(Collection updateRecords = new ArrayList<>(); - // 如果它是团长,则顺序(下单时间)继承 - if (Objects.equals(headId, CombinationRecordDO.HEAD_ID_GROUP)) { // 情况一:团长 - // 团员 - List list = getCombinationRecordListByHeadId(id); - if (CollUtil.isEmpty(list)) { - return; - } - // 按照创建时间升序排序 - list.sort(Comparator.comparing(CombinationRecordDO::getCreateTime)); // 影响原 list - CombinationRecordDO newHead = list.get(0); // 新团长继位 - list.forEach(item -> { - CombinationRecordDO recordDO = new CombinationRecordDO(); - recordDO.setId(item.getId()); - if (ObjUtil.equal(item.getId(), newHead.getId())) { // 新团长 - recordDO.setHeadId(CombinationRecordDO.HEAD_ID_GROUP); - } else { - recordDO.setHeadId(newHead.getId()); - } - recordDO.setUserCount(list.size()); - updateRecords.add(recordDO); - }); - } else { // 情况二:团员 - // 团长 - CombinationRecordDO recordHead = combinationRecordMapper.selectById(headId); - // 团员 - List records = getCombinationRecordListByHeadId(headId); - if (CollUtil.isEmpty(records)) { - return; - } - records.add(recordHead); // 加入团长,团长数据也需要更新 - records.forEach(item -> { - CombinationRecordDO recordDO = new CombinationRecordDO(); - recordDO.setId(item.getId()); - recordDO.setUserCount(records.size()); - updateRecords.add(recordDO); - }); - } - - // 更新拼团记录 - combinationRecordMapper.updateBatch(updateRecords); - } - @Override public KeyValue expireCombinationRecord() { // 1. 获取所有正在进行中的过期的父拼团 diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/product/ProductStatisticsJob.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/product/ProductStatisticsJob.java index 4d4182cecb..e1397fa6d0 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/product/ProductStatisticsJob.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/product/ProductStatisticsJob.java @@ -7,12 +7,9 @@ import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; import cn.iocoder.yudao.module.statistics.service.product.ProductStatisticsService; import com.xxl.job.core.handler.annotation.XxlJob; +import jakarta.annotation.Resource; import org.springframework.stereotype.Component; -import javax.annotation.Resource; - -// TODO 芋艿:缺个 Job 的配置;等和 Product 一起配置 - /** * 商品统计 Job * diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/trade/TradeStatisticsJob.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/trade/TradeStatisticsJob.java index 88e9f0c792..adf675b607 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/trade/TradeStatisticsJob.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/trade/TradeStatisticsJob.java @@ -11,7 +11,6 @@ import javax.annotation.Resource; -// TODO 芋艿:缺个 Job 的配置;等和 Product 一起配置 /** * 交易统计 Job * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java index f74c84b8fe..4b8756c7be 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java @@ -21,6 +21,9 @@ @ToString(callSuper = true) public class AfterSalePageReqVO extends PageParam { + @Schema(description = "用户编号", example = "1024") + private Long userId; + @Schema(description = "售后流水号", example = "202211190847450020500077") private String no; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java index 9d788137b1..d91969481a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java @@ -101,7 +101,7 @@ default ProductSkuUpdateStockReqDTO convertNegative(List list) default PayOrderCreateReqDTO convert(TradeOrderDO order, List orderItems, TradeOrderProperties orderProperties) { PayOrderCreateReqDTO createReqDTO = new PayOrderCreateReqDTO() - .setAppId(orderProperties.getAppId()).setUserIp(order.getUserIp()); + .setAppKey(orderProperties.getPayAppKey()).setUserIp(order.getUserIp()); // 商户相关字段 createReqDTO.setMerchantOrderId(String.valueOf(order.getId())); String subject = orderItems.get(0).getSpuName(); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java index 68a09a82a2..341dabc45e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java @@ -16,6 +16,7 @@ public interface AfterSaleMapper extends BaseMapperX { default PageResult selectPage(AfterSalePageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(AfterSaleDO::getUserId, reqVO.getUserId()) .likeIfPresent(AfterSaleDO::getNo, reqVO.getNo()) .eqIfPresent(AfterSaleDO::getStatus, reqVO.getStatus()) .eqIfPresent(AfterSaleDO::getType, reqVO.getType()) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/package-info.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/package-info.java deleted file mode 100644 index 37e0ba7d60..0000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * TODO 占位 - */ -package cn.iocoder.yudao.module.trade.dal.mysql; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderConfig.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderConfig.java index 715169275f..8d6ebea155 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderConfig.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderConfig.java @@ -3,7 +3,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; -// TODO @LeeYan9: 可以直接给 TradeOrderProperties 一个 @Component生效哈 /** * @author LeeYan9 * @since 2022-09-15 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderProperties.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderProperties.java index 786c0004bd..0d7b271d91 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderProperties.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/order/config/TradeOrderProperties.java @@ -1,10 +1,12 @@ package cn.iocoder.yudao.module.trade.framework.order.config; +import jakarta.validation.constraints.NotEmpty; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.validation.annotation.Validated; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; + import java.time.Duration; /** @@ -18,11 +20,15 @@ @Validated public class TradeOrderProperties { + private static final String PAY_APP_KEY_DEFAULT = "mall"; + /** - * 应用编号 + * 支付应用标识 + * + * 在 pay 模块的 [支付管理 -> 应用信息] 里添加 */ - @NotNull(message = "应用编号不能为空") - private Long appId; + @NotEmpty(message = "Pay 应用标识不能为空") + private String payAppKey = PAY_APP_KEY_DEFAULT; /** * 支付超时时间 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java index 4fc5d7ae17..78a3ba0149 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java @@ -125,7 +125,6 @@ public BrokerageUserDO getBindBrokerageUser(Long id) { @Override public BrokerageUserDO getOrCreateBrokerageUser(Long id) { - // TODO @芋艿:这块优化下;统一到注册时处理; BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(id); // 特殊:人人分销的情况下,如果分销人为空则创建分销人 if (brokerageUser == null && ObjUtil.equal(BrokerageEnabledConditionEnum.ALL.getCondition(), diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index 358bc4b8d3..68b7e60ac6 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -855,12 +855,14 @@ public void updateOrderCombinationInfo(Long orderId, Long activityId, Long combi @Override @Transactional(rollbackFor = Exception.class) public void cancelPaidOrder(Long userId, Long orderId) { - // TODO 芋艿:这里实现要优化下; + // TODO @puhui999:需要校验状态;已支付的情况下,才可以。 TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId); if (order == null) { throw exception(ORDER_NOT_FOUND); } cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL); + + // TODO @puhui999:需要退款 } /** diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java index 2862012af3..891f1e0dc8 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.trade.service.price.calculator; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; @@ -61,7 +62,7 @@ public static TradePriceCalculateRespBO buildCalculateResp(TradePriceCalculateRe orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId()) .setDeliveryTemplateId(spu.getDeliveryTemplateId()) .setGivePoint(spu.getGiveIntegral()).setUsePoint(0); - if (orderItem.getPicUrl() == null) { + if (StrUtil.isBlank(orderItem.getPicUrl())) { orderItem.setPicUrl(spu.getPicUrl()); } }); @@ -240,7 +241,7 @@ public static List dividePrice(List { default PageResult selectPage(PayAppPageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .likeIfPresent(PayAppDO::getName, reqVO.getName()) + .likeIfPresent(PayAppDO::getAppKey, reqVO.getAppKey()) .eqIfPresent(PayAppDO::getStatus, reqVO.getStatus()) .betweenIfPresent(PayAppDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(PayAppDO::getId)); } + default PayAppDO selectByAppKey(String appKey) { + return selectOne(PayAppDO::getAppKey, appKey); + } + } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java index d422b3528b..6fd496b3a6 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java @@ -15,6 +15,8 @@ public class PayProperties { private static final String ORDER_NO_PREFIX = "P"; private static final String REFUND_NO_PREFIX = "R"; + private static final String WALLET_PAY_APP_KEY_DEFAULT = "wallet"; + /** * 支付回调地址 * @@ -49,4 +51,10 @@ public class PayProperties { @NotEmpty(message = "退款订单 no 的前缀不能为空") private String refundNoPrefix = REFUND_NO_PREFIX; + /** + * 钱包支付应用 AppKey + */ + @NotEmpty(message = "钱包支付应用 AppKey 不能为空") + private String walletPayAppKey = WALLET_PAY_APP_KEY_DEFAULT; + } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java index 4f9e289d6c..2808b6323d 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java @@ -88,13 +88,13 @@ public interface PayAppService { * @return 商户 Map */ default Map getAppMap(Collection ids) { - List list = getAppList(ids); + List list = getAppList(ids); return CollectionUtils.convertMap(list, PayAppDO::getId); } /** * 支付应用的合法性 - * + *

* 如果不合法,抛出 {@link ServiceException} 业务异常 * * @param id 应用编号 @@ -102,4 +102,14 @@ default Map getAppMap(Collection ids) { */ 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..afe111c772 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 @@ -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 Long createApp(PayAppCreateReqVO createReqVO) { 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 List getAppList(Collection ids) { @Override public List getAppList() { - return appMapper.selectList(); + return appMapper.selectList(); } @Override @@ -110,14 +130,30 @@ public PageResult getAppPage(PayAppPageReqVO pageReqVO) { } @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 4821e92db7..15101d30c3 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 @@ -18,7 +18,7 @@ import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.time.Duration; import java.time.LocalDateTime; import java.util.HashMap; @@ -43,11 +43,11 @@ 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 Long createDemoOrder(Long userId, PayDemoOrderCreateReqVO createReqVO) { // 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)))).getCheckedData(); // 支付的过期时间 @@ -190,7 +190,7 @@ public void refundDemoOrder(Long id, String userIp) { 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())).getCheckedData();// 价格信息 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..31c1f8b55a 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 @@ -37,7 +37,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import javax.annotation.Resource; +import jakarta.annotation.Resource; import java.time.LocalDateTime; import java.util.Collection; import java.util.Collections; @@ -111,11 +111,11 @@ public List getOrderList(PayOrderExportReqVO exportReqVO) { @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..5ade42f0bb 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 @@ -93,9 +93,9 @@ public List getRefundList(PayRefundExportReqVO exportReqVO) { @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 Long createPayRefund(PayRefundCreateReqDTO reqDTO) { // 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 Long createPayRefund(PayRefundCreateReqDTO reqDTO) { * @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 @@ private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO) { } // 校验金额,退款金额不能大于原定的金额 - 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 void notifyRefund(Long channelId, PayRefundRespDTO notify) { * 通知并更新订单的退款结果 * * @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..c5e74b5e06 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 @@ -79,16 +79,16 @@ public PayTransferDO createTransfer(PayTransferCreateReqVO reqVO, String userIp) @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 Long createTransfer(PayTransferCreateReqDTO reqDTO) { 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.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 @@ @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 PayWalletRechargeDO createWalletRecharge(Long userId, Integer userType, S // 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 void refundWalletRecharge(Long id, String userIp) { 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 819d99cd13..8cd7d6a065 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; @@ -52,6 +53,16 @@ public PageResult getWalletTransactionPage(Long userId, @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 void testGetOrderList() { 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 void testCreateOrder_success() { 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 void testGetRefundList() { @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 void testCreateRefund_orderClosed() { 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 @@ private void testCreateRefund_orderWaitingOrClosed(Integer status) { 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 void testCreateRefund_refundPriceExceed() { 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 void testCreateRefund_orderHasRefunding() { 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 void testCreateRefund_channelNotFound() { 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 void testCreateRefund_refundExists() { 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 void testCreateRefund_invokeSuccess() { // 准备参数 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 4048e8177f..9980fb71e2 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 @@ -4,9 +4,9 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; import lombok.Data; -import javax.validation.Validator; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; /** * 支付宝的 PayClientConfig 实现类 @@ -26,6 +26,11 @@ public class AlipayPayClientConfig implements PayClientConfig { */ public static final Integer MODE_CERTIFICATE = 2; + /** + * 接口内容加密方式 - AES 加密 + */ + public static final String ENC_TYPE_AES = "AES"; + /** * 签名算法类型 - RSA */ @@ -92,6 +97,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/SmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClient.java index 46224663c2..5d7b80990a 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClient.java @@ -46,6 +46,8 @@ SmsSendRespDTO sendSms(Long logId, String mobile, String apiTemplateId, /** * 查询指定的短信模板 * + * 如果查询失败,则返回 null 空 + * * @param apiTemplateId 短信 API 的模板编号 * @return 短信模板 */ 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 SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable 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 @@ private JSONObject request(String apiName, TreeMap queryParams) // 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 bd34fadc90..52693cf919 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; import cn.hutool.crypto.SecureUtil; @@ -32,7 +31,7 @@ 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:参考阿里云在优化下 /** * 华为短信客户端的实现类 * @@ -51,7 +50,6 @@ public class HuaweiSmsClient extends AbstractSmsClient { @Override protected void doInit() { - } public HuaweiSmsClient(SmsChannelProperties properties) { @@ -63,6 +61,7 @@ public HuaweiSmsClient(SmsChannelProperties properties) { @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 @@ 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 @@ */ 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 TencentSmsClient(SmsChannelProperties properties) { @Override protected void doInit() { - } /** @@ -95,31 +87,96 @@ private String getApiKey() { @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()))); + 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")); + } + + @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")); // 发送序列号 + }); + } - JSONObject JsonResponse = sendSmsRequest(body,"SendSms","2021-01-11","ap-guangzhou"); - SmsResponse smsResponse = getSmsSendResponse(JsonResponse); + @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); - return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString()); + // 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); } - JSONObject sendSmsRequest(TreeMap body,String action,String version,String region) throws Exception { + @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 @@ JSONObject sendSmsRequest(TreeMap body,String action,String vers + "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 @@ JSONObject sendSmsRequest(TreeMap body,String action,String vers 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/main/java/cn/iocoder/yudao/module/system/framework/sms/core/property/SmsChannelProperties.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/property/SmsChannelProperties.java index 793b049e52..dc0a131374 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/property/SmsChannelProperties.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/property/SmsChannelProperties.java @@ -1,12 +1,11 @@ package cn.iocoder.yudao.module.system.framework.sms.core.property; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.Data; import org.springframework.validation.annotation.Validated; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; - /** * 短信渠道配置类 * 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 ef5826d67f..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 @@ -24,7 +24,7 @@ import static org.mockito.Mockito.mockStatic; /** - * {@link AliyunSmsClient} 的单元测试 + * {@link cn.iocoder.yudao.module.system.framework.sms.core.client.impl.AliyunSmsClient} 的单元测试 * * @author 芋道源码 */ @@ -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 @@ */ 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 void testAliyunSmsClient_sendSms() throws Throwable { 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 void testAliyunSmsClient_parseSmsReceiveStatus() { 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..c8870580d3 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.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyMap; +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 方法 - - // 调用 - smsClient.doInit(); - // 断言 - assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client")); + 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" + + "}"); + + // 调用 + 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 void testParseSmsReceiveStatus() { " \"ext\": {\"logId\":\"67890\"}\n" + " }\n" + "]"; - // mock 方法 // 调用 List statuses = smsClient.parseSmsReceiveStatus(text); @@ -164,42 +139,44 @@ public void testParseSmsReceiveStatus() { 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() {