From 162f34a6c4b9c25fac95e145199af5876c562cdf Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 23 Oct 2023 18:52:41 +0800 Subject: [PATCH 1/9] =?UTF-8?q?trade=EF=BC=9A=E5=90=AF=E5=8A=A8=E6=88=90?= =?UTF-8?q?=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/bargain/BargainActivityApi.java | 34 ++++--- .../api/bargain/BargainRecordApi.java | 56 +++++++----- .../combination/CombinationActivityApi.java | 10 -- .../api/combination/CombinationRecordApi.java | 91 +++++++++++-------- .../promotion/api/coupon/CouponApi.java | 44 +++++---- .../api/coupon/CouponTemplateApi.java | 23 ----- .../api/discount/DiscountActivityApi.java | 27 +++--- .../api/reward/RewardActivityApi.java | 26 +++--- .../api/seckill/SeckillActivityApi.java | 75 ++++++++------- .../CombinationActivityApiImpl.java | 13 --- .../combination/CombinationRecordApiImpl.java | 5 - .../api/coupon/CouponTemplateApiImpl.java | 33 ------- .../mysql/coupon/CouponTemplateMapper.java | 4 - .../combination/CombinationRecordService.java | 18 ---- .../CombinationRecordServiceImpl.java | 15 --- .../service/coupon/CouponTemplateService.java | 9 -- .../coupon/CouponTemplateServiceImpl.java | 5 - .../module/trade/api/order/TradeOrderApi.java | 8 +- .../rpc/config/RpcConfiguration.java | 35 +++++++ .../trade/framework/rpc/package-info.java | 4 + .../config/SecurityConfiguration.java | 37 ++++++++ .../framework/security/core/package-info.java | 4 + .../src/main/resources/application-dev.yaml | 1 + .../src/main/resources/application-local.yaml | 1 + .../src/main/resources/application.yaml | 14 +++ 25 files changed, 299 insertions(+), 293 deletions(-) delete mode 100644 yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationActivityApi.java delete mode 100644 yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponTemplateApi.java delete mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationActivityApiImpl.java delete mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponTemplateApiImpl.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/rpc/config/RpcConfiguration.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/rpc/package-info.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/security/config/SecurityConfiguration.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/security/core/package-info.java diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApi.java index 0f9812629..b4eec97a0 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApi.java @@ -1,18 +1,28 @@ package cn.iocoder.yudao.module.promotion.api.bargain; -/** - * 砍价活动 Api 接口 - * - * @author HUIHUI - */ +import cn.iocoder.yudao.module.promotion.enums.ApiConstants; +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.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestParam; + +// TODO 芋艿:commonresult +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +@Tag(name = "RPC 服务 - 砍价活动") public interface BargainActivityApi { - /** - * 更新砍价活动库存 - * - * @param id 砍价活动编号 - * @param count 购买数量 - */ - void updateBargainActivityStock(Long id, Integer count); + String PREFIX = ApiConstants.PREFIX + "/bargain-activity"; + + @PutMapping(PREFIX + "/update-stock") + @Operation(summary = "更新砍价活动库存") + @Parameters({ + @Parameter(name = "id", description = "砍价活动编号", required = true, example = "1024"), + @Parameter(name = "count", description = "购买数量", required = true, example = "1"), + }) + void updateBargainActivityStock(@RequestParam("id") Long id, + @RequestParam("count") Integer count); } diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainRecordApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainRecordApi.java index fb0e3f02b..a02a8960d 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainRecordApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainRecordApi.java @@ -1,34 +1,40 @@ package cn.iocoder.yudao.module.promotion.api.bargain; import cn.iocoder.yudao.module.promotion.api.bargain.dto.BargainValidateJoinRespDTO; +import cn.iocoder.yudao.module.promotion.enums.ApiConstants; +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.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestParam; -/** - * 砍价记录 API 接口 - * - * @author HUIHUI - */ +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +@Tag(name = "RPC 服务 - 砍价记录") public interface BargainRecordApi { - /** - * 【下单前】校验是否参与砍价活动 - *

- * 如果校验失败,则抛出业务异常 - * - * @param userId 用户编号 - * @param bargainRecordId 砍价活动编号 - * @param skuId SKU 编号 - * @return 砍价信息 - */ - BargainValidateJoinRespDTO validateJoinBargain(Long userId, Long bargainRecordId, Long skuId); + String PREFIX = ApiConstants.PREFIX + "/bargain-record"; - /** - * 更新砍价记录的订单编号 - * - * 在砍价成功后,用户发起订单后,会记录该订单编号 - * - * @param id 砍价记录编号 - * @param orderId 订单编号 - */ - void updateBargainRecordOrderId(Long id, Long orderId); + @GetMapping(PREFIX + "/validate-join") + @Operation(summary = "【下单前】校验是否参与砍价活动") // 如果校验失败,则抛出业务异常 + @Parameters({ + @Parameter(name = "userId", description = "用户编号", required = true, example = "1024"), + @Parameter(name = "bargainRecordId", description = "砍价记录编号", required = true, example = "2048"), + @Parameter(name = "skuId", description = "SKU 编号", required = true, example = "4096"), + }) + BargainValidateJoinRespDTO validateJoinBargain(@RequestParam("userId") Long userId, + @RequestParam("bargainRecordId") Long bargainRecordId, + @RequestParam("skuId") Long skuId); + + @PutMapping(PREFIX + "/update-order-id") + @Operation(summary = "更新砍价记录的订单编号") // 在砍价成功后,用户发起订单后,会记录该订单编号 + @Parameters({ + @Parameter(name = "id", description = "砍价记录编号", required = true, example = "1024"), + @Parameter(name = "orderId", description = "订单编号", required = true, example = "2048"), + }) + void updateBargainRecordOrderId(@RequestParam("id") Long id, + @RequestParam("oderId") Long orderId); } diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationActivityApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationActivityApi.java deleted file mode 100644 index a5163cdf1..000000000 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationActivityApi.java +++ /dev/null @@ -1,10 +0,0 @@ -package cn.iocoder.yudao.module.promotion.api.combination; - -/** - * 拼团活动 Api 接口 - * - * @author HUIHUI - */ -public interface CombinationActivityApi { - -} diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java index 05dfb8b6e..1dd5c8f3d 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java @@ -3,51 +3,52 @@ package cn.iocoder.yudao.module.promotion.api.combination; import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateRespDTO; import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO; +import cn.iocoder.yudao.module.promotion.enums.ApiConstants; +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.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import javax.validation.Valid; -/** - * 拼团记录 API 接口 - * - * @author HUIHUI - */ +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +@Tag(name = "RPC 服务 - 拼团记录") public interface CombinationRecordApi { - /** - * 校验是否满足拼团条件 - * - * @param userId 用户编号 - * @param activityId 活动编号 - * @param headId 团长编号 - * @param skuId sku 编号 - * @param count 数量 - */ - void validateCombinationRecord(Long userId, Long activityId, Long headId, Long skuId, Integer count); + String PREFIX = ApiConstants.PREFIX + "/combination-record"; - /** - * 创建开团记录 - * - * @param reqDTO 请求 DTO - * @return 拼团信息 - */ - CombinationRecordCreateRespDTO createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO); + @GetMapping(PREFIX + "/validate") + @Operation(summary = "校验是否满足拼团条件") + @Parameters({ + @Parameter(name = "userId", description = "用户编号", required = true, example = "1024"), + @Parameter(name = "activityId", description = "活动编号", required = true, example = "2048"), + @Parameter(name = "headId", description = "团长编号", required = true, example = "4096"), + @Parameter(name = "skuId", description = "SKU 编号", required = true, example = "8192"), + @Parameter(name = "count", description = "数量", required = true, example = "1"), + }) + void validateCombinationRecord(@RequestParam("userId") Long userId, + @RequestParam("activityId") Long activityId, + @RequestParam("headId") Long headId, + @RequestParam("skuId") Long skuId, + @RequestParam("count") Integer count); - /** - * 查询拼团记录是否成功 - * - * @param userId 用户编号 - * @param orderId 订单编号 - * @return 拼团是否成功 - */ - boolean isCombinationRecordSuccess(Long userId, Long orderId); + @PostMapping("/create") + @Operation(summary = "创建开团记录") + CombinationRecordCreateRespDTO createCombinationRecord(@RequestBody @Valid CombinationRecordCreateReqDTO reqDTO); - /** - * 更新拼团状态为【失败】 - * - * @param userId 用户编号 - * @param orderId 订单编号 - */ - void updateRecordStatusToFailed(Long userId, Long orderId); + @GetMapping("/is-success") + @Operation(summary = "查询拼团记录是否成功") + @Parameters({ + @Parameter(name = "userId", description = "用户编号", required = true, example = "1024"), + @Parameter(name = "orderId", description = "订单编号", required = true, example = "2048"), + }) + boolean isCombinationRecordSuccess(@RequestParam("userId") Long userId, + @RequestParam("orderId") Long orderId); /** * 【下单前】校验是否满足拼团活动条件 @@ -61,7 +62,19 @@ public interface CombinationRecordApi { * @param count 数量 * @return 拼团信息 */ - CombinationValidateJoinRespDTO validateJoinCombination(Long userId, Long activityId, Long headId, - Long skuId, Integer count); + @GetMapping("/validate-join") + @Operation(summary = "【下单前】校验是否满足拼团活动条件") // 如果校验失败,则抛出业务异常 + @Parameters({ + @Parameter(name = "userId", description = "用户编号", required = true, example = "1024"), + @Parameter(name = "activityId", description = "活动编号", required = true, example = "2048"), + @Parameter(name = "headId", description = "团长编号", required = true, example = "4096"), + @Parameter(name = "skuId", description = "SKU 编号", required = true, example = "8192"), + @Parameter(name = "count", description = "数量", required = true, example = "1"), + }) + CombinationValidateJoinRespDTO validateJoinCombination(@RequestParam("userId") Long userId, + @RequestParam("activityId") Long activityId, + @RequestParam("headId") Long headId, + @RequestParam("skuId") Long skuId, + @RequestParam("count") Integer count); } diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java index ab970c0a3..8fbed09dd 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java @@ -3,36 +3,34 @@ package cn.iocoder.yudao.module.promotion.api.coupon; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO; +import cn.iocoder.yudao.module.promotion.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import javax.validation.Valid; -/** - * 优惠劵 API 接口 - * - * @author 芋道源码 - */ +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +@Tag(name = "RPC 服务 - 优惠劵") public interface CouponApi { - /** - * 使用优惠劵 - * - * @param useReqDTO 使用请求 - */ - void useCoupon(@Valid CouponUseReqDTO useReqDTO); + String PREFIX = ApiConstants.PREFIX + "/coupon"; - /** - * 退还已使用的优惠券 - * - * @param id 优惠券编号 - */ - void returnUsedCoupon(Long id); + @PutMapping(PREFIX + "/use") + @Operation(summary = "使用优惠劵") + void useCoupon(@RequestBody @Valid CouponUseReqDTO useReqDTO); - /** - * 校验优惠劵 - * - * @param validReqDTO 校验请求 - * @return 优惠劵 - */ + @PutMapping(PREFIX + "/return-used") + @Parameter(name = "id", description = "优惠券编号", required = true, example = "1") + void returnUsedCoupon(@RequestParam("id") Long id); + + @GetMapping(PREFIX + "/get") + @Operation(summary = "校验优惠劵") CouponRespDTO validateCoupon(@Valid CouponValidReqDTO validReqDTO); } diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponTemplateApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponTemplateApi.java deleted file mode 100644 index d31e80ec1..000000000 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponTemplateApi.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.iocoder.yudao.module.promotion.api.coupon; - -import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponTemplateRespDTO; - -import java.util.Collection; -import java.util.List; - -/** - * 优惠劵模版 API 接口 - * - * @author HUIHUI - */ -public interface CouponTemplateApi { - - /** - * 获得优惠券模版的精简信息列表 - * - * @param ids 优惠券模版编号 - * @return 优惠券模版的精简信息列表 - */ - List getCouponTemplateListByIds(Collection ids); - -} diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/discount/DiscountActivityApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/discount/DiscountActivityApi.java index b25f67d9f..7f029257a 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/discount/DiscountActivityApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/discount/DiscountActivityApi.java @@ -1,23 +1,26 @@ package cn.iocoder.yudao.module.promotion.api.discount; import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO; +import cn.iocoder.yudao.module.promotion.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; import java.util.Collection; import java.util.List; -/** - * 限时折扣 API 接口 - * - * @author 芋道源码 - */ +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +@Tag(name = "RPC 服务 - 限时折扣") public interface DiscountActivityApi { - /** - * 获得商品匹配的的限时折扣信息 - * - * @param skuIds 商品 SKU 编号数组 - * @return 限时折扣信息 - */ - List getMatchDiscountProductList(Collection skuIds); + String PREFIX = ApiConstants.PREFIX + "/discount-activity"; + + @GetMapping(PREFIX + "/list-by-sku-id") + @Operation(summary = "获得商品匹配的的限时折扣信息") + @Parameter(name = "skuIds", description = "商品 SKU 编号数组", required = true, example = "[1, 2]") + List getMatchDiscountProductList(@RequestParam("skuIds") Collection skuIds); } diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/RewardActivityApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/RewardActivityApi.java index efeddf3d5..9a6383879 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/RewardActivityApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/RewardActivityApi.java @@ -1,24 +1,26 @@ package cn.iocoder.yudao.module.promotion.api.reward; import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; +import cn.iocoder.yudao.module.promotion.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; import java.util.Collection; import java.util.List; -/** - * 满减送活动 API 接口 - * - * @author 芋道源码 - */ +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +@Tag(name = "RPC 服务 - 满减送") public interface RewardActivityApi { + String PREFIX = ApiConstants.PREFIX + "/reward-activity"; - /** - * 基于指定的 SPU 编号数组,获得它们匹配的满减送活动 - * - * @param spuIds SPU 编号数组 - * @return 满减送活动列表 - */ - List getMatchRewardActivityList(Collection spuIds); + @GetMapping(PREFIX + "/list-by-spu-id") + @Operation(summary = "获得商品匹配的的满减送活动信息") + @Parameter(name = "spuIds", description = "商品 SPU 编号数组", required = true, example = "[1, 2]") + List getMatchRewardActivityList(@RequestParam("spuIds") Collection spuIds); } diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java index 0d65919d1..75ca6dd58 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java @@ -1,42 +1,53 @@ package cn.iocoder.yudao.module.promotion.api.seckill; import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO; +import cn.iocoder.yudao.module.promotion.enums.ApiConstants; +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.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestParam; -/** - * 秒杀活动 API 接口 - * - * @author HUIHUI - */ +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +@Tag(name = "RPC 服务 - 秒杀活动") public interface SeckillActivityApi { - /** - * 更新秒杀库存(减少) - * - * @param id 活动编号 - * @param skuId sku 编号 - * @param count 数量(正数) - */ - void updateSeckillStockDecr(Long id, Long skuId, Integer count); + String PREFIX = ApiConstants.PREFIX + "/discount-activity"; - /** - * 更新秒杀库存(增加) - * - * @param id 活动编号 - * @param skuId sku 编号 - * @param count 数量(正数) - */ - void updateSeckillStockIncr(Long id, Long skuId, Integer count); + @PutMapping(PREFIX + "/update-stock-decr") + @Operation(summary = "更新秒杀库存(减少)") + @Parameters({ + @Parameter(name = "id", description = "活动编号", required = true, example = "1"), + @Parameter(name = "skuId", description = "SKU 编号", required = true, example = "2"), + @Parameter(name = "count", description = "数量", required = true, example = "3"), + }) + void updateSeckillStockDecr(@RequestParam("id") Long id, + @RequestParam("skuId") Long skuId, + @RequestParam("count")Integer count); - /** - * 【下单前】校验是否参与秒杀活动 - * - * 如果校验失败,则抛出业务异常 - * - * @param activityId 活动编号 - * @param skuId SKU 编号 - * @param count 数量 - * @return 秒杀信息 - */ - SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count); + @PutMapping(PREFIX + "/update-stock-incr") + @Operation(summary = "更新秒杀库存(增加)") + @Parameters({ + @Parameter(name = "id", description = "活动编号", required = true, example = "1"), + @Parameter(name = "skuId", description = "SKU 编号", required = true, example = "2"), + @Parameter(name = "count", description = "数量", required = true, example = "3"), + }) + void updateSeckillStockIncr(@RequestParam("id") Long id, + @RequestParam("skuId") Long skuId, + @RequestParam("count")Integer count); + + @GetMapping("/validate-join") + @Operation(summary = "【下单前】校验是否参与秒杀活动") // 如果校验失败,则抛出业务异常 + @Parameters({ + @Parameter(name = "activityId", description = "活动编号", required = true, example = "1"), + @Parameter(name = "skuId", description = "SKU 编号", required = true, example = "2"), + @Parameter(name = "count", description = "数量", required = true, example = "3"), + }) + SeckillValidateJoinRespDTO validateJoinSeckill(@RequestParam("activityId") Long activityId, + @RequestParam("skuId") Long skuId, + @RequestParam("count")Integer count); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationActivityApiImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationActivityApiImpl.java deleted file mode 100644 index 967ce6101..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationActivityApiImpl.java +++ /dev/null @@ -1,13 +0,0 @@ -package cn.iocoder.yudao.module.promotion.api.combination; - -import org.springframework.stereotype.Service; - -/** - * 拼团活动 Api 接口实现类 - * - * @author HUIHUI - */ -@Service -public class CombinationActivityApiImpl implements CombinationActivityApi { - -} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java index 5f586dd7d..7d5c0506f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java @@ -44,11 +44,6 @@ public class CombinationRecordApiImpl implements CombinationRecordApi { return CombinationRecordStatusEnum.isSuccess(record.getStatus()); } - @Override - public void updateRecordStatusToFailed(Long userId, Long orderId) { - recordService.updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordStatusEnum.FAILED.getStatus(), userId, orderId); - } - @Override public CombinationValidateJoinRespDTO validateJoinCombination(Long userId, Long activityId, Long headId, Long skuId, Integer count) { return recordService.validateJoinCombination(userId, activityId, headId, skuId, count); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponTemplateApiImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponTemplateApiImpl.java deleted file mode 100644 index 8c4f443f5..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponTemplateApiImpl.java +++ /dev/null @@ -1,33 +0,0 @@ -package cn.iocoder.yudao.module.promotion.api.coupon; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponTemplateRespDTO; -import cn.iocoder.yudao.module.promotion.convert.coupon.CouponTemplateConvert; -import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * 优惠劵模版 API 接口实现类 - * - * @author HUIHUI - */ -@Service -public class CouponTemplateApiImpl implements CouponTemplateApi { - - @Resource - private CouponTemplateService couponTemplateService; - - @Override - public List getCouponTemplateListByIds(Collection ids) { - if (CollUtil.isEmpty(ids)) { // 防御一下 - return Collections.emptyList(); - } - return CouponTemplateConvert.INSTANCE.convertList(couponTemplateService.getCouponTemplateListByIds(ids)); - } - -} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java index ba5706a77..dc703d881 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java @@ -75,8 +75,4 @@ public interface CouponTemplateMapper extends BaseMapperX { return canTakeConsumer; } - default List selectListByIds(Collection ids) { - return selectList(new LambdaQueryWrapperX().in(CouponTemplateDO::getId, ids)); - } - } 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 5e6770061..d2b96b91d 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 @@ -21,15 +21,6 @@ import java.util.Map; */ public interface CombinationRecordService { - /** - * 更新拼团状态 - * - * @param status 状态 - * @param userId 用户编号 - * @param orderId 订单编号 - */ - void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId); - /** * 【下单前】校验是否满足拼团活动条件 * @@ -62,15 +53,6 @@ public interface CombinationRecordService { */ CombinationRecordDO getCombinationRecord(Long userId, Long orderId); - /** - * 获取拼团记录 - * - * @param userId 用户 id - * @param activityId 活动 id - * @return 拼团记录列表 - */ - List getCombinationRecordListByUserIdAndActivityId(Long userId, Long activityId); - /** * 【下单前】校验是否满足拼团活动条件 * 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 4a7421e49..2a23e4360 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 @@ -66,16 +66,6 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @Resource private TradeOrderApi tradeOrderApi; - @Override - @Transactional(rollbackFor = Exception.class) - public void updateCombinationRecordStatusByUserIdAndOrderId(Integer status, Long userId, Long orderId) { - // 校验拼团是否存在 - CombinationRecordDO record = validateCombinationRecord(userId, orderId); - - // 更新状态 - combinationRecordMapper.updateById(new CombinationRecordDO().setId(record.getId()).setStatus(status)); - } - private CombinationRecordDO validateCombinationRecord(Long userId, Long orderId) { // 校验拼团是否存在 CombinationRecordDO recordDO = combinationRecordMapper.selectByUserIdAndOrderId(userId, orderId); @@ -229,11 +219,6 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { return combinationRecordMapper.selectByUserIdAndOrderId(userId, orderId); } - @Override - public List getCombinationRecordListByUserIdAndActivityId(Long userId, Long activityId) { - return combinationRecordMapper.selectListByUserIdAndActivityId(userId, activityId); - } - @Override public CombinationValidateJoinRespDTO validateJoinCombination(Long userId, Long activityId, Long headId, Long skuId, Integer count) { diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateService.java index a47644a4c..db0fa6cb4 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateService.java @@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum; import javax.validation.Valid; -import java.util.Collection; import java.util.List; /** @@ -92,12 +91,4 @@ public interface CouponTemplateService { List getCouponTemplateList(List canTakeTypes, Integer productScope, Long productScopeValue, Integer count); - /** - * 获得优惠券模版列表 - * - * @param ids 优惠券模版编号 - * @return 优惠券模版列表 - */ - List getCouponTemplateListByIds(Collection ids); - } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java index 8a7fbb8ba..1803ccda2 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImpl.java @@ -127,9 +127,4 @@ public class CouponTemplateServiceImpl implements CouponTemplateService { return couponTemplateMapper.selectList(canTakeTypes, productScope, productScopeValue, count); } - @Override - public List getCouponTemplateListByIds(Collection ids) { - return couponTemplateMapper.selectListByIds(ids); - } - } diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java index 0715d0a9e..9abef0f4e 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java @@ -18,18 +18,20 @@ import java.util.List; @Tag(name = "RPC 服务 - 订单") public interface TradeOrderApi { - @GetMapping("/list") + String PREFIX = ApiConstants.PREFIX + "/order"; + + @GetMapping(PREFIX + "/list") @Operation(summary = "获得订单列表") @Parameter(name = "ids", description = "订单编号数组", required = true) List getOrderList(@RequestParam("ids") Collection ids); - @GetMapping("/get") + @GetMapping(PREFIX + "/get") @Operation(summary = "获得订单") @Parameter(name = "id", description = "订单编号", required = true) TradeOrderRespDTO getOrder(@RequestParam("id") Long id); // TODO 芋艿:需要优化下; - @PutMapping("/cancel-paid") + @PutMapping(PREFIX + "/cancel-paid") @Parameters({ @Parameter(name = "userId", description = "用户编号", required = true, example = "1024"), @Parameter(name = "orderId", description = "订单编号", required = true, example = "2048"), diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/rpc/config/RpcConfiguration.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/rpc/config/RpcConfiguration.java new file mode 100644 index 000000000..2e795cb54 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/rpc/config/RpcConfiguration.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.trade.framework.rpc.config; + +import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; +import cn.iocoder.yudao.module.member.api.config.MemberConfigApi; +import cn.iocoder.yudao.module.member.api.level.MemberLevelApi; +import cn.iocoder.yudao.module.member.api.point.MemberPointApi; +import cn.iocoder.yudao.module.member.api.user.MemberUserApi; +import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; +import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi; +import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi; +import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi; +import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; +import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi; +import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi; +import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi; +import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; +import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi; +import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi; +import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi; +import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableFeignClients(clients = { + BargainActivityApi.class, BargainRecordApi.class, CombinationRecordApi.class, + CouponApi.class, DiscountActivityApi.class, RewardActivityApi.class, SeckillActivityApi.class, + MemberUserApi.class, MemberPointApi.class, MemberLevelApi.class, MemberAddressApi.class, MemberConfigApi.class, + ProductSpuApi.class, ProductSkuApi.class, ProductCommentApi.class, ProductCategoryApi.class, + PayOrderApi.class, PayRefundApi.class, + NotifyMessageSendApi.class +}) +public class RpcConfiguration { +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/rpc/package-info.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/rpc/package-info.java new file mode 100644 index 000000000..42531afa8 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/rpc/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package cn.iocoder.yudao.module.trade.framework.rpc; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/security/config/SecurityConfiguration.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/security/config/SecurityConfiguration.java new file mode 100644 index 000000000..64c0e3db9 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/security/config/SecurityConfiguration.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.trade.framework.security.config; + +import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer; +import cn.iocoder.yudao.module.member.enums.ApiConstants; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; + +/** + * Trade 模块的 Security 配置 + */ +@Configuration("tradeSecurityConfiguration") +public class SecurityConfiguration { + + @Bean("tradeAuthorizeRequestsCustomizer") + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + return new AuthorizeRequestsCustomizer() { + + @Override + public void customize(ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry) { + // Swagger 接口文档 + registry.antMatchers("/v3/api-docs/**").permitAll() // 元数据 + .antMatchers("/swagger-ui.html").permitAll(); // Swagger UI + // Spring Boot Actuator 的安全配置 + registry.antMatchers("/actuator").anonymous() + .antMatchers("/actuator/**").anonymous(); + // Druid 监控 + registry.antMatchers("/druid/**").anonymous(); + // RPC 服务的安全配置 + registry.antMatchers(ApiConstants.PREFIX + "/**").permitAll(); + } + + }; + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/security/core/package-info.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/security/core/package-info.java new file mode 100644 index 000000000..0ec1e46e7 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/security/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package cn.iocoder.yudao.module.trade.framework.security.core; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application-dev.yaml b/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application-dev.yaml index d8a81c9a3..9bedff0a1 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application-dev.yaml +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application-dev.yaml @@ -111,3 +111,4 @@ yudao: admin-ui: url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址 demo: true # 开启演示模式 + tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application-local.yaml b/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application-local.yaml index 1a2e58dbf..f0a796a83 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application-local.yaml +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application-local.yaml @@ -139,3 +139,4 @@ yudao: error-code: # 错误码相关配置项 enable: false demo: false # 关闭演示模式 + tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application.yaml b/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application.yaml index 95c8d51d4..493ded33e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application.yaml +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/resources/application.yaml @@ -130,5 +130,19 @@ yudao: enable: true ignore-urls: ignore-tables: + trade: + order: + app-id: 1 # 商户编号 + pay-expire-time: 2h # 支付的过期时间 + receive-expire-time: 14d # 收货的过期时间 + comment-expire-time: 7d # 评论的过期时间 + express: + client: kd_niao + kd-niao: + api-key: cb022f1e-48f1-4c4a-a723-9001ac9676b8 + business-id: 1809751 + kd100: + key: pLXUGAwK5305 + customer: E77DF18BE109F454A5CD319E44BF5177 debug: false From e26b29c5eb079f0416f4ab403b86507a6b21f8c6 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 23 Oct 2023 19:22:47 +0800 Subject: [PATCH 2/9] =?UTF-8?q?trade=EF=BC=9A=E5=A2=9E=E5=8A=A0=20gateway?= =?UTF-8?q?=20=E8=BD=AC=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yaml | 16 ++++++++++++++++ .../module/member/api/user/MemberUserApi.java | 2 +- .../member/api/user/MemberUserApiImpl.java | 4 ++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/yudao-gateway/src/main/resources/application.yaml b/yudao-gateway/src/main/resources/application.yaml index 3ccb05120..d3ac74eae 100644 --- a/yudao-gateway/src/main/resources/application.yaml +++ b/yudao-gateway/src/main/resources/application.yaml @@ -110,6 +110,19 @@ spring: - Path=/app-api/promotion/** filters: - RewritePath=/app-api/promotion/v3/api-docs, /v3/api-docs + ## trade-server 服务 + - id: trade-admin-api # 路由的编号 + uri: grayLb://trade-server + predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 + - Path=/admin-api/trade/** + filters: + - RewritePath=/admin-api/trade/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs + - id: trade-app-api # 路由的编号 + uri: grayLb://trade-server + predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 + - Path=/app-api/trade/** + filters: + - RewritePath=/app-api/trade/v3/api-docs, /v3/api-docs x-forwarded: prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀 @@ -142,3 +155,6 @@ knife4j: - name: promotion-server service-name: promotion-server url: /admin-api/promotion/v3/api-docs + - name: trade-server + service-name: trade-server + url: /admin-api/trade/v3/api-docs diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java index 085f004e4..c05eaa9fd 100644 --- a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java +++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java @@ -17,7 +17,7 @@ import java.util.Map; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; @FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = -@Tag(name = "RPC 服务 - 会员用户的") +@Tag(name = "RPC 服务 - 会员用户") public interface MemberUserApi { String PREFIX = ApiConstants.PREFIX + "/user"; diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java index 648a02f2a..20ef36473 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java @@ -5,8 +5,8 @@ import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert; import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; import cn.iocoder.yudao.module.member.service.user.MemberUserService; -import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.Collection; @@ -19,7 +19,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; * * @author 芋道源码 */ -@Service +@RestController // 提供 RESTful API 接口,给 Feign 调用 @Validated public class MemberUserApiImpl implements MemberUserApi { From 97103a884689033cba695dbc54de8a0b4151516e Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 23 Oct 2023 19:40:29 +0800 Subject: [PATCH 3/9] =?UTF-8?q?statistics=EF=BC=9A=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/ip/core/utils/AreaUtils.java | 45 +++++- yudao-module-mall/pom.xml | 4 +- .../yudao-module-statistics-api/pom.xml | 47 ++++++ .../module/statistics/api/package-info.java | 4 + .../statistics/enums/TimeRangeTypeEnum.java | 48 ++++++ .../module/statistics/enums/package-info.java | 4 + .../yudao-module-statistics-biz/pom.xml | 141 ++++++++++++++++++ .../StatisticsServerApplication.java | 30 ++++ .../admin/common/vo/DataComparisonRespVO.java | 20 +++ .../member/MemberStatisticsController.java | 121 +++++++++++++++ .../member/vo/MemberAnalyseDataRespVO.java | 19 +++ .../admin/member/vo/MemberAnalyseReqVO.java | 19 +++ .../admin/member/vo/MemberAnalyseRespVO.java | 26 ++++ .../member/vo/MemberAreaStatisticsRespVO.java | 26 ++++ .../admin/member/vo/MemberCountRespVO.java | 16 ++ .../member/vo/MemberRegisterCountRespVO.java | 23 +++ .../member/vo/MemberSexStatisticsRespVO.java | 17 +++ .../admin/member/vo/MemberSummaryRespVO.java | 23 +++ .../vo/MemberTerminalStatisticsRespVO.java | 17 +++ .../admin/pay/PayStatisticsController.java | 36 +++++ .../admin/pay/vo/PaySummaryRespVO.java | 13 ++ .../product/ProductStatisticsController.java | 41 +++++ .../trade/TradeStatisticsController.java | 138 +++++++++++++++++ .../admin/trade/vo/TradeOrderCountRespVO.java | 22 +++ .../trade/vo/TradeOrderSummaryRespVO.java | 16 ++ .../admin/trade/vo/TradeOrderTrendReqVO.java | 31 ++++ .../admin/trade/vo/TradeOrderTrendRespVO.java | 19 +++ .../admin/trade/vo/TradeSummaryRespVO.java | 20 +++ .../admin/trade/vo/TradeTrendReqVO.java | 18 +++ .../trade/vo/TradeTrendSummaryExcelVO.java | 44 ++++++ .../trade/vo/TradeTrendSummaryRespVO.java | 40 +++++ .../controller/app/package-info.java | 4 + .../member/MemberStatisticsConvert.java | 51 +++++++ .../convert/pay/PayStatisticsConvert.java | 19 +++ .../convert/trade/TradeStatisticsConvert.java | 74 +++++++++ .../dataobject/member/MemberStatisticsDO.java | 70 +++++++++ .../dal/dataobject/package-info.java | 4 + .../dataobject/trade/TradeStatisticsDO.java | 89 +++++++++++ .../infra/ApiAccessLogStatisticsMapper.java | 29 ++++ .../mysql/member/MemberStatisticsMapper.java | 47 ++++++ .../mysql/pay/PayWalletStatisticsMapper.java | 43 ++++++ .../mysql/product/ProductSpuStatisticsDO.java | 74 +++++++++ .../mysql/product/ProductStatisticsDO.java | 70 +++++++++ .../trade/AfterSaleStatisticsMapper.java | 26 ++++ .../trade/BrokerageStatisticsMapper.java | 27 ++++ .../trade/TradeOrderStatisticsMapper.java | 75 ++++++++++ .../mysql/trade/TradeStatisticsMapper.java | 44 ++++++ .../module/statistics/job/package-info.java | 4 + .../job/trade/TradeStatisticsJob.java | 49 ++++++ .../yudao/module/statistics/package-info.java | 8 + .../infra/ApiAccessLogStatisticsService.java | 34 +++++ .../ApiAccessLogStatisticsServiceImpl.java | 32 ++++ .../member/MemberStatisticsService.java | 76 ++++++++++ .../member/MemberStatisticsServiceImpl.java | 136 +++++++++++++++++ .../member/bo/MemberAreaStatisticsRespBO.java | 38 +++++ .../pay/PayWalletStatisticsService.java | 43 ++++++ .../pay/PayWalletStatisticsServiceImpl.java | 52 +++++++ .../service/pay/bo/RechargeSummaryRespBO.java | 22 +++ .../trade/AfterSaleStatisticsService.java | 34 +++++ .../trade/AfterSaleStatisticsServiceImpl.java | 34 +++++ .../trade/BrokerageStatisticsService.java | 33 ++++ .../trade/BrokerageStatisticsServiceImpl.java | 37 +++++ .../trade/TradeOrderStatisticsService.java | 89 +++++++++++ .../TradeOrderStatisticsServiceImpl.java | 108 ++++++++++++++ .../service/trade/TradeStatisticsService.java | 70 +++++++++ .../trade/TradeStatisticsServiceImpl.java | 135 +++++++++++++++++ .../trade/bo/AfterSaleSummaryRespBO.java | 22 +++ .../trade/bo/MemberAreaStatisticsRespBO.java | 41 +++++ .../trade/bo/TradeOrderSummaryRespBO.java | 26 ++++ .../service/trade/bo/TradeSummaryRespBO.java | 23 +++ .../service/trade/bo/WalletSummaryRespBO.java | 35 +++++ .../src/main/resources/application-dev.yaml | 113 ++++++++++++++ .../src/main/resources/application-local.yaml | 141 ++++++++++++++++++ .../src/main/resources/application.yaml | 134 +++++++++++++++++ .../src/main/resources/bootstrap-local.yaml | 23 +++ .../src/main/resources/bootstrap.yaml | 14 ++ .../src/main/resources/logback-spring.xml | 76 ++++++++++ .../infra/ApiAccessLogStatisticsMapper.xml | 22 +++ .../mapper/member/MemberStatisticsMapper.xml | 51 +++++++ .../mapper/pay/PayWalletStatisticsMapper.xml | 55 +++++++ .../trade/AfterSaleStatisticsMapper.xml | 21 +++ .../trade/BrokerageStatisticsMapper.xml | 21 +++ .../trade/TradeOrderStatisticsMapper.xml | 106 +++++++++++++ .../mapper/trade/TradeStatisticsMapper.xml | 44 ++++++ .../enums/wallet/PayWalletBizTypeEnum.java | 40 +++++ 85 files changed, 3873 insertions(+), 3 deletions(-) create mode 100644 yudao-module-mall/yudao-module-statistics-api/pom.xml create mode 100644 yudao-module-mall/yudao-module-statistics-api/src/main/java/cn/iocoder/yudao/module/statistics/api/package-info.java create mode 100644 yudao-module-mall/yudao-module-statistics-api/src/main/java/cn/iocoder/yudao/module/statistics/enums/TimeRangeTypeEnum.java create mode 100644 yudao-module-mall/yudao-module-statistics-api/src/main/java/cn/iocoder/yudao/module/statistics/enums/package-info.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/pom.xml create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/StatisticsServerApplication.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/common/vo/DataComparisonRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/MemberStatisticsController.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseDataRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseReqVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAreaStatisticsRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberCountRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberRegisterCountRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberSexStatisticsRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberSummaryRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberTerminalStatisticsRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/pay/PayStatisticsController.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/pay/vo/PaySummaryRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/product/ProductStatisticsController.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/TradeStatisticsController.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderCountRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderSummaryRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderTrendReqVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderTrendRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeSummaryRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeTrendReqVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeTrendSummaryExcelVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeTrendSummaryRespVO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/app/package-info.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/member/MemberStatisticsConvert.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/pay/PayStatisticsConvert.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/trade/TradeStatisticsConvert.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/dataobject/member/MemberStatisticsDO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/dataobject/package-info.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/dataobject/trade/TradeStatisticsDO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/infra/ApiAccessLogStatisticsMapper.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/member/MemberStatisticsMapper.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/pay/PayWalletStatisticsMapper.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/product/ProductSpuStatisticsDO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/product/ProductStatisticsDO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/AfterSaleStatisticsMapper.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/BrokerageStatisticsMapper.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeOrderStatisticsMapper.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeStatisticsMapper.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/package-info.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/trade/TradeStatisticsJob.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/package-info.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsService.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsServiceImpl.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsService.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsServiceImpl.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/bo/MemberAreaStatisticsRespBO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsService.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsServiceImpl.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/bo/RechargeSummaryRespBO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsService.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsServiceImpl.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsService.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsServiceImpl.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsService.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsServiceImpl.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsService.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsServiceImpl.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/AfterSaleSummaryRespBO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/MemberAreaStatisticsRespBO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/TradeOrderSummaryRespBO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/TradeSummaryRespBO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/WalletSummaryRespBO.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/resources/application-dev.yaml create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/resources/application-local.yaml create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/resources/application.yaml create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/resources/bootstrap-local.yaml create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/resources/bootstrap.yaml create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/resources/logback-spring.xml create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/infra/ApiAccessLogStatisticsMapper.xml create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/member/MemberStatisticsMapper.xml create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/pay/PayWalletStatisticsMapper.xml create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/AfterSaleStatisticsMapper.xml create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/BrokerageStatisticsMapper.xml create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/TradeOrderStatisticsMapper.xml create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/TradeStatisticsMapper.xml create mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java diff --git a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java index d3fe59a6c..5a7340095 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java @@ -7,12 +7,16 @@ import cn.hutool.core.text.csv.CsvUtil; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.ip.core.Area; import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum; +import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; /** * 区域工具类 @@ -108,7 +112,7 @@ public class AreaUtils { // “递归”父节点 area = area.getParent(); if (area == null - || ObjectUtils.equalsAny(area.getId(), Area.ID_GLOBAL, Area.ID_CHINA)) { // 跳过父节点为中国的情况 + || ObjectUtils.equalsAny(area.getId(), Area.ID_GLOBAL, Area.ID_CHINA)) { // 跳过父节点为中国的情况 break; } sb.insert(0, separator); @@ -116,4 +120,43 @@ public class AreaUtils { return sb.toString(); } + /** + * 获取指定类型的区域列表 + * + * @param type 区域类型 + * @param func 转换函数 + * @param 结果类型 + * @return 区域列表 + */ + public static List getByType(AreaTypeEnum type, Function func) { + return convertList(areas.values(), func, area -> type.getType().equals(area.getType())); + } + + /** + * 根据区域编号、上级区域类型,获取上级区域编号 + * + * @param id 区域编号 + * @param type 区域类型 + * @return 上级区域编号 + */ + public static Integer getParentIdByType(Integer id, @NonNull AreaTypeEnum type) { + for (int i = 0; i < Byte.MAX_VALUE; i++) { + Area area = AreaUtils.getArea(id); + if (area == null) { + return null; + } + // 情况一:匹配到,返回它 + if (type.getType().equals(area.getType())) { + return area.getId(); + } + // 情况二:找到根节点,返回空 + if (area.getParent() == null || area.getParent().getId() == null) { + return null; + } + // 其它:继续向上查找 + id = area.getParent().getId(); + } + return null; + } + } diff --git a/yudao-module-mall/pom.xml b/yudao-module-mall/pom.xml index bb6417277..8cb649739 100644 --- a/yudao-module-mall/pom.xml +++ b/yudao-module-mall/pom.xml @@ -24,8 +24,8 @@ yudao-module-product-biz yudao-module-trade-api yudao-module-trade-biz - - + yudao-module-statistics-api + yudao-module-statistics-biz diff --git a/yudao-module-mall/yudao-module-statistics-api/pom.xml b/yudao-module-mall/yudao-module-statistics-api/pom.xml new file mode 100644 index 000000000..79e592c96 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-api/pom.xml @@ -0,0 +1,47 @@ + + + + cn.iocoder.cloud + yudao-module-mall + ${revision} + + 4.0.0 + yudao-module-statistics-api + jar + + ${project.artifactId} + + statistics 模块 API,暴露给其它模块调用 + + + + + cn.iocoder.cloud + yudao-common + + + + + org.springdoc + springdoc-openapi-ui + provided + + + + + org.springframework.boot + spring-boot-starter-validation + true + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + + diff --git a/yudao-module-mall/yudao-module-statistics-api/src/main/java/cn/iocoder/yudao/module/statistics/api/package-info.java b/yudao-module-mall/yudao-module-statistics-api/src/main/java/cn/iocoder/yudao/module/statistics/api/package-info.java new file mode 100644 index 000000000..2963c120a --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-api/src/main/java/cn/iocoder/yudao/module/statistics/api/package-info.java @@ -0,0 +1,4 @@ +/** + * TODO 占位,无特殊含义 + */ +package cn.iocoder.yudao.module.statistics.api; diff --git a/yudao-module-mall/yudao-module-statistics-api/src/main/java/cn/iocoder/yudao/module/statistics/enums/TimeRangeTypeEnum.java b/yudao-module-mall/yudao-module-statistics-api/src/main/java/cn/iocoder/yudao/module/statistics/enums/TimeRangeTypeEnum.java new file mode 100644 index 000000000..5f3c8fe22 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-api/src/main/java/cn/iocoder/yudao/module/statistics/enums/TimeRangeTypeEnum.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.statistics.enums; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 时间范围类型的枚举 + * + * @author owen + */ +@AllArgsConstructor +@Getter +public enum TimeRangeTypeEnum implements IntArrayValuable { + + /** + * 天 + */ + DAY(1), + /** + * 周 + */ + WEEK(7), + /** + * 月 + */ + MONTH(30), + /** + * 年 + */ + YEAR(365), + ; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TimeRangeTypeEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-api/src/main/java/cn/iocoder/yudao/module/statistics/enums/package-info.java b/yudao-module-mall/yudao-module-statistics-api/src/main/java/cn/iocoder/yudao/module/statistics/enums/package-info.java new file mode 100644 index 000000000..f885ae076 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-api/src/main/java/cn/iocoder/yudao/module/statistics/enums/package-info.java @@ -0,0 +1,4 @@ +/** + * TODO 占位,无特殊含义 + */ +package cn.iocoder.yudao.module.statistics.enums; diff --git a/yudao-module-mall/yudao-module-statistics-biz/pom.xml b/yudao-module-mall/yudao-module-statistics-biz/pom.xml new file mode 100644 index 000000000..52995fa13 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/pom.xml @@ -0,0 +1,141 @@ + + + + cn.iocoder.cloud + yudao-module-mall + ${revision} + + 4.0.0 + yudao-module-statistics-biz + jar + + ${project.artifactId} + + statistics 模块,主要实现统计相关功能 + 例如:统计商品、会员、交易等功能。 + + + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + + cn.iocoder.cloud + yudao-spring-boot-starter-env + + + + + cn.iocoder.cloud + yudao-module-statistics-api + ${revision} + + + cn.iocoder.cloud + yudao-module-promotion-api + ${revision} + + + cn.iocoder.cloud + yudao-module-product-api + ${revision} + + + cn.iocoder.cloud + yudao-module-trade-api + ${revision} + + + cn.iocoder.cloud + yudao-module-member-api + ${revision} + + + cn.iocoder.cloud + yudao-module-pay-api + ${revision} + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-operatelog + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-tenant + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-ip + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-web + + + cn.iocoder.cloud + yudao-spring-boot-starter-security + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-mybatis + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-rpc + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-job + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-test + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-excel + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-dict + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-monitor + + + + diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/StatisticsServerApplication.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/StatisticsServerApplication.java new file mode 100644 index 000000000..8811256d2 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/StatisticsServerApplication.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.statistics; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 项目的启动类 + * + * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * + * @author 芋道源码 + */ +@SpringBootApplication +public class StatisticsServerApplication { + + public static void main(String[] args) { + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + + SpringApplication.run(StatisticsServerApplication.class, args); + + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/common/vo/DataComparisonRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/common/vo/DataComparisonRespVO.java new file mode 100644 index 000000000..efd889a87 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/common/vo/DataComparisonRespVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.common.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Schema(description = "管理后台 - 数据对照 Response VO") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DataComparisonRespVO { + + @Schema(description = "当前数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private T value; + + @Schema(description = "参照数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private T reference; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/MemberStatisticsController.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/MemberStatisticsController.java new file mode 100644 index 000000000..f82b26226 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/MemberStatisticsController.java @@ -0,0 +1,121 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.member; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.NumberUtil; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.*; +import cn.iocoder.yudao.module.statistics.convert.member.MemberStatisticsConvert; +import cn.iocoder.yudao.module.statistics.service.infra.ApiAccessLogStatisticsService; +import cn.iocoder.yudao.module.statistics.service.member.MemberStatisticsService; +import cn.iocoder.yudao.module.statistics.service.trade.TradeOrderStatisticsService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +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.RestController; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 会员统计") +@RestController +@RequestMapping("/statistics/member") +@Validated +@Slf4j +public class MemberStatisticsController { + + @Resource + private MemberStatisticsService memberStatisticsService; + @Resource + private TradeOrderStatisticsService tradeOrderStatisticsService; + @Resource + private ApiAccessLogStatisticsService apiAccessLogStatisticsService; + + // TODO 芋艿:已经 review + @GetMapping("/summary") + @Operation(summary = "获得会员统计(实时统计)") + @PreAuthorize("@ss.hasPermission('statistics:member:query')") + public CommonResult getMemberSummary() { + return success(memberStatisticsService.getMemberSummary()); + } + + // TODO 芋艿:已经 review + @GetMapping("/analyse") + @Operation(summary = "获得会员分析数据") + @PreAuthorize("@ss.hasPermission('statistics:member:query')") + public CommonResult getMemberAnalyse(MemberAnalyseReqVO reqVO) { + // 1. 查询数据 + LocalDateTime beginTime = ArrayUtil.get(reqVO.getTimes(), 0); + LocalDateTime endTime = ArrayUtil.get(reqVO.getTimes(), 1); + // 1.1 查询分析对照数据 + DataComparisonRespVO comparisonData = memberStatisticsService.getMemberAnalyseComparisonData(beginTime, endTime); + // TODO @疯狂:这个可能有点特殊,要按照 create_time 来查询;不然它的漏斗就不统一;因为是访问数量 > 今日下单人 > 今日支付人;是一个统一的维度; + // 1.2 查询成交用户数量 + Integer payUserCount = tradeOrderStatisticsService.getPayUserCount(beginTime, endTime); + // 1.3 计算客单价 + int atv = 0; + if (payUserCount != null && payUserCount > 0) { + // TODO @疯狂:类似上面的 payUserCount + Integer payPrice = tradeOrderStatisticsService.getOrderPayPrice(beginTime, endTime); + atv = NumberUtil.div(payPrice, payUserCount).intValue(); + } + // 1.4 查询访客数量 + Integer visitUserCount = apiAccessLogStatisticsService.getIpCount(UserTypeEnum.MEMBER.getValue(), beginTime, endTime); + // 1.5 下单用户数量 + Integer orderUserCount = tradeOrderStatisticsService.getOrderUserCount(beginTime, endTime); + + // 2. 拼接返回 + return success(MemberStatisticsConvert.INSTANCE.convert(visitUserCount, orderUserCount, payUserCount, atv, comparisonData)); + } + + // TODO 芋艿:已经 review + @GetMapping("/area-statistics-list") + @Operation(summary = "按照省份,获得会员统计列表") + @PreAuthorize("@ss.hasPermission('statistics:member:query')") + public CommonResult> getMemberAreaStatisticsList() { + return success(memberStatisticsService.getMemberAreaStatisticsList()); + } + + // TODO 芋艿:已经 review + @GetMapping("/sex-statistics-list") + @Operation(summary = "按照性别,获得会员统计列表") + @PreAuthorize("@ss.hasPermission('statistics:member:query')") + public CommonResult> getMemberSexStatisticsList() { + return success(memberStatisticsService.getMemberSexStatisticsList()); + } + + // TODO 芋艿:已经 review + @GetMapping("/terminal-statistics-list") + @Operation(summary = "按照终端,获得会员统计列表") + @PreAuthorize("@ss.hasPermission('statistics:member:query')") + public CommonResult> getMemberTerminalStatisticsList() { + return success(memberStatisticsService.getMemberTerminalStatisticsList()); + } + + // TODO 芋艿:已经 review + // TODO @疯狂:要注意 date 的排序; + @GetMapping("/user-count-comparison") + @Operation(summary = "获得用户数量对照") + @PreAuthorize("@ss.hasPermission('statistics:member:query')") + public CommonResult> getUserCountComparison() { + return success(memberStatisticsService.getUserCountComparison()); + } + + // TODO 芋艿:已经 review + @GetMapping("/register-count-list") + @Operation(summary = "获得会员注册数量列表") + @PreAuthorize("@ss.hasPermission('statistics:member:query')") + public CommonResult> getMemberRegisterCountList(MemberAnalyseReqVO reqVO) { + return success(memberStatisticsService.getMemberRegisterCountList( + ArrayUtil.get(reqVO.getTimes(), 0), ArrayUtil.get(reqVO.getTimes(), 1))); + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseDataRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseDataRespVO.java new file mode 100644 index 000000000..d2dd3e483 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseDataRespVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.member.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 会员分析数据 Response VO") +@Data +public class MemberAnalyseDataRespVO { + + @Schema(description = "会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer registerUserCount; + + @Schema(description = "活跃用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer visitUserCount; + + @Schema(description = "充值会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "221") + private Integer rechargeUserCount; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseReqVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseReqVO.java new file mode 100644 index 000000000..e0106b4e8 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.member.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 会员分析 Request VO") +@Data +public class MemberAnalyseReqVO { + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "时间范围") + private LocalDateTime[] times; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseRespVO.java new file mode 100644 index 000000000..e1c46547b --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAnalyseRespVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.member.vo; + +import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 会员分析 Response VO") +@Data +public class MemberAnalyseRespVO { + + @Schema(description = "访客数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer visitUserCount; + + @Schema(description = "下单用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer orderUserCount; + + @Schema(description = "成交用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer payUserCount; + + @Schema(description = "客单价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer atv; + + @Schema(description = "对照数据", requiredMode = Schema.RequiredMode.REQUIRED) + private DataComparisonRespVO comparison; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAreaStatisticsRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAreaStatisticsRespVO.java new file mode 100644 index 000000000..1024d04ef --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberAreaStatisticsRespVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.member.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 会员地区统计 Response VO") +@Data +public class MemberAreaStatisticsRespVO { + + @Schema(description = "省份编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer areaId; + @Schema(description = "省份名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "浙江省") + private String areaName; + + @Schema(description = "会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer userCount; + + @Schema(description = "下单的会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer orderCreateUserCount; + @Schema(description = "支付订单的会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "512") + private Integer orderPayUserCount; + + @Schema(description = "订单支付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "622") + private Integer orderPayPrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberCountRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberCountRespVO.java new file mode 100644 index 000000000..ce81658cf --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberCountRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.member.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 会员数量统计 Response VO") +@Data +public class MemberCountRespVO { + + @Schema(description = "用户访问量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer visitUserCount; + + @Schema(description = "注册用户数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer registerUserCount; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberRegisterCountRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberRegisterCountRespVO.java new file mode 100644 index 000000000..fb7fc2ac4 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberRegisterCountRespVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.member.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDate; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT; + +@Schema(description = "管理后台 - 会员注册数量 Response VO") +@Data +public class MemberRegisterCountRespVO { + + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY, timezone = TIME_ZONE_DEFAULT) + @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private LocalDate date; + + @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer count; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberSexStatisticsRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberSexStatisticsRespVO.java new file mode 100644 index 000000000..eaf7ce5f5 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberSexStatisticsRespVO.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.member.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 会员性别统计 Response VO") +@Data +public class MemberSexStatisticsRespVO { + + @Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer sex; + + // TODO @疯狂:要不还是其它字段,我们也补全,这样方便使用的用户,做定制化;就保持和 MemberAreaStatisticsRespVO 一致; + @Schema(description = "会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer userCount; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberSummaryRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberSummaryRespVO.java new file mode 100644 index 000000000..2a55e3897 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberSummaryRespVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.member.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 会员统计 Response VO") +@Data +public class MemberSummaryRespVO { + + @Schema(description = "会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer userCount; + + @Schema(description = "充值会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "221") + private Integer rechargeUserCount; + + @Schema(description = "充值金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer rechargePrice; + + // TODO @疯狂:要不干脆这个字段改成:orderPayPrice?? + @Schema(description = "支出金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer expensePrice; // 只计算 mall 交易订单的支付金额,不考虑退款 + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberTerminalStatisticsRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberTerminalStatisticsRespVO.java new file mode 100644 index 000000000..3ecf0f8fe --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/vo/MemberTerminalStatisticsRespVO.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.member.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 会员终端统计 Response VO") +@Data +public class MemberTerminalStatisticsRespVO { + + @Schema(description = "终端", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer terminal; + + // TODO @疯狂:要不 orderCreateUserCount 和 orderPayUserCount 貌似更统一一些; + @Schema(description = "会员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer userCount; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/pay/PayStatisticsController.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/pay/PayStatisticsController.java new file mode 100644 index 000000000..362ec7d57 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/pay/PayStatisticsController.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.pay; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.statistics.controller.admin.pay.vo.PaySummaryRespVO; +import cn.iocoder.yudao.module.statistics.convert.pay.PayStatisticsConvert; +import cn.iocoder.yudao.module.statistics.service.pay.PayWalletStatisticsService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +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.RestController; + +import javax.annotation.Resource; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 支付统计") +@RestController +@RequestMapping("/statistics/pay") +@Validated +@Slf4j +public class PayStatisticsController { + + @Resource + private PayWalletStatisticsService payWalletStatisticsService; + + @GetMapping("/summary") + @Operation(summary = "获取充值金额") + public CommonResult getWalletRechargePrice() { + Integer rechargePrice = payWalletStatisticsService.getRechargePriceSummary(); + return success(PayStatisticsConvert.INSTANCE.convert(rechargePrice)); + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/pay/vo/PaySummaryRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/pay/vo/PaySummaryRespVO.java new file mode 100644 index 000000000..01edc24cf --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/pay/vo/PaySummaryRespVO.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.pay.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 支付统计 Response VO") +@Data +public class PaySummaryRespVO { + + @Schema(description = "充值金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer rechargePrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/product/ProductStatisticsController.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/product/ProductStatisticsController.java new file mode 100644 index 000000000..4e5684fb9 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/product/ProductStatisticsController.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.product; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.statistics.dal.mysql.product.ProductSpuStatisticsDO; +import cn.iocoder.yudao.module.statistics.dal.mysql.product.ProductStatisticsDO; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.List; + +@Tag(name = "管理后台 - 商品统计") +@RestController +@RequestMapping("/statistics/product") +@Validated +@Slf4j +public class ProductStatisticsController { + + // TODO @麦子:返回 ProductStatisticsComparisonResp, 里面有两个字段,一个是选择的时间范围的合计结果,一个是对比的时间范围的合计结果; + // 例如说,选择时间范围是 2023-10-01 ~ 2023-10-02,那么对比就是 2023-09-30,再倒推 2 天; + public CommonResult getProductStatisticsComparison() { + return null; + } + + // TODO @麦子:查询指定时间范围内的商品统计数据;DO 到时需要改成 VO 哈 + public CommonResult> getProductStatisticsList( + LocalDateTime[] times) { + return null; + } + + // TODO @麦子:查询指定时间范围内的商品 SPU 统计数据;DO 到时需要改成 VO 哈 + // 入参是分页参数 + 时间范围 + 排序字段 + public CommonResult> getProductSpuStatisticsPage() { + return null; + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/TradeStatisticsController.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/TradeStatisticsController.java new file mode 100644 index 000000000..b46a9c2a3 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/TradeStatisticsController.java @@ -0,0 +1,138 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.trade; + +import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.*; +import cn.iocoder.yudao.module.statistics.convert.trade.TradeStatisticsConvert; +import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO; +import cn.iocoder.yudao.module.statistics.service.trade.AfterSaleStatisticsService; +import cn.iocoder.yudao.module.statistics.service.trade.BrokerageStatisticsService; +import cn.iocoder.yudao.module.statistics.service.trade.TradeOrderStatisticsService; +import cn.iocoder.yudao.module.statistics.service.trade.TradeStatisticsService; +import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeSummaryRespBO; +import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleStatusEnum; +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum; +import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +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.RestController; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.IOException; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 交易统计") +@RestController +@RequestMapping("/statistics/trade") +@Validated +@Slf4j +public class TradeStatisticsController { + + @Resource + private TradeStatisticsService tradeStatisticsService; + @Resource + private TradeOrderStatisticsService tradeOrderStatisticsService; + @Resource + private AfterSaleStatisticsService afterSaleStatisticsService; + @Resource + private BrokerageStatisticsService brokerageStatisticsService; + + // TODO 芋艿:已经 review + @GetMapping("/summary") + @Operation(summary = "获得交易统计") + @PreAuthorize("@ss.hasPermission('statistics:trade:query')") + public CommonResult> getTradeSummaryComparison() { + // 1.1 昨天的数据 + TradeSummaryRespBO yesterdayData = tradeStatisticsService.getTradeSummaryByDays(-1); + // 1.2 前天的数据(用于对照昨天的数据) + TradeSummaryRespBO beforeYesterdayData = tradeStatisticsService.getTradeSummaryByDays(-2); + + // 2.1 本月数据 + TradeSummaryRespBO monthData = tradeStatisticsService.getTradeSummaryByMonths(0); + // 2.2 上月数据(用于对照本月的数据) + TradeSummaryRespBO lastMonthData = tradeStatisticsService.getTradeSummaryByMonths(-1); + // 拼接数据 + return success(TradeStatisticsConvert.INSTANCE.convert(yesterdayData, beforeYesterdayData, monthData, lastMonthData)); + } + + // TODO @疯狂:【晚点再改和讨论;等首页的接口出来】这个要不还是叫 analyse,对比选中的时间段,和上一个时间段;类似 MemberStatisticsController 的 getMemberAnalyse + @GetMapping("/trend/summary") + @Operation(summary = "获得交易状况统计") + @PreAuthorize("@ss.hasPermission('statistics:trade:query')") + public CommonResult> getTradeTrendSummaryComparison( + TradeTrendReqVO reqVO) { + return success(tradeStatisticsService.getTradeTrendSummaryComparison(ArrayUtil.get(reqVO.getTimes(), 0), + ArrayUtil.get(reqVO.getTimes(), 1))); + } + + // TODO 芋艿:已经 review + @GetMapping("/list") + @Operation(summary = "获得交易状况明细") + @PreAuthorize("@ss.hasPermission('statistics:trade:query')") + public CommonResult> getTradeStatisticsList(TradeTrendReqVO reqVO) { + List list = tradeStatisticsService.getTradeStatisticsList(ArrayUtil.get(reqVO.getTimes(), 0), + ArrayUtil.get(reqVO.getTimes(), 1)); + return success(TradeStatisticsConvert.INSTANCE.convertList(list)); + } + + // TODO 芋艿:已经 review + @GetMapping("/export-excel") + @Operation(summary = "导出获得交易状况明细 Excel") + @PreAuthorize("@ss.hasPermission('statistics:trade:export')") + public void exportTradeStatisticsExcel(TradeTrendReqVO reqVO, HttpServletResponse response) throws IOException { + List list = tradeStatisticsService.getTradeStatisticsList(ArrayUtil.get(reqVO.getTimes(), 0), + ArrayUtil.get(reqVO.getTimes(), 1)); + // 导出 Excel + List voList = TradeStatisticsConvert.INSTANCE.convertList(list); + List data = TradeStatisticsConvert.INSTANCE.convertList02(voList); + ExcelUtils.write(response, "交易状况.xls", "数据", TradeTrendSummaryExcelVO.class, data); + } + + // TODO 芋艿:已经 review + @GetMapping("/order-count") + @Operation(summary = "获得交易订单数量") + @PreAuthorize("@ss.hasPermission('statistics:trade:query')") + public CommonResult getOrderCount() { + // 订单统计 + Long undeliveredCount = tradeOrderStatisticsService.getCountByStatusAndDeliveryType( + TradeOrderStatusEnum.UNDELIVERED.getStatus(), DeliveryTypeEnum.EXPRESS.getType()); + // TODO @疯狂:订单支付后,如果是门店自提的,需要 update 成 DELIVERED;;目前还没搞~~突然反应过来 + Long pickUpCount = tradeOrderStatisticsService.getCountByStatusAndDeliveryType( + TradeOrderStatusEnum.DELIVERED.getStatus(), DeliveryTypeEnum.PICK_UP.getType()); + // 售后统计 + Long afterSaleApplyCount = afterSaleStatisticsService.getCountByStatus(AfterSaleStatusEnum.APPLY); + Long auditingWithdrawCount = brokerageStatisticsService.getWithdrawCountByStatus(BrokerageWithdrawStatusEnum.AUDITING); + // 拼接返回 + return success(TradeStatisticsConvert.INSTANCE.convert(undeliveredCount, pickUpCount, afterSaleApplyCount, auditingWithdrawCount)); + } + + // TODO 芋艿:已经 review + @GetMapping("/order-comparison") + @Operation(summary = "获得交易订单数量") + @PreAuthorize("@ss.hasPermission('statistics:trade:query')") + public CommonResult> getOrderComparison() { + return success(tradeOrderStatisticsService.getOrderComparison()); + } + + // TODO 芋艿:已经 review + @GetMapping("/order-count-trend") + @Operation(summary = "获得订单量趋势统计") + @PreAuthorize("@ss.hasPermission('statistics:trade:query')") + public CommonResult>> getOrderCountTrendComparison(@Valid TradeOrderTrendReqVO reqVO) { + // TODO @疯狂:要注意 date 的排序; + return success(tradeOrderStatisticsService.getOrderCountTrendComparison(reqVO)); + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderCountRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderCountRespVO.java new file mode 100644 index 000000000..1320a889e --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderCountRespVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.trade.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 交易订单数量 Response VO") +@Data +public class TradeOrderCountRespVO { + + @Schema(description = "待发货", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long undelivered; + + @Schema(description = "待核销", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long pickUp; + + @Schema(description = "退款中", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long afterSaleApply; + + @Schema(description = "提现待审核", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long auditingWithdraw; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderSummaryRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderSummaryRespVO.java new file mode 100644 index 000000000..22d8f4a14 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderSummaryRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.trade.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 交易订单统计 Response VO") +@Data +public class TradeOrderSummaryRespVO { + + @Schema(description = "支付订单商品数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer orderPayCount; + + @Schema(description = "总支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer orderPayPrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderTrendReqVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderTrendReqVO.java new file mode 100644 index 000000000..57f054629 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderTrendReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.trade.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.statistics.enums.TimeRangeTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 交易订单量趋势统计 Request VO") +@Data +public class TradeOrderTrendReqVO { + + @Schema(description = "日期范围类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "日期范围类型不能为空") + @InEnum(value = TimeRangeTypeEnum.class, message = "日期范围类型,必须是 {value}") + private Integer type; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "起始时间") + private LocalDateTime beginTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "截止时间") + private LocalDateTime endTime; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderTrendRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderTrendRespVO.java new file mode 100644 index 000000000..d69c343cb --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeOrderTrendRespVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.trade.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 订单量趋势统计 Response VO") +@Data +public class TradeOrderTrendRespVO { + + @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String date; + + @Schema(description = "订单数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer orderPayCount; + + @Schema(description = "订单支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer orderPayPrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeSummaryRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeSummaryRespVO.java new file mode 100644 index 000000000..5e0e7c62d --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeSummaryRespVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.trade.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 交易统计 Response VO") +@Data +public class TradeSummaryRespVO { + + @Schema(description = "昨日订单数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer yesterdayOrderCount; + @Schema(description = "昨日支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer yesterdayPayPrice; + + @Schema(description = "本月订单数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer monthOrderCount; + @Schema(description = "本月支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer monthPayPrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeTrendReqVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeTrendReqVO.java new file mode 100644 index 000000000..234b7a785 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeTrendReqVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.trade.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 交易状况 Request VO") +@Data +public class TradeTrendReqVO { + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "时间范围") + private LocalDateTime[] times; +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeTrendSummaryExcelVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeTrendSummaryExcelVO.java new file mode 100644 index 000000000..5b14fa1d4 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeTrendSummaryExcelVO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.trade.vo; + +import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.format.DateTimeFormat; +import lombok.Data; + +import java.time.LocalDate; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; + +/** + * 交易状况统计 Excel VO + * + * @author owen + */ +@Data +public class TradeTrendSummaryExcelVO { + + @ExcelProperty(value = "日期") + @DateTimeFormat(FORMAT_YEAR_MONTH_DAY) + private LocalDate date; + + @ExcelProperty(value = "营业额", converter = MoneyConvert.class) + private Integer turnoverPrice; + + @ExcelProperty(value = "商品支付金额", converter = MoneyConvert.class) + private Integer orderPayPrice; + + @ExcelProperty(value = "充值金额", converter = MoneyConvert.class) + private Integer rechargePrice; + + @ExcelProperty(value = "支出金额", converter = MoneyConvert.class) + private Integer expensePrice; + + @ExcelProperty(value = "余额支付金额", converter = MoneyConvert.class) + private Integer walletPayPrice; + + @ExcelProperty(value = "支付佣金金额", converter = MoneyConvert.class) + private Integer brokerageSettlementPrice; + + @ExcelProperty(value = "商品退款金额", converter = MoneyConvert.class) + private Integer afterSaleRefundPrice; +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeTrendSummaryRespVO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeTrendSummaryRespVO.java new file mode 100644 index 000000000..f76d02e91 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/trade/vo/TradeTrendSummaryRespVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.statistics.controller.admin.trade.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDate; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; + +@Schema(description = "管理后台 - 交易状况统计 Response VO") +@Data +public class TradeTrendSummaryRespVO { + + @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate date; + + @Schema(description = "营业额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer turnoverPrice; // 营业额 = 商品支付金额 + 充值金额 + + @Schema(description = "订单支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer orderPayPrice; + + @Schema(description = "余额支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer walletPayPrice; + + @Schema(description = "订单退款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer afterSaleRefundPrice; + + @Schema(description = "支付佣金金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer brokerageSettlementPrice; + + @Schema(description = "充值金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer rechargePrice; + + @Schema(description = "支出金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer expensePrice; // 余额支付金额 + 支付佣金金额 + 商品退款金额 + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/app/package-info.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/app/package-info.java new file mode 100644 index 000000000..1384194b4 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/app/package-info.java @@ -0,0 +1,4 @@ +/** + * TODO 芋艿:占位 + */ +package cn.iocoder.yudao.module.statistics.controller.app; diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/member/MemberStatisticsConvert.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/member/MemberStatisticsConvert.java new file mode 100644 index 000000000..2d527b0a3 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/member/MemberStatisticsConvert.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.statistics.convert.member; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.ip.core.Area; +import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAnalyseDataRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAnalyseRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberAreaStatisticsRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSummaryRespVO; +import cn.iocoder.yudao.module.statistics.service.member.bo.MemberAreaStatisticsRespBO; +import cn.iocoder.yudao.module.statistics.service.pay.bo.RechargeSummaryRespBO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * 会员统计 Convert + * + * @author owen + */ +@Mapper +public interface MemberStatisticsConvert { + + MemberStatisticsConvert INSTANCE = Mappers.getMapper(MemberStatisticsConvert.class); + + default List convertList(List areaList, + Map userCountMap, + Map orderMap) { + return CollectionUtils.convertList(areaList, area -> { + MemberAreaStatisticsRespBO orderVo = Optional.ofNullable(orderMap.get(area.getId())) + .orElseGet(MemberAreaStatisticsRespBO::new); + return new MemberAreaStatisticsRespVO() + .setAreaId(area.getId()).setAreaName(area.getName()) + .setUserCount(MapUtil.getInt(userCountMap, area.getId(), 0)) + .setOrderCreateUserCount(ObjUtil.defaultIfNull(orderVo.getOrderCreateUserCount(), 0)) + .setOrderPayUserCount(ObjUtil.defaultIfNull(orderVo.getOrderPayUserCount(), 0)) + .setOrderPayPrice(ObjUtil.defaultIfNull(orderVo.getOrderPayPrice(), 0)); + }); + } + + MemberSummaryRespVO convert(RechargeSummaryRespBO rechargeSummary, Integer expensePrice, Integer userCount); + + MemberAnalyseRespVO convert(Integer visitUserCount, Integer orderUserCount, Integer payUserCount, int atv, + DataComparisonRespVO comparison); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/pay/PayStatisticsConvert.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/pay/PayStatisticsConvert.java new file mode 100644 index 000000000..08f38005f --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/pay/PayStatisticsConvert.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.statistics.convert.pay; + +import cn.iocoder.yudao.module.statistics.controller.admin.pay.vo.PaySummaryRespVO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * 支付统计 Convert + * + * @author owen + */ +@Mapper +public interface PayStatisticsConvert { + + PayStatisticsConvert INSTANCE = Mappers.getMapper(PayStatisticsConvert.class); + + PaySummaryRespVO convert(Integer rechargePrice); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/trade/TradeStatisticsConvert.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/trade/TradeStatisticsConvert.java new file mode 100644 index 000000000..7c140628f --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/convert/trade/TradeStatisticsConvert.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.statistics.convert.trade; + +import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeOrderCountRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeSummaryRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryExcelVO; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryRespVO; +import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeOrderSummaryRespBO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeSummaryRespBO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO; +import org.mapstruct.IterableMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 交易统计 Convert + * + * @author owen + */ +@Mapper +public interface TradeStatisticsConvert { + + TradeStatisticsConvert INSTANCE = Mappers.getMapper(TradeStatisticsConvert.class); + + default DataComparisonRespVO convert(TradeSummaryRespBO yesterdayData, + TradeSummaryRespBO beforeYesterdayData, + TradeSummaryRespBO monthData, + TradeSummaryRespBO lastMonthData) { + return convert(convert(yesterdayData, monthData), convert(beforeYesterdayData, lastMonthData)); + } + + + default TradeSummaryRespVO convert(TradeSummaryRespBO yesterdayData, TradeSummaryRespBO monthData) { + return new TradeSummaryRespVO() + .setYesterdayOrderCount(yesterdayData.getCount()).setYesterdayPayPrice(yesterdayData.getSummary()) + .setMonthOrderCount(monthData.getCount()).setMonthPayPrice(monthData.getSummary()); + } + + DataComparisonRespVO convert(TradeSummaryRespVO value, TradeSummaryRespVO reference); + + DataComparisonRespVO convert(TradeTrendSummaryRespVO value, + TradeTrendSummaryRespVO reference); + + List convertList02(List list); + + TradeStatisticsDO convert(LocalDateTime time, TradeOrderSummaryRespBO orderSummary, + AfterSaleSummaryRespBO afterSaleSummary, Integer brokerageSettlementPrice, + WalletSummaryRespBO walletSummary); + + @IterableMapping(qualifiedByName = "convert") + List convertList(List list); + + TradeTrendSummaryRespVO convertA(TradeStatisticsDO tradeStatistics); + + @Named("convert") + default TradeTrendSummaryRespVO convert(TradeStatisticsDO tradeStatistics) { + TradeTrendSummaryRespVO vo = convertA(tradeStatistics); + return vo + .setDate(tradeStatistics.getTime().toLocalDate()) + // 营业额 = 商品支付金额 + 充值金额 + .setTurnoverPrice(tradeStatistics.getOrderPayPrice() + tradeStatistics.getRechargePayPrice()) + // 支出金额 = 余额支付金额 + 支付佣金金额 + 商品退款金额 + .setExpensePrice(tradeStatistics.getWalletPayPrice() + tradeStatistics.getBrokerageSettlementPrice() + tradeStatistics.getAfterSaleRefundPrice()); + } + + TradeOrderCountRespVO convert(Long undelivered, Long pickUp, Long afterSaleApply, Long auditingWithdraw); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/dataobject/member/MemberStatisticsDO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/dataobject/member/MemberStatisticsDO.java new file mode 100644 index 000000000..af980b5c1 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/dataobject/member/MemberStatisticsDO.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.statistics.dal.dataobject.member; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 会员统计 DO + *

+ * 以天为维度,统计全部的数据 + * + * @author 芋道源码 + */ +@TableName("member_statistics") +@KeySequence("member_statistics_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MemberStatisticsDO extends BaseDO { + + /** + * 编号,主键自增 + */ + @TableId + private Long id; + + /** + * 统计日期 + */ + private LocalDateTime time; + + /** + * 注册用户数量 + */ + private Integer userRegisterCount; + /** + * 访问用户数量(UV) + */ + private Integer userVisitCount; + /** + * 访问页面数量(PV) + */ + private Integer pageVisitCount; + + /** + * 充值用户数量 + */ + private Integer rechargeUserCount; + + /** + * 创建订单用户数 + */ + private Integer orderCreateUserCount; + /** + * 支付订单用户数 + */ + private Integer orderPayUserCount; + /** + * 总支付金额,单位:分 + */ + private Integer orderPayPrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/dataobject/package-info.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/dataobject/package-info.java new file mode 100644 index 000000000..80eca3b41 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/dataobject/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 todo + */ +package cn.iocoder.yudao.module.statistics.dal.dataobject; diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/dataobject/trade/TradeStatisticsDO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/dataobject/trade/TradeStatisticsDO.java new file mode 100644 index 000000000..46e148aa4 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/dataobject/trade/TradeStatisticsDO.java @@ -0,0 +1,89 @@ +package cn.iocoder.yudao.module.statistics.dal.dataobject.trade; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 交易统计 DO + *

+ * 以天为维度,统计全部的数据 + * + * @author 芋道源码 + */ +@TableName("trade_statistics") +@KeySequence("trade_statistics_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TradeStatisticsDO extends BaseDO { + + /** + * 编号,主键自增 + */ + @TableId + private Long id; + + /** + * 统计日期 + */ + private LocalDateTime time; + + /** + * 创建订单数 + */ + private Integer orderCreateCount; + /** + * 支付订单商品数 + */ + private Integer orderPayCount; + /** + * 总支付金额,单位:分 + */ + private Integer orderPayPrice; + + /** + * 退款订单数 + */ + private Integer afterSaleCount; + /** + * 总退款金额,单位:分 + */ + private Integer afterSaleRefundPrice; + + /** + * 佣金金额(已结算),单位:分 + */ + private Integer brokerageSettlementPrice; + + /** + * 总支付金额(余额),单位:分 + */ + private Integer walletPayPrice; + /** + * 充值订单数 + *

+ * 从 PayWalletRechargeDO 计算 + */ + private Integer rechargePayCount; + /** + * 充值金额,单位:分 + */ + private Integer rechargePayPrice; + /** + * 充值退款订单数 + */ + private Integer rechargeRefundCount; + /** + * 充值退款金额,单位:分 + */ + private Integer rechargeRefundPrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/infra/ApiAccessLogStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/infra/ApiAccessLogStatisticsMapper.java new file mode 100644 index 000000000..cf7f98a0e --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/infra/ApiAccessLogStatisticsMapper.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.statistics.dal.mysql.infra; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; + +// TODO @芋艿:api 访问日志,现在会清理,可能要单独有个偏业务的访问表; +/** + * API 访问日志的统计 Mapper + * + * @author owen + */ +@Mapper +@SuppressWarnings("rawtypes") +public interface ApiAccessLogStatisticsMapper extends BaseMapperX { + + // TODO 芋艿:已经 review + Integer selectIpCountByUserTypeAndCreateTimeBetween(@Param("userType") Integer userType, + @Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + // TODO 芋艿:已经 review + Integer selectUserCountByUserTypeAndCreateTimeBetween(@Param("userType") Integer userType, + @Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/member/MemberStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/member/MemberStatisticsMapper.java new file mode 100644 index 000000000..1d215b818 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/member/MemberStatisticsMapper.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.statistics.dal.mysql.member; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberRegisterCountRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberSexStatisticsRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberTerminalStatisticsRespVO; +import cn.iocoder.yudao.module.statistics.service.member.bo.MemberAreaStatisticsRespBO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 会员信息的统计 Mapper + * + * @author owen + */ +@Mapper +@SuppressWarnings("rawtypes") +public interface MemberStatisticsMapper extends BaseMapperX { + + // TODO @芋艿:已经 review + List selectSummaryListByAreaId(); + + // TODO @芋艿:已经 review + List selectSummaryListBySex(); + + // TODO @芋艿:已经 review + List selectSummaryListByRegisterTerminal(); + + // TODO @芋艿:已经 review + Integer selectUserCount(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + // TODO @芋艿:已经 review + /** + * 获得用户的每天注册数量列表 + * + * @param beginTime 开始时间 + * @param endTime 结束时间 + * @return 每天注册数量列表 + */ + List selectListByCreateTimeBetween(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/pay/PayWalletStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/pay/PayWalletStatisticsMapper.java new file mode 100644 index 000000000..90ab0e0c6 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/pay/PayWalletStatisticsMapper.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.statistics.dal.mysql.pay; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.statistics.service.pay.bo.RechargeSummaryRespBO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; + +/** + * 支付钱包的统计 Mapper + * + * @author owen + */ +@Mapper +@SuppressWarnings("rawtypes") +public interface PayWalletStatisticsMapper extends BaseMapperX { + + // TODO 芋艿:已经 review; + WalletSummaryRespBO selectRechargeSummaryByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime, + @Param("payStatus") Boolean payStatus); + + // TODO 芋艿:已经 review; + WalletSummaryRespBO selectRechargeSummaryByRefundTimeBetween(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime, + @Param("refundStatus") Integer refundStatus); + + // TODO 芋艿:已经 review; + Integer selectPriceSummaryByBizTypeAndCreateTimeBetween(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime, + @Param("bizType") Integer bizType); + + // TODO 芋艿:已经 review; + RechargeSummaryRespBO selectRechargeSummaryGroupByWalletId(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime, + @Param("payStatus") Boolean payStatus); + + // TODO 芋艿:已经 review; + Integer selectRechargePriceSummary(@Param("payStatus") Integer payStatus); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/product/ProductSpuStatisticsDO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/product/ProductSpuStatisticsDO.java new file mode 100644 index 000000000..d29d4332b --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/product/ProductSpuStatisticsDO.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.statistics.dal.mysql.product; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 商品 SPU 统计 DO + * + * 以天为维度,统计商品 SPU 的数据 + * + * @author 芋道源码 + */ +@TableName("product_spu_statistics") +@KeySequence("product_spu_statistics_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductSpuStatisticsDO extends BaseDO { + + /** + * 编号,主键自增 + */ + @TableId + private Long id; + + /** + * 商品 SPU 编号 + * + * 关联 ProductSpuDO 的 id 字段 + */ + private Long spuId; + /** + * 统计日期 + */ + private LocalDateTime time; + + /** + * 浏览量 + */ + private Integer browseCount; + /** + * 收藏量 + */ + private Integer favoriteCount; + + /** + * 添加购物车次数 + * + * 以商品被添加到购物车的 createTime 计算,后续多次添加,不会增加该值。 + * 直到该次被下单、或者被删除,后续再次被添加到购物车。 + */ + private Integer addCartCount; + /** + * 创建订单商品数 + */ + private Integer createOrderCount; + /** + * 支付订单商品数 + */ + private Integer payOrderCount; + /** + * 总支付金额,单位:分 + */ + private Integer payPrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/product/ProductStatisticsDO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/product/ProductStatisticsDO.java new file mode 100644 index 000000000..5937b41da --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/product/ProductStatisticsDO.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.statistics.dal.mysql.product; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 商品统计 DO + * + * 以天为维度,统计全部的数据 + * + * 和 {@link ProductSpuStatisticsDO} 的差异是,它是全局的统计 + * + * @author 芋道源码 + */ +@TableName("product_spu_statistics") +@KeySequence("product_spu_statistics_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductStatisticsDO extends BaseDO { + + /** + * 编号,主键自增 + */ + @TableId + private Long id; + + /** + * 统计日期 + */ + private LocalDateTime time; + + /** + * 浏览量 + */ + private Integer browseCount; + /** + * 收藏量 + */ + private Integer favoriteCount; + + /** + * 添加购物车次数 + * + * 以商品被添加到购物车的 createTime 计算,后续多次添加,不会增加该值。 + * 直到该次被下单、或者被删除,后续再次被添加到购物车。 + */ + private Integer addCartCount; + /** + * 创建订单商品数 + */ + private Integer createOrderCount; + /** + * 支付订单商品数 + */ + private Integer payOrderCount; + /** + * 总支付金额,单位:分 + */ + private Integer payPrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/AfterSaleStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/AfterSaleStatisticsMapper.java new file mode 100644 index 000000000..1d2bb8814 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/AfterSaleStatisticsMapper.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.statistics.dal.mysql.trade; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; + +/** + * 售后订单的统计 Mapper + * + * @author owen + */ +@Mapper +public interface AfterSaleStatisticsMapper extends BaseMapperX { + + // TODO 芋艿:已 review + AfterSaleSummaryRespBO selectSummaryByRefundTimeBetween(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + // TODO 芋艿:已经 review + Long selectCountByStatus(@Param("status") Integer status); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/BrokerageStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/BrokerageStatisticsMapper.java new file mode 100644 index 000000000..46d651a75 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/BrokerageStatisticsMapper.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.statistics.dal.mysql.trade; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; + +/** + * 订单分销的统计 Mapper + * + * @author owen + */ +@Mapper +public interface BrokerageStatisticsMapper extends BaseMapperX { + + // TODO 芋艿:已经 review + Integer selectSummaryPriceByStatusAndUnfreezeTimeBetween(@Param("bizType") Integer bizType, + @Param("status") Integer status, + @Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + // TODO 芋艿:已经 review + Long selectWithdrawCountByStatus(@Param("status") Integer status); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeOrderStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeOrderStatisticsMapper.java new file mode 100644 index 000000000..43b4c4dfd --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeOrderStatisticsMapper.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.statistics.dal.mysql.trade; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeOrderSummaryRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeOrderTrendRespVO; +import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO; +import cn.iocoder.yudao.module.statistics.service.member.bo.MemberAreaStatisticsRespBO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 交易订单的统计 Mapper + * + * @author owen + */ +@Mapper +public interface TradeOrderStatisticsMapper extends BaseMapperX { + + // TODO 芋艿:已经 review + List selectSummaryListByAreaId(); + + // TODO 芋艿:已经 review + Integer selectCountByCreateTimeBetween(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + // TODO 芋艿:已经 review + Integer selectCountByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + // TODO 芋艿:已经 review + Integer selectSummaryPriceByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + // TODO 芋艿:已经 review + Integer selectUserCountByCreateTimeBetween(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + // TODO 芋艿:已经 review + Integer selectUserCountByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + // TODO 芋艿:已经 review + /** + * 按照支付时间统计订单(按天分组) + * + * @param beginTime 支付起始时间 + * @param endTime 支付截止时间 + * @return 订单统计列表 + */ + List selectListByPayTimeBetweenAndGroupByDay(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + // TODO 芋艿:已经 review + /** + * 按照支付时间统计订单(按月分组) + * + * @param beginTime 支付起始时间 + * @param endTime 支付截止时间 + * @return 订单统计列表 + */ + List selectListByPayTimeBetweenAndGroupByMonth(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + // TODO @芋艿:已经 review + Long selectCountByStatusAndDeliveryType(@Param("status") Integer status, @Param("deliveryType") Integer deliveryType); + + // TODO 芋艿:已经 review + TradeOrderSummaryRespVO selectPaySummaryByStatusAndPayTimeBetween(@Param("status") Integer status, + @Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeStatisticsMapper.java new file mode 100644 index 000000000..d5ed54cec --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeStatisticsMapper.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.statistics.dal.mysql.trade; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryRespVO; +import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeSummaryRespBO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 交易统计 Mapper + * + * @author owen + */ +@Mapper +public interface TradeStatisticsMapper extends BaseMapperX { + + TradeSummaryRespBO selectOrderCreateCountSumAndOrderPayPriceSumByTimeBetween(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + TradeTrendSummaryRespVO selectVoByTimeBetween(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + // TODO @芋艿:已经 review + default List selectListByTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) { + return selectList(new LambdaQueryWrapperX() + .between(TradeStatisticsDO::getTime, beginTime, endTime)); + } + + // TODO @芋艿:已经 review + Integer selectExpensePriceByTimeBetween(@Param("beginTime") LocalDateTime beginTime, + @Param("endTime") LocalDateTime endTime); + + // TODO @芋艿:已经 review + default TradeStatisticsDO selectByTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) { + return selectOne(new LambdaQueryWrapperX() + .between(TradeStatisticsDO::getTime, beginTime, endTime)); + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/package-info.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/package-info.java new file mode 100644 index 000000000..e8cf302e1 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/package-info.java @@ -0,0 +1,4 @@ +/** + * TODO 芋艿,占坑,无特殊含义 + */ +package cn.iocoder.yudao.module.statistics.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 new file mode 100644 index 000000000..88e9f0c79 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/job/trade/TradeStatisticsJob.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.statistics.job.trade; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.statistics.service.trade.TradeStatisticsService; +import com.xxl.job.core.handler.annotation.XxlJob; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +// TODO 芋艿:缺个 Job 的配置;等和 Product 一起配置 +/** + * 交易统计 Job + * + * @author owen + */ +@Component +public class TradeStatisticsJob { + + @Resource + private TradeStatisticsService tradeStatisticsService; + + /** + * 执行交易统计任务 + * + * @param param 要统计的天数,只能是正整数,1 代表昨日数据 + * @return 统计结果 + */ + @XxlJob("tradeStatisticsJob") + @TenantJob + public String execute(String param) { + // 默认昨日 + param = ObjUtil.defaultIfBlank(param, "1"); + // 校验参数的合理性 + if (!NumberUtil.isInteger(param)) { + throw new RuntimeException("交易统计任务的参数只能为是正整数"); + } + Integer days = Convert.toInt(param, 0); + if (days < 1) { + throw new RuntimeException("交易统计任务的参数只能为是正整数"); + } + String result = tradeStatisticsService.statisticsTrade(days); + return StrUtil.format("交易统计:\n{}", result); + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/package-info.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/package-info.java new file mode 100644 index 000000000..598e16c02 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/package-info.java @@ -0,0 +1,8 @@ +/** + * statistics 模块,主要实现统计相关功能。 + * 例如:统计商品、会员、交易等功能。 + * + * 1. Controller URL:以 /statistics/ 开头,避免和其它 Module 冲突 + * 2. DataObject 表名:以 statistics_ 为后缀,方便在数据库中区分【特殊】 + */ +package cn.iocoder.yudao.module.statistics; diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsService.java new file mode 100644 index 000000000..f4042730b --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsService.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.statistics.service.infra; + +import java.time.LocalDateTime; + +/** + * API 访问日志的统计 Service 接口 + * + * @author owen + */ +public interface ApiAccessLogStatisticsService { + + // TODO 芋艿:已经 review + /** + * 获取活跃用户数量 + * + * @param userType 用户类型 + * @param beginTime 起始时间 + * @param endTime 截止时间 + * @return 活跃用户数量 + */ + Integer getUserCount(Integer userType, LocalDateTime beginTime, LocalDateTime endTime); + + // TODO 芋艿:已经 review + /** + * 获取访问用户数量 + * + * @param userType 用户类型 + * @param beginTime 起始时间 + * @param endTime 截止时间 + * @return 访问用户数量 + */ + Integer getIpCount(Integer userType, LocalDateTime beginTime, LocalDateTime endTime); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsServiceImpl.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsServiceImpl.java new file mode 100644 index 000000000..7ad62d00a --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsServiceImpl.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.statistics.service.infra; + +import cn.iocoder.yudao.module.statistics.dal.mysql.infra.ApiAccessLogStatisticsMapper; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.time.LocalDateTime; + +/** + * API 访问日志的统计 Service 实现类 + * + * @author owen + */ +@Service +@Validated +public class ApiAccessLogStatisticsServiceImpl implements ApiAccessLogStatisticsService { + + @Resource + private ApiAccessLogStatisticsMapper apiAccessLogStatisticsMapper; + + @Override + public Integer getUserCount(Integer userType, LocalDateTime beginTime, LocalDateTime endTime) { + return apiAccessLogStatisticsMapper.selectUserCountByUserTypeAndCreateTimeBetween(userType, beginTime, endTime); + } + + @Override + public Integer getIpCount(Integer userType, LocalDateTime beginTime, LocalDateTime endTime) { + return apiAccessLogStatisticsMapper.selectIpCountByUserTypeAndCreateTimeBetween(userType, beginTime, endTime); + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsService.java new file mode 100644 index 000000000..324905e59 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsService.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.statistics.service.member; + +import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.*; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 会员信息的统计 Service 接口 + * + * @author owen + */ +public interface MemberStatisticsService { + + // TODO 芋艿:已经 review + /** + * 获取会员统计(实时统计) + * + * @return 会员统计 + */ + MemberSummaryRespVO getMemberSummary(); + + // TODO 芋艿:已经 review + /** + * 获取会员分析对照数据 + * + * @param beginTime 起始时间 + * @param endTime 截止时间 + * @return 会员分析对照数据 + */ + DataComparisonRespVO getMemberAnalyseComparisonData(LocalDateTime beginTime, + LocalDateTime endTime); + + // TODO 芋艿:已经 review + /** + * 按照省份,获得会员统计列表 + * + * @return 会员统计列表 + */ + List getMemberAreaStatisticsList(); + + // TODO 芋艿:已经 review + /** + * 按照性别,获得会员统计列表 + * + * @return 会员统计列表 + */ + List getMemberSexStatisticsList(); + + /** + * 按照终端,获得会员统计列表 + * + * @return 会员统计列表 + */ + List getMemberTerminalStatisticsList(); + + // TODO 芋艿:已经 review + /** + * 获取用户注册数量列表 + * + * @param beginTime 起始时间 + * @param endTime 截止时间 + * @return 注册数量列表 + */ + List getMemberRegisterCountList(LocalDateTime beginTime, LocalDateTime endTime); + + // TODO 芋艿:已经 review + /** + * 获得用户数量量统计对照 + * + * @return 用户数量量统计对照 + */ + DataComparisonRespVO getUserCountComparison(); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsServiceImpl.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsServiceImpl.java new file mode 100644 index 000000000..7b159059f --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsServiceImpl.java @@ -0,0 +1,136 @@ +package cn.iocoder.yudao.module.statistics.service.member; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.ip.core.Area; +import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum; +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; +import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.member.vo.*; +import cn.iocoder.yudao.module.statistics.convert.member.MemberStatisticsConvert; +import cn.iocoder.yudao.module.statistics.dal.mysql.member.MemberStatisticsMapper; +import cn.iocoder.yudao.module.statistics.service.infra.ApiAccessLogStatisticsService; +import cn.iocoder.yudao.module.statistics.service.member.bo.MemberAreaStatisticsRespBO; +import cn.iocoder.yudao.module.statistics.service.pay.PayWalletStatisticsService; +import cn.iocoder.yudao.module.statistics.service.pay.bo.RechargeSummaryRespBO; +import cn.iocoder.yudao.module.statistics.service.trade.TradeOrderStatisticsService; +import cn.iocoder.yudao.module.statistics.service.trade.TradeStatisticsService; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * 会员信息的统计 Service 实现类 + * + * @author owen + */ +@Service +@Validated +public class MemberStatisticsServiceImpl implements MemberStatisticsService { + + @Resource + private MemberStatisticsMapper memberStatisticsMapper; + + @Resource + private PayWalletStatisticsService payWalletStatisticsService; + @Resource + private TradeStatisticsService tradeStatisticsService; + @Resource + private TradeOrderStatisticsService tradeOrderStatisticsService; + @Resource + private ApiAccessLogStatisticsService apiAccessLogStatisticsService; + + @Override + public MemberSummaryRespVO getMemberSummary() { + RechargeSummaryRespBO rechargeSummary = payWalletStatisticsService.getUserRechargeSummary(null, null); + // TODO @疯狂:1)这里是实时统计,不好走走 TradeStatistics 表;2)因为这个放在商城下,所以只考虑订单数据,即按照 trade_order 的 pay_price 并且已支付来计算; + Integer expensePrice = tradeStatisticsService.getExpensePrice(null, null); + Integer userCount = memberStatisticsMapper.selectUserCount(null, null); + return MemberStatisticsConvert.INSTANCE.convert(rechargeSummary, expensePrice, userCount); + } + + @Override + public List getMemberAreaStatisticsList() { + // 统计用户 + // TODO @疯狂:可能得把每个省的用户,都查询出来,然后去 order 那边 in;因为要按照这些人为基础来计算;;用户规模量大可能不太好,但是暂时就先这样搞吧 = = + Map userCountMap = convertMap(memberStatisticsMapper.selectSummaryListByAreaId(), + vo -> AreaUtils.getParentIdByType(vo.getAreaId(), AreaTypeEnum.PROVINCE), + MemberAreaStatisticsRespBO::getUserCount, Integer::sum); + // 统计订单 + Map orderMap = convertMap(tradeOrderStatisticsService.getSummaryListByAreaId(), + bo -> AreaUtils.getParentIdByType(bo.getAreaId(), AreaTypeEnum.PROVINCE), + bo -> bo, + (a, b) -> new MemberAreaStatisticsRespBO() + .setOrderCreateUserCount(a.getOrderCreateUserCount() + b.getOrderCreateUserCount()) + .setOrderPayUserCount(a.getOrderPayUserCount() + b.getOrderPayUserCount()) + .setOrderPayPrice(a.getOrderPayPrice() + b.getOrderPayPrice())); + // 拼接数据 + List areaList = AreaUtils.getByType(AreaTypeEnum.PROVINCE, area -> area); + areaList.add(new Area().setId(null).setName("未知")); + return MemberStatisticsConvert.INSTANCE.convertList(areaList, userCountMap, orderMap); + } + + @Override + public DataComparisonRespVO getMemberAnalyseComparisonData(LocalDateTime beginTime, LocalDateTime endTime) { + // 当前数据 + MemberAnalyseDataRespVO vo = getMemberAnalyseData(beginTime, endTime); + // 对照数据 + LocalDateTime referenceEndDate = beginTime.minusDays(1); // 减少1天,防止出现时间重叠 + LocalDateTime referenceBeginDate = referenceEndDate.minus(Duration.between(beginTime, endTime)); + MemberAnalyseDataRespVO reference = getMemberAnalyseData( + LocalDateTimeUtil.beginOfDay(referenceBeginDate), LocalDateTimeUtil.endOfDay(referenceEndDate)); + return new DataComparisonRespVO<>(vo, reference); + } + + private MemberAnalyseDataRespVO getMemberAnalyseData(LocalDateTime beginTime, LocalDateTime endTime) { + Integer rechargeUserCount = Optional.ofNullable(payWalletStatisticsService.getUserRechargeSummary(beginTime, endTime)) + .map(RechargeSummaryRespBO::getRechargeUserCount).orElse(0); + return new MemberAnalyseDataRespVO() + .setRegisterUserCount(memberStatisticsMapper.selectUserCount(beginTime, endTime)) + .setVisitUserCount(apiAccessLogStatisticsService.getUserCount(UserTypeEnum.MEMBER.getValue(), beginTime, endTime)) + .setRechargeUserCount(rechargeUserCount); + } + + @Override + public List getMemberSexStatisticsList() { + return memberStatisticsMapper.selectSummaryListBySex(); + } + + @Override + public List getMemberTerminalStatisticsList() { + return memberStatisticsMapper.selectSummaryListByRegisterTerminal(); + } + + @Override + public List getMemberRegisterCountList(LocalDateTime beginTime, LocalDateTime endTime) { + return memberStatisticsMapper.selectListByCreateTimeBetween(beginTime, endTime); + } + + @Override + public DataComparisonRespVO getUserCountComparison() { + // 今日时间范围 + LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); + LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(beginOfToday); + // 昨日时间范围 + LocalDateTime beginOfYesterday = LocalDateTimeUtil.beginOfDay(beginOfToday.minusDays(1)); + LocalDateTime endOfYesterday = LocalDateTimeUtil.endOfDay(beginOfYesterday); + return new DataComparisonRespVO() + .setValue(getUserCount(beginOfToday, endOfToday)) + .setReference(getUserCount(beginOfYesterday, endOfYesterday)); + } + + private MemberCountRespVO getUserCount(LocalDateTime beginTime, LocalDateTime endTime) { + return new MemberCountRespVO() + .setRegisterUserCount(memberStatisticsMapper.selectUserCount(beginTime, endTime)) + .setVisitUserCount(apiAccessLogStatisticsService.getIpCount(UserTypeEnum.MEMBER.getValue(), beginTime, endTime)); + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/bo/MemberAreaStatisticsRespBO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/bo/MemberAreaStatisticsRespBO.java new file mode 100644 index 000000000..6b2d9ceab --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/bo/MemberAreaStatisticsRespBO.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.statistics.service.member.bo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 会员地区统计 Response BO") +@Data +public class MemberAreaStatisticsRespBO { + + /** + * 省份编号 + */ + private Integer areaId; + /** + * 省份名称 + */ + private String areaName; + + /** + * 会员数量 + */ + private Integer userCount; + + /** + * 下单的会员数量 + */ + private Integer orderCreateUserCount; + /** + * 支付订单的会员数量 + */ + private Integer orderPayUserCount; + + /** + * 订单支付金额,单位:分 + */ + private Integer orderPayPrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsService.java new file mode 100644 index 000000000..4801ad186 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsService.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.statistics.service.pay; + +import cn.iocoder.yudao.module.statistics.service.pay.bo.RechargeSummaryRespBO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO; + +import java.time.LocalDateTime; + +/** + * 钱包的统计 Service 接口 + * + * @author owen + */ +public interface PayWalletStatisticsService { + + // TODO 芋艿:已经 review + /** + * 获取钱包统计 + * + * @param beginTime 起始时间 + * @param endTime 截止时间 + * @return 钱包统计 + */ + WalletSummaryRespBO getWalletSummary(LocalDateTime beginTime, LocalDateTime endTime); + + // TODO 芋艿:已经 review + /** + * 获取钱包充值统计 + * + * @param beginTime 起始时间 + * @param endTime 截止时间 + * @return 钱包充值统计 + */ + RechargeSummaryRespBO getUserRechargeSummary(LocalDateTime beginTime, LocalDateTime endTime); + + // TODO 芋艿:已经 review + /** + * 获取充值金额合计 + * + * @return 充值金额合计 + */ + Integer getRechargePriceSummary(); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsServiceImpl.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsServiceImpl.java new file mode 100644 index 000000000..0bbb0573d --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsServiceImpl.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.statistics.service.pay; + +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.statistics.dal.mysql.pay.PayWalletStatisticsMapper; +import cn.iocoder.yudao.module.statistics.service.pay.bo.RechargeSummaryRespBO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.time.LocalDateTime; + +/** + * 钱包的统计 Service 实现类 + * + * @author owen + */ +@Service +@Validated +public class PayWalletStatisticsServiceImpl implements PayWalletStatisticsService { + + @Resource + private PayWalletStatisticsMapper payWalletStatisticsMapper; + + @Override + public WalletSummaryRespBO getWalletSummary(LocalDateTime beginTime, LocalDateTime endTime) { + WalletSummaryRespBO paySummary = payWalletStatisticsMapper.selectRechargeSummaryByPayTimeBetween( + beginTime, endTime, true); + WalletSummaryRespBO refundSummary = payWalletStatisticsMapper.selectRechargeSummaryByRefundTimeBetween( + beginTime, endTime, PayRefundStatusEnum.SUCCESS.getStatus()); + Integer walletPayPrice = payWalletStatisticsMapper.selectPriceSummaryByBizTypeAndCreateTimeBetween( + beginTime, endTime, PayWalletBizTypeEnum.PAYMENT.getType()); + // 拼接 + paySummary.setWalletPayPrice(walletPayPrice) + .setRechargeRefundCount(refundSummary.getRechargeRefundCount()) + .setRechargeRefundPrice(refundSummary.getRechargeRefundPrice()); + return paySummary; + } + + @Override + public RechargeSummaryRespBO getUserRechargeSummary(LocalDateTime beginTime, LocalDateTime endTime) { + return payWalletStatisticsMapper.selectRechargeSummaryGroupByWalletId(beginTime, endTime, true); + } + + @Override + public Integer getRechargePriceSummary() { + return payWalletStatisticsMapper.selectRechargePriceSummary(PayOrderStatusEnum.SUCCESS.getStatus()); + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/bo/RechargeSummaryRespBO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/bo/RechargeSummaryRespBO.java new file mode 100644 index 000000000..05cfa1155 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/bo/RechargeSummaryRespBO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.statistics.service.pay.bo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 充值统计 Response BO + */ +@Data +public class RechargeSummaryRespBO { + + /** + * 充值会员数量 + */ + private Integer rechargeUserCount; + + /** + * 充值金额 + */ + private Integer rechargePrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsService.java new file mode 100644 index 000000000..3904387d6 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsService.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.statistics.service.trade; + +import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO; +import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleStatusEnum; + +import java.time.LocalDateTime; + +/** + * 售后统计 Service 接口 + * + * @author owen + */ +public interface AfterSaleStatisticsService { + + // TODO 芋艿:已经 review + /** + * 获取售后单统计 + * + * @param beginTime 起始时间 + * @param endTime 截止时间 + * @return 售后统计结果 + */ + AfterSaleSummaryRespBO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime); + + // TODO 芋艿:已经 review + /** + * 获取指定状态的售后订单数量 + * + * @param status 售后状态 + * @return 售后订单数量 + */ + Long getCountByStatus(AfterSaleStatusEnum status); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsServiceImpl.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsServiceImpl.java new file mode 100644 index 000000000..46ab5f3ad --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsServiceImpl.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.statistics.service.trade; + +import cn.iocoder.yudao.module.statistics.dal.mysql.trade.AfterSaleStatisticsMapper; +import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO; +import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleStatusEnum; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.time.LocalDateTime; + +/** + * 售后统计 Service 实现类 + * + * @author owen + */ +@Service +@Validated +public class AfterSaleStatisticsServiceImpl implements AfterSaleStatisticsService { + + @Resource + private AfterSaleStatisticsMapper afterSaleStatisticsMapper; + + @Override + public AfterSaleSummaryRespBO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime) { + return afterSaleStatisticsMapper.selectSummaryByRefundTimeBetween(beginTime, endTime); + } + + @Override + public Long getCountByStatus(AfterSaleStatusEnum status) { + return afterSaleStatisticsMapper.selectCountByStatus(status.getStatus()); + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsService.java new file mode 100644 index 000000000..5f856778d --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsService.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.statistics.service.trade; + +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum; + +import java.time.LocalDateTime; + +/** + * 分销统计 Service 接口 + * + * @author owen + */ +public interface BrokerageStatisticsService { + + // TODO 芋艿:已经 review + /** + * 获取已结算的佣金金额 + * + * @param beginTime 起始时间 + * @param endTime 截止时间 + * @return 已结算的佣金金额 + */ + Integer getBrokerageSettlementPriceSummary(LocalDateTime beginTime, LocalDateTime endTime); + + // TODO 芋艿:已经 review + /** + * 获取指定状态的提现记录数量 + * + * @param status 提现记录状态 + * @return 提现记录数量 + */ + Long getWithdrawCountByStatus(BrokerageWithdrawStatusEnum status); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsServiceImpl.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsServiceImpl.java new file mode 100644 index 000000000..d7af3d523 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsServiceImpl.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.statistics.service.trade; + +import cn.iocoder.yudao.module.statistics.dal.mysql.trade.BrokerageStatisticsMapper; +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum; +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum; +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.time.LocalDateTime; + +/** + * 分销统计 Service 实现类 + * + * @author owen + */ +@Service +@Validated +public class BrokerageStatisticsServiceImpl implements BrokerageStatisticsService { + + @Resource + private BrokerageStatisticsMapper brokerageStatisticsMapper; + + @Override + public Integer getBrokerageSettlementPriceSummary(LocalDateTime beginTime, LocalDateTime endTime) { + return brokerageStatisticsMapper.selectSummaryPriceByStatusAndUnfreezeTimeBetween( + BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(), + beginTime, endTime); + } + + @Override + public Long getWithdrawCountByStatus(BrokerageWithdrawStatusEnum status) { + return brokerageStatisticsMapper.selectWithdrawCountByStatus(status.getStatus()); + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsService.java new file mode 100644 index 000000000..982957e24 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsService.java @@ -0,0 +1,89 @@ +package cn.iocoder.yudao.module.statistics.service.trade; + +import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.*; +import cn.iocoder.yudao.module.statistics.service.member.bo.MemberAreaStatisticsRespBO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeOrderSummaryRespBO; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 交易订单的统计 Service 接口 + * + * @author owen + */ +public interface TradeOrderStatisticsService { + + /** + * 获取订单统计 + * + * @param beginTime 起始时间 + * @param endTime 截止时间 + * @return 订单统计结果 + */ + TradeOrderSummaryRespBO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime); + + // TODO 芋艿:已经 review + /** + * 获取地区订单统计 + * + * @return 订单统计结果 + */ + List getSummaryListByAreaId(); + + // TODO 芋艿:已经 review + /** + * 获取下单用户数量 + * + * @param beginTime 起始时间 + * @param endTime 截止时间 + * @return 下单用户数量 + */ + Integer getOrderUserCount(LocalDateTime beginTime, LocalDateTime endTime); + + // TODO 芋艿:已经 review + /** + * 获取支付用户数量 + * + * @param beginTime 起始时间 + * @param endTime 截止时间 + * @return 支付用户数量 + */ + Integer getPayUserCount(LocalDateTime beginTime, LocalDateTime endTime); + + // TODO 芋艿:已经 review + /** + * 获取支付金额 + * + * @param beginTime 起始时间 + * @param endTime 截止时间 + * @return 支付用户金额 + */ + Integer getOrderPayPrice(LocalDateTime beginTime, LocalDateTime endTime); + + /** + * 根据订单状态、物流类型,获得交易订单数量 + * + * @return 订单数量 + */ + Long getCountByStatusAndDeliveryType(Integer status, Integer deliveryType); + + // TODO 芋艿:已经 review + /** + * 交易订单销售额对照 + * + * @return 销售额对照 + */ + DataComparisonRespVO getOrderComparison(); + + // TODO 芋艿:已经 review + /** + * 获得订单量趋势统计 + * + * @param reqVO 统计参数 + * @return 订单量趋势统计 + */ + List> getOrderCountTrendComparison(TradeOrderTrendReqVO reqVO); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsServiceImpl.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsServiceImpl.java new file mode 100644 index 000000000..36131ec96 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsServiceImpl.java @@ -0,0 +1,108 @@ +package cn.iocoder.yudao.module.statistics.service.trade; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeOrderSummaryRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeOrderTrendReqVO; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeOrderTrendRespVO; +import cn.iocoder.yudao.module.statistics.dal.mysql.trade.TradeOrderStatisticsMapper; +import cn.iocoder.yudao.module.statistics.enums.TimeRangeTypeEnum; +import cn.iocoder.yudao.module.statistics.service.member.bo.MemberAreaStatisticsRespBO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeOrderSummaryRespBO; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * 交易订单统计 Service 实现类 + * + * @author owen + */ +@Service +@Validated +public class TradeOrderStatisticsServiceImpl implements TradeOrderStatisticsService { + + @Resource + private TradeOrderStatisticsMapper tradeOrderStatisticsMapper; + + @Override + public TradeOrderSummaryRespBO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime) { + return new TradeOrderSummaryRespBO() + .setOrderCreateCount(tradeOrderStatisticsMapper.selectCountByCreateTimeBetween(beginTime, endTime)) + .setOrderPayCount(tradeOrderStatisticsMapper.selectCountByPayTimeBetween(beginTime, endTime)) + .setOrderPayPrice(tradeOrderStatisticsMapper.selectSummaryPriceByPayTimeBetween(beginTime, endTime)); + } + + @Override + public List getSummaryListByAreaId() { + return tradeOrderStatisticsMapper.selectSummaryListByAreaId(); + } + + @Override + public Integer getOrderUserCount(LocalDateTime beginTime, LocalDateTime endTime) { + return tradeOrderStatisticsMapper.selectUserCountByCreateTimeBetween(beginTime, endTime); + } + + @Override + public Integer getPayUserCount(LocalDateTime beginTime, LocalDateTime endTime) { + return tradeOrderStatisticsMapper.selectUserCountByPayTimeBetween(beginTime, endTime); + } + + @Override + public Integer getOrderPayPrice(LocalDateTime beginTime, LocalDateTime endTime) { + return tradeOrderStatisticsMapper.selectSummaryPriceByPayTimeBetween(beginTime, endTime); + } + + @Override + public Long getCountByStatusAndDeliveryType(Integer status, Integer deliveryType) { + return tradeOrderStatisticsMapper.selectCountByStatusAndDeliveryType(status, deliveryType); + } + + @Override + public DataComparisonRespVO getOrderComparison() { + return new DataComparisonRespVO() + .setValue(getPayPriceSummary(LocalDateTime.now())) + .setReference(getPayPriceSummary(LocalDateTime.now().minusDays(1))); + } + + private TradeOrderSummaryRespVO getPayPriceSummary(LocalDateTime date) { + LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(date); + LocalDateTime endTime = LocalDateTimeUtil.beginOfDay(date); + return tradeOrderStatisticsMapper.selectPaySummaryByStatusAndPayTimeBetween( + PayOrderStatusEnum.SUCCESS.getStatus(), beginTime, endTime); + } + + @Override + public List> getOrderCountTrendComparison(TradeOrderTrendReqVO reqVO) { + // 查询当前数据 + List value = getOrderCountTrend(reqVO.getType(), reqVO.getBeginTime(), reqVO.getEndTime()); + // 查询对照数据 + LocalDateTime referenceEndTime = reqVO.getBeginTime().minusDays(1); + LocalDateTime referenceBeginTime = referenceEndTime.minus(Duration.between(reqVO.getBeginTime(), reqVO.getEndTime())); + List reference = getOrderCountTrend(reqVO.getType(), referenceBeginTime, referenceEndTime); + // 顺序对比返回 + return IntStream.range(0, value.size()) + .mapToObj(index -> new DataComparisonRespVO() + .setValue(CollUtil.get(value, index)) + .setReference(CollUtil.get(reference, index))) + .collect(Collectors.toList()); + } + + private List getOrderCountTrend(Integer timeRangeType, LocalDateTime beginTime, LocalDateTime endTime) { + // 情况一:按年统计时,以月份分组 + if (TimeRangeTypeEnum.YEAR.getType().equals(timeRangeType)) { + return tradeOrderStatisticsMapper.selectListByPayTimeBetweenAndGroupByMonth(beginTime, endTime); + } + // 情况二:其它以天分组(天、周、月) + return tradeOrderStatisticsMapper.selectListByPayTimeBetweenAndGroupByDay(beginTime, endTime); + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsService.java new file mode 100644 index 000000000..ec2e5bd5a --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsService.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.statistics.service.trade; + +import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryRespVO; +import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeSummaryRespBO; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 交易统计 Service 接口 + * + * @author owen + */ +public interface TradeStatisticsService { + + /** + * 获得交易状况统计对照 + * + * @return 统计数据对照 + */ + DataComparisonRespVO getTradeTrendSummaryComparison( + LocalDateTime beginTime, LocalDateTime endTime); + + /** + * 获得交易状况统计 + * + * @param beginTime 开始时间 + * @param endTime 结束时间 + * @return 统计数据对照 + */ + Integer getExpensePrice(LocalDateTime beginTime, LocalDateTime endTime); + + /** + * 获得交易状况明细 + * + * @param beginTime 开始时间 + * @param endTime 结束时间 + * @return 统计数据列表 + */ + List getTradeStatisticsList(LocalDateTime beginTime, LocalDateTime endTime); + + // TODO 芋艿:已经 review; + /** + * 统计指定天数的交易数据 + * + * @return 统计结果 + */ + String statisticsTrade(Integer days); + + // TODO 芋艿:已经 review + /** + * 统计指定日期的交易数据 + * + * @param days 增加的天数 + * @return 交易数据 + */ + TradeSummaryRespBO getTradeSummaryByDays(int days); + + // TODO 芋艿:已经 review + /** + * 统计指定月份的交易数据 + * + * @param months 增加的月数 + * @return 交易数据 + */ + TradeSummaryRespBO getTradeSummaryByMonths(int months); + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsServiceImpl.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsServiceImpl.java new file mode 100644 index 000000000..22436c78b --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsServiceImpl.java @@ -0,0 +1,135 @@ +package cn.iocoder.yudao.module.statistics.service.trade; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; +import cn.iocoder.yudao.module.statistics.controller.admin.common.vo.DataComparisonRespVO; +import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryRespVO; +import cn.iocoder.yudao.module.statistics.convert.trade.TradeStatisticsConvert; +import cn.iocoder.yudao.module.statistics.dal.dataobject.trade.TradeStatisticsDO; +import cn.iocoder.yudao.module.statistics.dal.mysql.trade.TradeStatisticsMapper; +import cn.iocoder.yudao.module.statistics.service.pay.PayWalletStatisticsService; +import cn.iocoder.yudao.module.statistics.service.trade.bo.AfterSaleSummaryRespBO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeOrderSummaryRespBO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.TradeSummaryRespBO; +import cn.iocoder.yudao.module.statistics.service.trade.bo.WalletSummaryRespBO; +import org.springframework.stereotype.Service; +import org.springframework.util.StopWatch; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * 交易统计 Service 实现类 + * + * @author owen + */ +@Service +@Validated +public class TradeStatisticsServiceImpl implements TradeStatisticsService { + + @Resource + private TradeStatisticsMapper tradeStatisticsMapper; + + @Resource + private TradeOrderStatisticsService tradeOrderStatisticsService; + @Resource + private AfterSaleStatisticsService afterSaleStatisticsService; + @Resource + private BrokerageStatisticsService brokerageStatisticsService; + @Resource + private PayWalletStatisticsService payWalletStatisticsService; + + @Override + public TradeSummaryRespBO getTradeSummaryByDays(int days) { + LocalDateTime date = LocalDateTime.now().plusDays(days); + return tradeStatisticsMapper.selectOrderCreateCountSumAndOrderPayPriceSumByTimeBetween( + LocalDateTimeUtil.beginOfDay(date), LocalDateTimeUtil.endOfDay(date)); + } + + @Override + public TradeSummaryRespBO getTradeSummaryByMonths(int months) { + LocalDateTime monthDate = LocalDateTime.now().plusMonths(months); + return tradeStatisticsMapper.selectOrderCreateCountSumAndOrderPayPriceSumByTimeBetween( + LocalDateTimeUtils.beginOfMonth(monthDate), LocalDateTimeUtils.endOfMonth(monthDate)); + } + + @Override + public DataComparisonRespVO getTradeTrendSummaryComparison(LocalDateTime beginTime, + LocalDateTime endTime) { + // 统计数据 + TradeTrendSummaryRespVO value = tradeStatisticsMapper.selectVoByTimeBetween(beginTime, endTime); + // 对照数据 + LocalDateTime referenceBeginTime = beginTime.minus(Duration.between(beginTime, endTime)); + TradeTrendSummaryRespVO reference = tradeStatisticsMapper.selectVoByTimeBetween(referenceBeginTime, beginTime); + return TradeStatisticsConvert.INSTANCE.convert(value, reference); + } + + @Override + public Integer getExpensePrice(LocalDateTime beginTime, LocalDateTime endTime) { + return tradeStatisticsMapper.selectExpensePriceByTimeBetween(beginTime, endTime); + } + + @Override + public List getTradeStatisticsList(LocalDateTime beginTime, LocalDateTime endTime) { + return tradeStatisticsMapper.selectListByTimeBetween(beginTime, endTime); + } + + @Override + public String statisticsTrade(Integer days) { + LocalDateTime today = LocalDateTime.now(); + return IntStream.rangeClosed(1, days) + .mapToObj(day -> statisticsTrade(today.minusDays(day))) + .sorted() + .collect(Collectors.joining("\n")); + } + + /** + * 统计交易数据 + * + * @param date 需要统计的日期 + * @return 统计结果 + */ + private String statisticsTrade(LocalDateTime date) { + // 1. 处理统计时间范围 + LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(date); + LocalDateTime endTime = LocalDateTimeUtil.endOfDay(date); + String dateStr = DatePattern.NORM_DATE_FORMAT.format(date); + // 2. 检查该日是否已经统计过 + TradeStatisticsDO entity = tradeStatisticsMapper.selectByTimeBetween(beginTime, endTime); + if (entity != null) { + return dateStr + " 数据已存在,如果需要重新统计,请先删除对应的数据"; + } + + // 3. 从各个数据表,统计对应数据 + StopWatch stopWatch = new StopWatch(dateStr); + // 3.1 统计订单 + stopWatch.start("统计订单"); + TradeOrderSummaryRespBO orderSummary = tradeOrderStatisticsService.getOrderSummary(beginTime, endTime); + stopWatch.stop(); + // 3.2 统计售后 + stopWatch.start("统计售后"); + AfterSaleSummaryRespBO afterSaleSummary = afterSaleStatisticsService.getAfterSaleSummary(beginTime, endTime); + stopWatch.stop(); + // 3.3 统计佣金 + stopWatch.start("统计佣金"); + Integer brokerageSettlementPrice = brokerageStatisticsService.getBrokerageSettlementPriceSummary(beginTime, endTime); + stopWatch.stop(); + // 3.4 统计充值 + stopWatch.start("统计充值"); + WalletSummaryRespBO walletSummary = payWalletStatisticsService.getWalletSummary(beginTime, endTime); + stopWatch.stop(); + + // 4. 插入数据 + entity = TradeStatisticsConvert.INSTANCE.convert(date, orderSummary, afterSaleSummary, brokerageSettlementPrice, + walletSummary); + tradeStatisticsMapper.insert(entity); + return stopWatch.prettyPrint(); + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/AfterSaleSummaryRespBO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/AfterSaleSummaryRespBO.java new file mode 100644 index 000000000..985643636 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/AfterSaleSummaryRespBO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.statistics.service.trade.bo; + +import lombok.Data; + +/** + * 售后统计 Response DTO + * + * @author owen + */ +@Data +public class AfterSaleSummaryRespBO { + + /** + * 退款订单数 + */ + private Integer afterSaleCount; + /** + * 总退款金额,单位:分 + */ + private Integer afterSaleRefundPrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/MemberAreaStatisticsRespBO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/MemberAreaStatisticsRespBO.java new file mode 100644 index 000000000..3d3572f90 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/MemberAreaStatisticsRespBO.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.statistics.service.trade.bo; + +import lombok.Data; + +/** + * 会员地区统计 Response BO + * + * @author owen + */ +@Data +public class MemberAreaStatisticsRespBO { + + /** + * 省份编号 + */ + private Integer areaId; + /** + * 省份名称 + */ + private String areaName; + + /** + * 会员数量 + */ + private Integer userCount; + + /** + * 下单的会员数量 + */ + private Integer orderCreateUserCount; + /** + * 支付订单的会员数量 + */ + private Integer orderPayUserCount; + + /** + * 订单支付金额,单位:分 + */ + private Integer orderPayPrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/TradeOrderSummaryRespBO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/TradeOrderSummaryRespBO.java new file mode 100644 index 000000000..bc4f3903b --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/TradeOrderSummaryRespBO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.statistics.service.trade.bo; + +import lombok.Data; + +/** + * 订单统计 Response BO + * + * @author owen + */ +@Data +public class TradeOrderSummaryRespBO { + + /** + * 创建订单数 + */ + private Integer orderCreateCount; + /** + * 支付订单商品数 + */ + private Integer orderPayCount; + /** + * 总支付金额,单位:分 + */ + private Integer orderPayPrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/TradeSummaryRespBO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/TradeSummaryRespBO.java new file mode 100644 index 000000000..8937c809c --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/TradeSummaryRespBO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.statistics.service.trade.bo; + +import lombok.Data; + +/** + * 交易统计 Resp BO + * + * @author owen + */ +@Data +public class TradeSummaryRespBO { + + /** + * 数量 + */ + private Integer count; + + /** + * 合计 + */ + private Integer summary; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/WalletSummaryRespBO.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/WalletSummaryRespBO.java new file mode 100644 index 000000000..89371f6c1 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/bo/WalletSummaryRespBO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.statistics.service.trade.bo; + +import lombok.Data; + +/** + * 钱包统计 Response DTO + * + * @author owen + */ +@Data +public class WalletSummaryRespBO { + + /** + * 总支付金额(余额),单位:分 + */ + private Integer walletPayPrice; + + /** + * 充值订单数 + */ + private Integer rechargePayCount; + /** + * 充值金额,单位:分 + */ + private Integer rechargePayPrice; + /** + * 充值退款订单数 + */ + private Integer rechargeRefundCount; + /** + * 充值退款金额,单位:分 + */ + private Integer rechargeRefundPrice; + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/application-dev.yaml b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/application-dev.yaml new file mode 100644 index 000000000..d8a81c9a3 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/application-dev.yaml @@ -0,0 +1,113 @@ +--- #################### 数据库相关配置 #################### +spring: + # 数据源配置项 + autoconfigure: + exclude: + - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源 + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 5 # 初始连接数 + min-idle: 10 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: master + datasource: + master: + name: ruoyi-vue-pro + url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&nullCatalogMeansCurrent=true + driver-class-name: com.mysql.jdbc.Driver + username: root + password: 3WLiVUBEwTbvAfsh + slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 + name: ruoyi-vue-pro + url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&nullCatalogMeansCurrent=true + driver-class-name: com.mysql.jdbc.Driver + username: root + password: 3WLiVUBEwTbvAfsh + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + redis: + host: 400-infra.server.iocoder.cn # 地址 + port: 6379 # 端口 + database: 1 # 数据库索引 +# password: 123456 # 密码,建议生产环境开启 + +--- #################### MQ 消息队列相关配置 #################### +spring: + cloud: + stream: + rocketmq: + # RocketMQ Binder 配置项,对应 RocketMQBinderConfigurationProperties 类 + binder: + name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址 + +--- #################### 定时任务相关配置 #################### +xxl: + job: + admin: + addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址 + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator + exposure: + include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + # Spring Boot Admin Client 客户端的相关配置 + client: + instance: + service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + # Spring Boot Admin Server 服务端的相关配置 + context-path: /admin # 配置 Spring + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + xss: + enable: false + web: + admin-ui: + url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址 + demo: true # 开启演示模式 diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/application-local.yaml b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/application-local.yaml new file mode 100644 index 000000000..1a2e58dbf --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/application-local.yaml @@ -0,0 +1,141 @@ +--- #################### 数据库相关配置 #################### +spring: + # 数据源配置项 + autoconfigure: + exclude: + - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源 + - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置 + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 1 # 初始连接数 + min-idle: 1 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: master + datasource: + master: + name: ruoyi-vue-pro + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 +# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 +# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例 +# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 +# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例 + username: root + password: 123456 +# username: sa +# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W + slave: # 模拟从库,可根据自己需要修改 + name: ruoyi-vue-pro + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 +# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 +# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 +# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 +# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例 + username: root + password: 123456 +# username: sa +# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + redis: + host: 127.0.0.1 # 地址 + port: 6379 # 端口 + database: 0 # 数据库索引 +# password: 123456 # 密码,建议生产环境开启 + +--- #################### MQ 消息队列相关配置 #################### +spring: + cloud: + stream: + rocketmq: + # RocketMQ Binder 配置项,对应 RocketMQBinderConfigurationProperties 类 + binder: + name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址 + binding-retry-interval: 7200 # 消息绑定重试间隔时间,单位:秒,默认为 30 秒。考虑到本地可能不启动 RocketMQ 服务,设置为 2 小时 + +--- #################### 定时任务相关配置 #################### + +xxl: + job: + enabled: false # 是否开启调度中心,默认为 true 开启 + admin: + addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址 + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator + exposure: + include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + # Spring Boot Admin Client 客户端的相关配置 + client: + instance: + service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + +# 日志文件配置 +logging: + level: + # 配置自己写的 MyBatis Mapper 打印日志 + cn.iocoder.yudao.module.system.dal.mysql: debug + cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper: INFO # 配置 SensitiveWordMapper 的日志级别为 info + cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper: INFO # 配置 SmsChannelMapper 的日志级别为 info + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + env: # 多环境的配置项 + tag: ${HOSTNAME} + web: + admin-ui: + url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址 + security: + mock-enable: true + xss: + enable: false + access-log: # 访问日志的配置项 + enable: false + error-code: # 错误码相关配置项 + enable: false + demo: false # 关闭演示模式 diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/application.yaml b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/application.yaml new file mode 100644 index 000000000..d72091fc1 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/application.yaml @@ -0,0 +1,134 @@ +spring: + main: + allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。 + allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务 + + # Servlet 配置 + servlet: + # 文件上传相关配置项 + multipart: + max-file-size: 16MB # 单个文件大小 + max-request-size: 32MB # 设置总上传的文件大小 + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER # 解决 SpringFox 与 SpringBoot 2.6.x 不兼容的问题,参见 SpringFoxHandlerProviderBeanPostProcessor 类 + + # Jackson 配置项 + jackson: + serialization: + write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳 + write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401 + write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳 + fail-on-empty-beans: false # 允许序列化无属性的 Bean + + # Cache 配置项 + cache: + type: REDIS + redis: + time-to-live: 1h # 设置过期时间为 1 小时 + +--- #################### 接口文档配置 #################### + +springdoc: + api-docs: + enabled: true # 1. 是否开启 Swagger 接文档的元数据 + path: /v3/api-docs + swagger-ui: + enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面 + path: /swagger-ui.html + default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档 + +knife4j: + enable: true # 2.2 是否开启 Swagger 文档的 Knife4j UI 界面 + setting: + language: zh_cn + +# MyBatis Plus 的配置项 +mybatis-plus: + configuration: + map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。 + global-config: + db-config: + id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。 + # id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库 + # id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 + # id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解 + logic-delete-value: 1 # 逻辑已删除值(默认为 1) + logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) + banner: false # 关闭控制台的 Banner 打印 + type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject + encryptor: + password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成 + +mybatis-plus-join: + banner: false # 关闭控制台的 Banner 打印 + +# Spring Data Redis 配置 +spring: + data: + redis: + repositories: + enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度 + +--- #################### RPC 远程调用相关配置 #################### + +--- #################### MQ 消息队列相关配置 #################### + +spring: + cloud: + # Spring Cloud Stream 配置项,对应 BindingServiceProperties 类 + stream: + function: + definition: busConsumer; + # Binding 配置项,对应 BindingProperties Map + # Spring Cloud Stream RocketMQ 配置项 + rocketmq: + # RocketMQ Binder 配置项,对应 RocketMQBinderConfigurationProperties 类 + binder: + name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址 + default: # 默认 bindings 全局配置 + producer: # RocketMQ Producer 配置项,对应 RocketMQProducerProperties 类 + group: statistics_producer_group # 生产者分组 + send-type: SYNC # 发送模式,SYNC 同步 + bindings: + springCloudBusInput: + consumer: + message-model: BROADCASTING # 重要,解决 Spring Cloud Bus RocketMQ 默认不是 BROADCASTING 广播消费的问题 + + # Spring Cloud Bus 配置项,对应 BusProperties 类 + bus: + enabled: true # 是否开启,默认为 true + id: ${spring.application.name}:${server.port} # 编号,Spring Cloud Alibaba 建议使用“应用:端口”的格式 + destination: springCloudBus # 目标消息队列,默认为 springCloudBus + +--- #################### 定时任务相关配置 #################### + +xxl: + job: + executor: + appname: ${spring.application.name} # 执行器 AppName + logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径 + accessToken: default_token # 执行器通讯TOKEN + +--- #################### 芋道相关配置 #################### + +yudao: + info: + version: 1.0.0 + base-package: cn.iocoder.yudao.module.statistics + swagger: + title: 管理后台 + description: 提供管理员管理的所有功能 + version: ${yudao.info.version} + base-package: ${yudao.info.base-package} + captcha: + enable: true # 验证码的开关,默认为 true; + error-code: # 错误码相关配置项 + constants-class-list: + - cn.iocoder.yudao.module.statistics.enums.ErrorCodeConstants + tenant: # 多租户相关配置项 + enable: true + ignore-urls: + ignore-tables: + +debug: false diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/bootstrap-local.yaml b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/bootstrap-local.yaml new file mode 100644 index 000000000..2de0efbf7 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/bootstrap-local.yaml @@ -0,0 +1,23 @@ +--- #################### 注册中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: 127.0.0.1:8848 + discovery: + namespace: dev # 命名空间。这里使用 dev 开发环境 + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + +--- #################### 配置中心相关配置 #################### + +spring: + cloud: + nacos: + # Nacos Config 配置项,对应 NacosConfigProperties 配置属性类 + config: + server-addr: 127.0.0.1:8848 # Nacos 服务器地址 + namespace: dev # 命名空间 dev 的ID,不能直接使用 dev 名称。创建命名空间的时候需要指定ID为 dev,这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + name: ${spring.application.name} # 使用的 Nacos 配置集的 dataId,默认为 spring.application.name + file-extension: yaml # 使用的 Nacos 配置集的 dataId 的文件拓展名,同时也是 Nacos 配置集的配置格式,默认为 properties diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/bootstrap.yaml b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/bootstrap.yaml new file mode 100644 index 000000000..4808939ca --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/bootstrap.yaml @@ -0,0 +1,14 @@ +spring: + application: + name: statistics-server + + profiles: + active: local + +server: + port: 48103 + +# 日志文件配置。注意,如果 logging.file.name 不放在 bootstrap.yaml 配置文件,而是放在 application.yaml 中,会导致出现 LOG_FILE_IS_UNDEFINED 文件 +logging: + file: + name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径 diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/logback-spring.xml b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..b1b9f3faf --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/logback-spring.xml @@ -0,0 +1,76 @@ + + + + + + + + + +       + + + ${PATTERN_DEFAULT} + + + + + + + + + + ${PATTERN_DEFAULT} + + + + ${LOG_FILE} + + + ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} + + ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} + + ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} + + ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} + + ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} + + + + + + 0 + + 256 + + + + + + + + ${PATTERN_DEFAULT} + + + + + + + + + + + + + + + + + + + + + + diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/infra/ApiAccessLogStatisticsMapper.xml b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/infra/ApiAccessLogStatisticsMapper.xml new file mode 100644 index 000000000..e64161597 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/infra/ApiAccessLogStatisticsMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/member/MemberStatisticsMapper.xml b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/member/MemberStatisticsMapper.xml new file mode 100644 index 000000000..33500fb43 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/member/MemberStatisticsMapper.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/pay/PayWalletStatisticsMapper.xml b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/pay/PayWalletStatisticsMapper.xml new file mode 100644 index 000000000..097780841 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/pay/PayWalletStatisticsMapper.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/AfterSaleStatisticsMapper.xml b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/AfterSaleStatisticsMapper.xml new file mode 100644 index 000000000..933c45610 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/AfterSaleStatisticsMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/BrokerageStatisticsMapper.xml b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/BrokerageStatisticsMapper.xml new file mode 100644 index 000000000..dff7e444f --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/BrokerageStatisticsMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/TradeOrderStatisticsMapper.xml b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/TradeOrderStatisticsMapper.xml new file mode 100644 index 000000000..c79c639a4 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/TradeOrderStatisticsMapper.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/TradeStatisticsMapper.xml b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/TradeStatisticsMapper.xml new file mode 100644 index 000000000..2415f6d55 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/resources/mapper/trade/TradeStatisticsMapper.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java new file mode 100644 index 000000000..20e0a8b09 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.pay.enums.wallet; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 钱包交易业务分类 + * + * @author jason + */ +@AllArgsConstructor +@Getter +public enum PayWalletBizTypeEnum implements IntArrayValuable { + + RECHARGE(1, "充值"), + RECHARGE_REFUND(2, "充值退款"), + PAYMENT(3, "支付"), + PAYMENT_REFUND(4, "支付退款"); + + // TODO 后续增加 + + /** + * 业务分类 + */ + private final Integer type; + /** + * 说明 + */ + private final String description; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PayWalletBizTypeEnum::getType).toArray(); + + @Override + public int[] array() { + return ARRAYS; + } +} From f826d117f8c880b25a064b33c95a61ba198463da Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 23 Oct 2023 19:47:19 +0800 Subject: [PATCH 4/9] =?UTF-8?q?statistics=EF=BC=9A=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=BD=91=E5=85=B3=E8=BD=AC=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yaml | 16 +++++++++ .../statistics/framework/package-info.java | 6 ++++ .../config/SecurityConfiguration.java | 34 +++++++++++++++++++ .../framework/security/core/package-info.java | 4 +++ 4 files changed, 60 insertions(+) create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/framework/package-info.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/framework/security/config/SecurityConfiguration.java create mode 100644 yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/framework/security/core/package-info.java diff --git a/yudao-gateway/src/main/resources/application.yaml b/yudao-gateway/src/main/resources/application.yaml index d3ac74eae..0f5e204a7 100644 --- a/yudao-gateway/src/main/resources/application.yaml +++ b/yudao-gateway/src/main/resources/application.yaml @@ -123,6 +123,19 @@ spring: - Path=/app-api/trade/** filters: - RewritePath=/app-api/trade/v3/api-docs, /v3/api-docs + ## statistics-server 服务 + - id: statistics-admin-api # 路由的编号 + uri: grayLb://statistics-server + predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 + - Path=/admin-api/statistics/** + filters: + - RewritePath=/admin-api/statistics/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs + - id: statistics-app-api # 路由的编号 + uri: grayLb://statistics-server + predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 + - Path=/app-api/statistics/** + filters: + - RewritePath=/app-api/statistics/v3/api-docs, /v3/api-docs x-forwarded: prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀 @@ -158,3 +171,6 @@ knife4j: - name: trade-server service-name: trade-server url: /admin-api/trade/v3/api-docs + - name: statistics-server + service-name: statistics-server + url: /admin-api/statistics/v3/api-docs diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/framework/package-info.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/framework/package-info.java new file mode 100644 index 000000000..032d7a91b --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 statistics 模块的 framework 封装 + * + * @author 芋道源码 + */ +package cn.iocoder.yudao.module.statistics.framework; diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/framework/security/config/SecurityConfiguration.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/framework/security/config/SecurityConfiguration.java new file mode 100644 index 000000000..5fa5376b5 --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/framework/security/config/SecurityConfiguration.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.statistics.framework.security.config; + +import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; + +/** + * Statistics 模块的 Security 配置 + */ +@Configuration("statisticsSecurityConfiguration") +public class SecurityConfiguration { + + @Bean("statisticsAuthorizeRequestsCustomizer") + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + return new AuthorizeRequestsCustomizer() { + + @Override + public void customize(ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry) { + // Swagger 接口文档 + registry.antMatchers("/v3/api-docs/**").permitAll() // 元数据 + .antMatchers("/swagger-ui.html").permitAll(); // Swagger UI + // Spring Boot Actuator 的安全配置 + registry.antMatchers("/actuator").anonymous() + .antMatchers("/actuator/**").anonymous(); + // Druid 监控 + registry.antMatchers("/druid/**").anonymous(); + } + + }; + } + +} diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/framework/security/core/package-info.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/framework/security/core/package-info.java new file mode 100644 index 000000000..01062c49e --- /dev/null +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/framework/security/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package cn.iocoder.yudao.module.statistics.framework.security.core; From 5b1481062652c4110690322a9ce5c4c1f21fba9a Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 23 Oct 2023 23:53:06 +0800 Subject: [PATCH 5/9] =?UTF-8?q?1.=20=E4=BF=9D=E6=8C=81=20boot=20=E5=92=8C?= =?UTF-8?q?=20cloud=20=E7=9A=84=E7=BB=9F=E4=B8=80=202.=20=E6=9A=82?= =?UTF-8?q?=E6=97=B6=E6=B8=85=E7=90=86=20mall=20=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=E5=8D=95=E6=B5=8B=EF=BC=8C=E7=AD=89=E5=90=8E=E7=BB=AD?= =?UTF-8?q?=E5=85=A8=E9=83=A8=E9=80=82=E9=85=8D=E5=AE=8C=EF=BC=8C=E5=86=8D?= =?UTF-8?q?=E5=BC=80=E5=90=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../brand/ProductBrandServiceImplTest.java | 133 ----- .../ProductCategoryServiceImplTest.java | 161 ------ .../ProductCommentServiceImplTest.java | 193 ------- .../service/sku/ProductSkuServiceTest.java | 205 ------- .../spu/ProductSpuServiceImplTest.java | 503 ------------------ .../coupon/AppCouponTemplateController.java | 6 +- .../CombinationActivityServiceImpl.java | 4 +- .../service/coupon/CouponServiceImpl.java | 8 +- .../seckill/SeckillActivityServiceImpl.java | 4 +- .../ArticleCategoryServiceImplTest.java | 139 ----- .../article/ArticleServiceImplTest.java | 167 ------ .../CombinationActivityServiceImplTest.java | 198 ------- .../coupon/CouponTemplateServiceImplTest.java | 147 ----- .../DecorateComponentServiceImplTest.java | 36 -- .../DiscountActivityServiceImplTest.java | 210 -------- .../reward/RewardActivityServiceImplTest.java | 218 -------- .../SeckillActivityServiceImplTest.java | 171 ------ .../SeckillConfigServiceImplTest.java | 190 ------- .../brokerage/BrokerageUserController.java | 1 + .../brokerage/BrokerageUserServiceImpl.java | 3 +- .../Kd100ExpressClientIntegrationTest.java | 46 -- .../KdNiaoExpressClientIntegrationTest.java | 46 -- .../aftersale/AfterSaleServiceTest.java | 156 ------ .../BrokerageRecordServiceImplTest.java | 118 ---- .../BrokerageUserServiceImplTest.java | 66 --- .../BrokerageWithdrawServiceImplTest.java | 117 ---- .../order/TradeOrderUpdateServiceTest.java | 316 ----------- .../price/TradePriceServiceImplTest.java | 135 ----- .../TradeCouponPriceCalculatorTest.java | 146 ----- .../TradeDeliveryPriceCalculatorTest.java | 193 ------- ...deDiscountActivityPriceCalculatorTest.java | 120 ----- .../TradeMemberLevelPriceCalculatorTest.java | 118 ---- .../TradePointGiveCalculatorTest.java | 99 ---- .../TradePointUsePriceCalculatorTest.java | 333 ------------ ...radeRewardActivityPriceCalculatorTest.java | 235 -------- .../module/member/api/user/MemberUserApi.java | 3 +- .../address/AddressServiceImplTest.java | 98 ---- .../service/auth/MemberAuthServiceTest.java | 118 ---- .../group/MemberGroupServiceImplTest.java | 160 ------ .../level/MemberLevelServiceImplTest.java | 268 ---------- .../service/tag/MemberTagServiceImplTest.java | 133 ----- .../user/MemberUserServiceImplTest.java | 136 ----- .../yudao/module/system/api/dept/DeptApi.java | 3 +- .../module/system/api/user/AdminUserApi.java | 3 +- 44 files changed, 19 insertions(+), 5844 deletions(-) delete mode 100644 yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/brand/ProductBrandServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java delete mode 100755 yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/article/ArticleCategoryServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/article/ArticleServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImplTest.java delete mode 100755 yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/decorate/DecorateComponentServiceImplTest.java delete mode 100755 yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImplTest.java delete mode 100755 yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/seckillactivity/SeckillActivityServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/seckillconfig/SeckillConfigServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/Kd100ExpressClientIntegrationTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/KdNiaoExpressClientIntegrationTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculatorTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeMemberLevelPriceCalculatorTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculatorTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculatorTest.java delete mode 100644 yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java delete mode 100644 yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/address/AddressServiceImplTest.java delete mode 100644 yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java delete mode 100644 yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImplTest.java delete mode 100644 yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImplTest.java delete mode 100644 yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/tag/MemberTagServiceImplTest.java delete mode 100644 yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImplTest.java diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/brand/ProductBrandServiceImplTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/brand/ProductBrandServiceImplTest.java deleted file mode 100644 index 1b5c68ba4..000000000 --- a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/brand/ProductBrandServiceImplTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package cn.iocoder.yudao.module.product.service.brand; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandCreateReqVO; -import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandPageReqVO; -import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandUpdateReqVO; -import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO; -import cn.iocoder.yudao.module.product.dal.mysql.brand.ProductBrandMapper; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.BRAND_NOT_EXISTS; -import static org.junit.jupiter.api.Assertions.*; - -/** -* {@link ProductBrandServiceImpl} 的单元测试类 -* -* @author 芋道源码 -*/ -@Import(ProductBrandServiceImpl.class) -public class ProductBrandServiceImplTest extends BaseDbUnitTest { - - @Resource - private ProductBrandServiceImpl brandService; - - @Resource - private ProductBrandMapper brandMapper; - - @Test - public void testCreateBrand_success() { - // 准备参数 - ProductBrandCreateReqVO reqVO = randomPojo(ProductBrandCreateReqVO.class); - - // 调用 - Long brandId = brandService.createBrand(reqVO); - // 断言 - assertNotNull(brandId); - // 校验记录的属性是否正确 - ProductBrandDO brand = brandMapper.selectById(brandId); - assertPojoEquals(reqVO, brand); - } - - @Test - public void testUpdateBrand_success() { - // mock 数据 - ProductBrandDO dbBrand = randomPojo(ProductBrandDO.class); - brandMapper.insert(dbBrand);// @Sql: 先插入出一条存在的数据 - // 准备参数 - ProductBrandUpdateReqVO reqVO = randomPojo(ProductBrandUpdateReqVO.class, o -> { - o.setId(dbBrand.getId()); // 设置更新的 ID - }); - - // 调用 - brandService.updateBrand(reqVO); - // 校验是否更新正确 - ProductBrandDO brand = brandMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, brand); - } - - @Test - public void testUpdateBrand_notExists() { - // 准备参数 - ProductBrandUpdateReqVO reqVO = randomPojo(ProductBrandUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> brandService.updateBrand(reqVO), BRAND_NOT_EXISTS); - } - - @Test - public void testDeleteBrand_success() { - // mock 数据 - ProductBrandDO dbBrand = randomPojo(ProductBrandDO.class); - brandMapper.insert(dbBrand);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbBrand.getId(); - - // 调用 - brandService.deleteBrand(id); - // 校验数据不存在了 - assertNull(brandMapper.selectById(id)); - } - - @Test - public void testDeleteBrand_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> brandService.deleteBrand(id), BRAND_NOT_EXISTS); - } - - @Test - public void testGetBrandPage() { - // mock 数据 - ProductBrandDO dbBrand = randomPojo(ProductBrandDO.class, o -> { // 等会查询到 - o.setName("芋道源码"); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - o.setCreateTime(buildTime(2022, 2, 1)); - }); - brandMapper.insert(dbBrand); - // 测试 name 不匹配 - brandMapper.insert(cloneIgnoreId(dbBrand, o -> o.setName("源码"))); - // 测试 status 不匹配 - brandMapper.insert(cloneIgnoreId(dbBrand, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); - // 测试 createTime 不匹配 - brandMapper.insert(cloneIgnoreId(dbBrand, o -> o.setCreateTime(buildTime(2022, 3, 1)))); - // 准备参数 - ProductBrandPageReqVO reqVO = new ProductBrandPageReqVO(); - reqVO.setName("芋道"); - reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); - reqVO.setCreateTime((new LocalDateTime[]{buildTime(2022, 1, 1), buildTime(2022, 2, 25)})); - - // 调用 - PageResult pageResult = brandService.getBrandPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbBrand, pageResult.getList().get(0)); - } - -} diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImplTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImplTest.java deleted file mode 100644 index 37e262d9a..000000000 --- a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImplTest.java +++ /dev/null @@ -1,161 +0,0 @@ -package cn.iocoder.yudao.module.product.service.category; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryCreateReqVO; -import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryListReqVO; -import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryUpdateReqVO; -import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO; -import cn.iocoder.yudao.module.product.dal.mysql.category.ProductCategoryMapper; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO.PARENT_ID_NULL; -import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.CATEGORY_NOT_EXISTS; -import static org.junit.jupiter.api.Assertions.*; - -/** - * {@link ProductCategoryServiceImpl} 的单元测试类 - * - * @author 芋道源码 - */ -@Import(ProductCategoryServiceImpl.class) -public class ProductCategoryServiceImplTest extends BaseDbUnitTest { - - @Resource - private ProductCategoryServiceImpl productCategoryService; - - @Resource - private ProductCategoryMapper productCategoryMapper; - - @Test - public void testCreateCategory_success() { - // 准备参数 - ProductCategoryCreateReqVO reqVO = randomPojo(ProductCategoryCreateReqVO.class); - - // mock 父类 - ProductCategoryDO parentProductCategory = randomPojo(ProductCategoryDO.class, o -> { - reqVO.setParentId(o.getId()); - o.setParentId(PARENT_ID_NULL); - }); - productCategoryMapper.insert(parentProductCategory); - - // 调用 - Long categoryId = productCategoryService.createCategory(reqVO); - // 断言 - assertNotNull(categoryId); - // 校验记录的属性是否正确 - ProductCategoryDO category = productCategoryMapper.selectById(categoryId); - assertPojoEquals(reqVO, category); - } - - @Test - public void testUpdateCategory_success() { - // mock 数据 - ProductCategoryDO dbCategory = randomPojo(ProductCategoryDO.class); - productCategoryMapper.insert(dbCategory);// @Sql: 先插入出一条存在的数据 - // 准备参数 - ProductCategoryUpdateReqVO reqVO = randomPojo(ProductCategoryUpdateReqVO.class, o -> { - o.setId(dbCategory.getId()); // 设置更新的 ID - }); - // mock 父类 - ProductCategoryDO parentProductCategory = randomPojo(ProductCategoryDO.class, o -> o.setId(reqVO.getParentId())); - productCategoryMapper.insert(parentProductCategory); - - // 调用 - productCategoryService.updateCategory(reqVO); - // 校验是否更新正确 - ProductCategoryDO category = productCategoryMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, category); - } - - @Test - public void testUpdateCategory_notExists() { - // 准备参数 - ProductCategoryUpdateReqVO reqVO = randomPojo(ProductCategoryUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> productCategoryService.updateCategory(reqVO), CATEGORY_NOT_EXISTS); - } - - @Test - public void testDeleteCategory_success() { - // mock 数据 - ProductCategoryDO dbCategory = randomPojo(ProductCategoryDO.class); - productCategoryMapper.insert(dbCategory);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbCategory.getId(); - - // 调用 - productCategoryService.deleteCategory(id); - // 校验数据不存在了 - assertNull(productCategoryMapper.selectById(id)); - } - - @Test - public void testDeleteCategory_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> productCategoryService.deleteCategory(id), CATEGORY_NOT_EXISTS); - } - - @Test - public void testGetCategoryLevel() { - // mock 数据 - ProductCategoryDO category1 = randomPojo(ProductCategoryDO.class, - o -> o.setParentId(PARENT_ID_NULL)); - productCategoryMapper.insert(category1); - ProductCategoryDO category2 = randomPojo(ProductCategoryDO.class, - o -> o.setParentId(category1.getId())); - productCategoryMapper.insert(category2); - ProductCategoryDO category3 = randomPojo(ProductCategoryDO.class, - o -> o.setParentId(category2.getId())); - productCategoryMapper.insert(category3); - - // 调用,并断言 - assertEquals(productCategoryService.getCategoryLevel(category1.getId()), 1); - assertEquals(productCategoryService.getCategoryLevel(category2.getId()), 2); - assertEquals(productCategoryService.getCategoryLevel(category3.getId()), 3); - } - - @Test - public void testGetCategoryList() { - // mock 数据 - ProductCategoryDO dbCategory = randomPojo(ProductCategoryDO.class, o -> { // 等会查询到 - o.setName("奥特曼"); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - o.setParentId(PARENT_ID_NULL); - }); - productCategoryMapper.insert(dbCategory); - // 测试 name 不匹配 - productCategoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setName("奥特块"))); - // 测试 status 不匹配 - productCategoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); - // 测试 parentId 不匹配 - productCategoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setParentId(3333L))); - // 准备参数 - ProductCategoryListReqVO reqVO = new ProductCategoryListReqVO(); - reqVO.setName("特曼"); - reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); - reqVO.setParentId(PARENT_ID_NULL); - - // 调用 - List list = productCategoryService.getEnableCategoryList(reqVO); - List all = productCategoryService.getEnableCategoryList(new ProductCategoryListReqVO()); - // 断言 - assertEquals(1, list.size()); - assertEquals(4, all.size()); - assertPojoEquals(dbCategory, list.get(0)); - } - -} diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java deleted file mode 100644 index fb6bc3649..000000000 --- a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java +++ /dev/null @@ -1,193 +0,0 @@ -package cn.iocoder.yudao.module.product.service.comment; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.RandomUtil; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentPageReqVO; -import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentReplyReqVO; -import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentRespVO; -import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO; -import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO; -import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentStatisticsRespVO; -import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert; -import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO; -import cn.iocoder.yudao.module.product.dal.mysql.comment.ProductCommentMapper; -import cn.iocoder.yudao.module.product.enums.comment.ProductCommentScoresEnum; -import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; -import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.Lazy; - -import javax.annotation.Resource; -import java.time.LocalDateTime; -import java.util.Date; - -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; - -// TODO 芋艿:单测详细 review 下 -/** - * {@link ProductCommentServiceImpl} 的单元测试类 - * - * @author wangzhs - */ -@Import(ProductCommentServiceImpl.class) -public class ProductCommentServiceImplTest extends BaseDbUnitTest { - - @Resource - private ProductCommentMapper productCommentMapper; - - @Resource - @Lazy - private ProductCommentServiceImpl productCommentService; - - @MockBean - private ProductSpuService productSpuService; - @MockBean - private ProductSkuService productSkuService; - - public String generateNo() { - return DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomInt(100000, 999999); - } - - public Long generateId() { - return RandomUtil.randomLong(100000, 999999); - } - - @Test - public void testCreateCommentAndGet_success() { - // mock 测试 - ProductCommentDO productComment = randomPojo(ProductCommentDO.class); - productCommentMapper.insert(productComment); - - // 断言 - // 校验记录的属性是否正确 - ProductCommentDO comment = productCommentMapper.selectById(productComment.getId()); - assertPojoEquals(productComment, comment); - } - - @Test - public void testGetCommentPage_success() { - // 准备参数 - ProductCommentDO productComment = randomPojo(ProductCommentDO.class, o -> { - o.setUserNickname("王二狗"); - o.setSpuName("感冒药"); - o.setScores(ProductCommentScoresEnum.FOUR.getScores()); - o.setReplyStatus(Boolean.TRUE); - o.setVisible(Boolean.TRUE); - o.setId(generateId()); - o.setUserId(generateId()); - o.setAnonymous(Boolean.TRUE); - o.setOrderId(generateId()); - o.setOrderItemId(generateId()); - o.setSpuId(generateId()); - o.setSkuId(generateId()); - o.setDescriptionScores(ProductCommentScoresEnum.FOUR.getScores()); - o.setBenefitScores(ProductCommentScoresEnum.FOUR.getScores()); - o.setContent("真好吃"); - o.setReplyUserId(generateId()); - o.setReplyContent("确实"); - o.setReplyTime(LocalDateTime.now()); - o.setCreateTime(LocalDateTime.now()); - o.setUpdateTime(LocalDateTime.now()); - }); - productCommentMapper.insert(productComment); - - Long orderId = productComment.getOrderId(); - Long spuId = productComment.getSpuId(); - - // 测试 userNickname 不匹配 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setUserNickname("王三").setScores(ProductCommentScoresEnum.ONE.getScores()))); - // 测试 orderId 不匹配 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setOrderId(generateId()))); - // 测试 spuId 不匹配 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setSpuId(generateId()))); - // 测试 spuName 不匹配 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setSpuName("感康"))); - // 测试 scores 不匹配 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setScores(ProductCommentScoresEnum.ONE.getScores()))); - // 测试 replied 不匹配 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setReplyStatus(Boolean.FALSE))); - // 测试 visible 不匹配 - productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setVisible(Boolean.FALSE))); - - // 调用 - ProductCommentPageReqVO productCommentPageReqVO = new ProductCommentPageReqVO(); - productCommentPageReqVO.setUserNickname("王二"); - productCommentPageReqVO.setOrderId(orderId); - productCommentPageReqVO.setSpuId(spuId); - productCommentPageReqVO.setSpuName("感冒药"); - productCommentPageReqVO.setScores(ProductCommentScoresEnum.FOUR.getScores()); - productCommentPageReqVO.setReplyStatus(Boolean.TRUE); - - PageResult commentPage = productCommentService.getCommentPage(productCommentPageReqVO); - PageResult result = ProductCommentConvert.INSTANCE.convertPage(productCommentMapper.selectPage(productCommentPageReqVO)); - assertEquals(result.getTotal(), commentPage.getTotal()); - - PageResult all = productCommentService.getCommentPage(new ProductCommentPageReqVO()); - assertEquals(8, all.getTotal()); - - // 测试获取所有商品分页评论数据 - PageResult result1 = productCommentService.getCommentPage(new AppCommentPageReqVO(), Boolean.TRUE); - assertEquals(7, result1.getTotal()); - - // 测试获取所有商品分页中评数据 - PageResult result2 = productCommentService.getCommentPage(new AppCommentPageReqVO().setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE); - assertEquals(2, result2.getTotal()); - - // 测试获取指定 spuId 商品分页中评数据 - PageResult result3 = productCommentService.getCommentPage(new AppCommentPageReqVO().setSpuId(spuId).setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE); - assertEquals(2, result3.getTotal()); - - // 测试分页 tab count - AppCommentStatisticsRespVO tabsCount = productCommentService.getCommentStatistics(spuId, Boolean.TRUE); - assertEquals(4, tabsCount.getGoodCount()); - assertEquals(2, tabsCount.getMediocreCount()); - assertEquals(0, tabsCount.getNegativeCount()); - - } - - @Test - public void testUpdateCommentVisible_success() { - // mock 测试 - ProductCommentDO productComment = randomPojo(ProductCommentDO.class, o -> { - o.setVisible(Boolean.TRUE); - }); - productCommentMapper.insert(productComment); - - Long productCommentId = productComment.getId(); - - ProductCommentUpdateVisibleReqVO updateReqVO = new ProductCommentUpdateVisibleReqVO(); - updateReqVO.setId(productCommentId); - updateReqVO.setVisible(Boolean.FALSE); - productCommentService.updateCommentVisible(updateReqVO); - - ProductCommentDO productCommentDO = productCommentMapper.selectById(productCommentId); - assertFalse(productCommentDO.getVisible()); - } - - - @Test - public void testCommentReply_success() { - // mock 测试 - ProductCommentDO productComment = randomPojo(ProductCommentDO.class); - productCommentMapper.insert(productComment); - - Long productCommentId = productComment.getId(); - - ProductCommentReplyReqVO replyVO = new ProductCommentReplyReqVO(); - replyVO.setId(productCommentId); - replyVO.setReplyContent("测试"); - productCommentService.replyComment(replyVO, 1L); - - ProductCommentDO productCommentDO = productCommentMapper.selectById(productCommentId); - assertEquals("测试", productCommentDO.getReplyContent()); - } - -} diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java deleted file mode 100644 index 9df947b43..000000000 --- a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java +++ /dev/null @@ -1,205 +0,0 @@ -package cn.iocoder.yudao.module.product.service.sku; - -import cn.hutool.core.util.RandomUtil; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.framework.test.core.util.AssertUtils; -import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO; -import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO; -import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; -import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper; -import cn.iocoder.yudao.module.product.service.property.ProductPropertyService; -import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService; -import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; -import java.util.Arrays; -import java.util.List; - -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; -import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH; -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.verify; - -/** - * {@link ProductSkuServiceImpl} 的单元测试 - * - * @author 芋道源码 - */ -@Import(ProductSkuServiceImpl.class) -public class ProductSkuServiceTest extends BaseDbUnitTest { - - @Resource - private ProductSkuService productSkuService; - - @Resource - private ProductSkuMapper productSkuMapper; - - @MockBean - private ProductSpuService productSpuService; - @MockBean - private ProductPropertyService productPropertyService; - @MockBean - private ProductPropertyValueService productPropertyValueService; - - public Long generateId() { - return RandomUtil.randomLong(100000, 999999); - } - - @Test - public void testUpdateSkuList() { - // mock 数据 - ProductSkuDO sku01 = randomPojo(ProductSkuDO.class, o -> { // 测试更新 - o.setSpuId(1L); - o.setProperties(singletonList(new ProductSkuDO.Property( - 10L, "颜色", 20L, "红色"))); - }); - productSkuMapper.insert(sku01); - ProductSkuDO sku02 = randomPojo(ProductSkuDO.class, o -> { // 测试删除 - o.setSpuId(1L); - o.setProperties(singletonList(new ProductSkuDO.Property( - 10L, "颜色", 30L, "蓝色"))); - - }); - productSkuMapper.insert(sku02); - // 准备参数 - Long spuId = 1L; - String spuName = "测试商品"; - List skus = Arrays.asList( - randomPojo(ProductSkuCreateOrUpdateReqVO.class, o -> { // 测试更新 - o.setProperties(singletonList(new ProductSkuCreateOrUpdateReqVO.Property( - 10L, "颜色", 20L, "红色"))); - }), - randomPojo(ProductSkuCreateOrUpdateReqVO.class, o -> { // 测试新增 - o.setProperties(singletonList(new ProductSkuCreateOrUpdateReqVO.Property( - 10L, "颜色", 20L, "红色"))); - }) - ); - - // 调用 - productSkuService.updateSkuList(spuId, skus); - // 断言 - List dbSkus = productSkuMapper.selectListBySpuId(spuId); - assertEquals(dbSkus.size(), 2); - // 断言更新的 - assertEquals(dbSkus.get(0).getId(), sku01.getId()); - assertPojoEquals(dbSkus.get(0), skus.get(0), "properties"); - assertEquals(skus.get(0).getProperties().size(), 1); - assertPojoEquals(dbSkus.get(0).getProperties().get(0), skus.get(0).getProperties().get(0)); - // 断言新增的 - assertNotEquals(dbSkus.get(1).getId(), sku02.getId()); - assertPojoEquals(dbSkus.get(1), skus.get(1), "properties"); - assertEquals(skus.get(1).getProperties().size(), 1); - assertPojoEquals(dbSkus.get(1).getProperties().get(0), skus.get(1).getProperties().get(0)); - } - - @Test - public void testUpdateSkuStock_incrSuccess() { - // 准备参数 - ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO() - .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncrCount(10))); - // mock 数据 - productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> { - o.setId(1L).setSpuId(10L).setStock(20); - o.getProperties().forEach(p -> { - // 指定 id 范围 解决 Value too long - p.setPropertyId(generateId()); - p.setValueId(generateId()); - }); - })); - - // 调用 - productSkuService.updateSkuStock(updateStockReqDTO); - // 断言 - ProductSkuDO sku = productSkuMapper.selectById(1L); - assertEquals(sku.getStock(), 30); - verify(productSpuService).updateSpuStock(argThat(spuStockIncrCounts -> { - assertEquals(spuStockIncrCounts.size(), 1); - assertEquals(spuStockIncrCounts.get(10L), 10); - return true; - })); - } - - @Test - public void testUpdateSkuStock_decrSuccess() { - // 准备参数 - ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO() - .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncrCount(-10))); - // mock 数据 - productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> { - o.setId(1L).setSpuId(10L).setStock(20); - o.getProperties().forEach(p -> { - // 指定 id 范围 解决 Value too long - p.setPropertyId(generateId()); - p.setValueId(generateId()); - }); - })); - - // 调用 - productSkuService.updateSkuStock(updateStockReqDTO); - // 断言 - ProductSkuDO sku = productSkuMapper.selectById(1L); - assertEquals(sku.getStock(), 10); - verify(productSpuService).updateSpuStock(argThat(spuStockIncrCounts -> { - assertEquals(spuStockIncrCounts.size(), 1); - assertEquals(spuStockIncrCounts.get(10L), -10); - return true; - })); - } - - @Test - public void testUpdateSkuStock_decrFail() { - // 准备参数 - ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO() - .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncrCount(-30))); - // mock 数据 - productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> { - o.setId(1L).setSpuId(10L).setStock(20); - o.getProperties().forEach(p -> { - // 指定 id 范围 解决 Value too long - p.setPropertyId(generateId()); - p.setValueId(generateId()); - }); - })); - // 调用并断言 - AssertUtils.assertServiceException(() -> productSkuService.updateSkuStock(updateStockReqDTO), - SKU_STOCK_NOT_ENOUGH); - } - - @Test - public void testDeleteSku_success() { - ProductSkuDO dbSku = randomPojo(ProductSkuDO.class, o -> { - o.setId(generateId()).setSpuId(generateId()); - o.getProperties().forEach(p -> { - // 指定 id 范围 解决 Value too long - p.setPropertyId(generateId()); - p.setValueId(generateId()); - }); - }); - // mock 数据 - productSkuMapper.insert(dbSku); - // 准备参数 - Long id = dbSku.getId(); - - // 调用 - productSkuService.deleteSku(id); - // 校验数据不存在了 - assertNull(productSkuMapper.selectById(id)); - } - - @Test - public void testDeleteSku_notExists() { - // 准备参数 - Long id = 1L; - - // 调用, 并断言异常 - assertServiceException(() -> productSkuService.deleteSku(id), SKU_NOT_EXISTS); - } -} diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java deleted file mode 100755 index dcda35550..000000000 --- a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java +++ /dev/null @@ -1,503 +0,0 @@ -package cn.iocoder.yudao.module.product.service.spu; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.RandomUtil; -import cn.iocoder.yudao.framework.common.exception.ServiceException; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO; -import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuCreateReqVO; -import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO; -import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuRespVO; -import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuUpdateReqVO; -import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert; -import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; -import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper; -import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; -import cn.iocoder.yudao.module.product.service.brand.ProductBrandServiceImpl; -import cn.iocoder.yudao.module.product.service.category.ProductCategoryServiceImpl; -import cn.iocoder.yudao.module.product.service.property.ProductPropertyService; -import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService; -import cn.iocoder.yudao.module.product.service.sku.ProductSkuServiceImpl; -import com.google.common.collect.Lists; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; -import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static org.assertj.core.util.Lists.newArrayList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -// TODO @芋艿:review 下单元测试 - -/** - * {@link ProductSpuServiceImpl} 的单元测试类 - * - * @author 芋道源码 - */ -@Import(ProductSpuServiceImpl.class) -public class ProductSpuServiceImplTest extends BaseDbUnitTest { - - @Resource - private ProductSpuServiceImpl productSpuService; - - @Resource - private ProductSpuMapper productSpuMapper; - - @MockBean - private ProductSkuServiceImpl productSkuService; - @MockBean - private ProductCategoryServiceImpl categoryService; - @MockBean - private ProductBrandServiceImpl brandService; - @MockBean - private ProductPropertyService productPropertyService; - @MockBean - private ProductPropertyValueService productPropertyValueService; - - public String generateNo() { - return DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomInt(100000, 999999); - } - - public Long generateId() { - return RandomUtil.randomLong(100000, 999999); - } - - public int generaInt(){return RandomUtil.randomInt(1,9999999);} - - // TODO @芋艿:单测后续 review 哈 - - @Test - public void testCreateSpu_success() { - // 准备参数 - ProductSkuCreateOrUpdateReqVO skuCreateOrUpdateReqVO = randomPojo(ProductSkuCreateOrUpdateReqVO.class,o->{ - // 限制范围为正整数 - o.setCostPrice(generaInt()); - o.setPrice(generaInt()); - o.setMarketPrice(generaInt()); - o.setStock(generaInt()); - o.setWarnStock(10); - o.setFirstBrokeragePrice(generaInt()); - o.setSecondBrokeragePrice(generaInt()); - // 限制分数为两位数 - o.setWeight(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); - o.setVolume(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); - }); - ProductSpuCreateReqVO createReqVO = randomPojo(ProductSpuCreateReqVO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setSkus(newArrayList(skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO)); - }); - when(categoryService.getCategoryLevel(eq(createReqVO.getCategoryId()))).thenReturn(2); - Long spu = productSpuService.createSpu(createReqVO); - ProductSpuDO productSpuDO = productSpuMapper.selectById(spu); - assertPojoEquals(createReqVO, productSpuDO); - } - - @Test - public void testUpdateSpu_success() { - // 准备参数 - ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - }); - productSpuMapper.insert(createReqVO); - // 准备参数 - ProductSkuCreateOrUpdateReqVO skuCreateOrUpdateReqVO = randomPojo(ProductSkuCreateOrUpdateReqVO.class,o->{ - // 限制范围为正整数 - o.setCostPrice(generaInt()); - o.setPrice(generaInt()); - o.setMarketPrice(generaInt()); - o.setStock(generaInt()); - o.setWarnStock(10); - o.setFirstBrokeragePrice(generaInt()); - o.setSecondBrokeragePrice(generaInt()); - // 限制分数为两位数 - o.setWeight(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); - o.setVolume(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); - }); - // 准备参数 - ProductSpuUpdateReqVO reqVO = randomPojo(ProductSpuUpdateReqVO.class, o -> { - o.setId(createReqVO.getId()); // 设置更新的 ID - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - o.setStatus(0); - o.setSkus(newArrayList(skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO)); - }); - when(categoryService.getCategoryLevel(eq(reqVO.getCategoryId()))).thenReturn(2); - // 调用 - productSpuService.updateSpu(reqVO); - // 校验是否更新正确 - ProductSpuDO spu = productSpuMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, spu); - } - - @Test - public void testValidateSpuExists_exception() { - ProductSpuUpdateReqVO reqVO = randomPojo(ProductSpuUpdateReqVO.class); - // 调用 - Assertions.assertThrows(ServiceException.class, () -> productSpuService.updateSpu(reqVO)); - } - - @Test - void deleteSpu() { - // 准备参数 - ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - o.setStatus(-1); // 加入回收站才可删除 - }); - productSpuMapper.insert(createReqVO); - - // 调用 - productSpuService.deleteSpu(createReqVO.getId()); - - Assertions.assertNull(productSpuMapper.selectById(createReqVO.getId())); - } - - @Test - void getSpu() { - // 准备参数 - ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - }); - productSpuMapper.insert(createReqVO); - - ProductSpuDO spu = productSpuService.getSpu(createReqVO.getId()); - assertPojoEquals(createReqVO, spu); - } - - @Test - void getSpuList() { - // 准备参数 - ArrayList createReqVOs = Lists.newArrayList(randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - }), randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - })); - productSpuMapper.insertBatch(createReqVOs); - - // 调用 - List spuList = productSpuService.getSpuList(createReqVOs.stream().map(ProductSpuDO::getId).collect(Collectors.toList())); - Assertions.assertIterableEquals(createReqVOs, spuList); - } - - @Test - void getSpuPage_alarmStock_empty() { - // 准备参数 - ArrayList createReqVOs = Lists.newArrayList(randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(11); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - }), randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(11); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - })); - productSpuMapper.insertBatch(createReqVOs); - // 调用 - ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO(); - productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK); - - PageResult spuPage = productSpuService.getSpuPage(productSpuPageReqVO); - - PageResult result = PageResult.empty(); - Assertions.assertIterableEquals(result.getList(), spuPage.getList()); - assertEquals(spuPage.getTotal(), result.getTotal()); - } - - @Test - void getSpuPage_alarmStock() { - // 准备参数 - ArrayList createReqVOs = Lists.newArrayList(randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(5); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - }), randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(9); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - })); - productSpuMapper.insertBatch(createReqVOs); - // 调用 - ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO(); - productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK); - PageResult spuPage = productSpuService.getSpuPage(productSpuPageReqVO); - assertEquals(createReqVOs.size(), spuPage.getTotal()); - } - - @Test - void testGetSpuPage() { - // 准备参数 - ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - }); - - // 准备参数 - productSpuMapper.insert(createReqVO); - // 测试 status 不匹配 - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setStatus(ProductSpuStatusEnum.DISABLE.getStatus()))); - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setStatus(ProductSpuStatusEnum.RECYCLE.getStatus()))); - // 测试 SpecType 不匹配 - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setSpecType(true))); - // 测试 BrandId 不匹配 - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setBrandId(generateId()))); - // 测试 CategoryId 不匹配 - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setCategoryId(generateId()))); - - // 调用 - ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO(); - // 查询条件 按需打开 - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK); - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.RECYCLE_BIN); - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.FOR_SALE); - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.IN_WAREHOUSE); - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.SOLD_OUT); - //productSpuPageReqVO.setName(createReqVO.getName()); - //productSpuPageReqVO.setCategoryId(createReqVO.getCategoryId()); - - PageResult spuPage = productSpuService.getSpuPage(productSpuPageReqVO); - - PageResult result = ProductSpuConvert.INSTANCE.convertPage(productSpuMapper.selectPage(productSpuPageReqVO)); - assertEquals(result.getTotal(), spuPage.getTotal()); - } - - /** - * 生成笛卡尔积 - * - * @param data 数据 - * @return 笛卡尔积 - */ - public static List> cartesianProduct(List> data) { - List> res = null; // 结果集(当前为第N个List,则该处存放的就为前N-1个List的笛卡尔积集合) - for (List list : data) { // 遍历数据 - List> temp = new ArrayList<>(); // 临时结果集,存放本次循环后生成的笛卡尔积集合 - if (res == null) { // 结果集为null表示第一次循环既list为第一个List - for (T t : list) { // 便利第一个List - // 利用stream生成List,第一个List的笛卡尔积集合约等于自己本身(需要创建一个List并把对象添加到当中),存放到临时结果集 - temp.add(Stream.of(t).collect(Collectors.toList())); - } - res = temp; // 将临时结果集赋值给结果集 - continue; // 跳过本次循环 - } - // 不为第一个List,计算前面的集合(笛卡尔积)和当前List的笛卡尔积集合 - for (T t : list) { // 便利 - for (List rl : res) { // 便利前面的笛卡尔积集合 - // 利用stream生成List - temp.add(Stream.concat(rl.stream(), Stream.of(t)).collect(Collectors.toList())); - } - } - res = temp; // 将临时结果集赋值给结果集 - } - // 返回结果 - return res; - } - - @Test - public void testUpdateSpuStock() { - // 准备参数 - Map stockIncrCounts = MapUtil.builder(1L, 10).put(2L, -20).build(); - // mock 方法(数据) - productSpuMapper.insert(randomPojo(ProductSpuDO.class, o ->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - o.setId(1L).setStock(20); - })); - productSpuMapper.insert(randomPojo(ProductSpuDO.class, o -> { - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - o.setId(2L).setStock(30); - })); - - // 调用 - productSpuService.updateSpuStock(stockIncrCounts); - // 断言 - assertEquals(productSpuService.getSpu(1L).getStock(), 30); - assertEquals(productSpuService.getSpu(2L).getStock(), 10); - } - -} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java index 3eb2178fa..809816e30 100755 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java @@ -96,13 +96,13 @@ public class AppCouponTemplateController { */ private Long getProductScopeValue(Integer productScope, Long spuId) { // 通用券:没有商品范围 - if (productScope == null || ObjectUtils.equalsAny(productScope, PromotionProductScopeEnum.ALL.getScope(), null)) { + if (ObjectUtils.equalsAny(productScope, PromotionProductScopeEnum.ALL.getScope(), null)) { return null; } // 品类券:查询商品的品类编号 if (Objects.equals(productScope, PromotionProductScopeEnum.CATEGORY.getScope()) && spuId != null) { - return Optional.ofNullable(productSpuApi.getSpu(spuId).getCheckedData()) - .map(ProductSpuRespDTO::getCategoryId).orElse(null); + ProductSpuRespDTO spu = productSpuApi.getSpu(spuId).getCheckedData(); + return spu != null ? spu.getCategoryId() : null; } // 商品卷:直接返回 return spuId; diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java index 4b8cd3f68..c1c6c52ce 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java @@ -108,8 +108,8 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic } // 2. 校验商品 sku 都存在 - Map skuMap = convertMap(productSkuApi.getSkuListBySpuId(singletonList(spuId)).getCheckedData(), - ProductSkuRespDTO::getId); + List skus = productSkuApi.getSkuListBySpuId(singletonList(spuId)).getCheckedData(); + Map skuMap = convertMap(skus, ProductSkuRespDTO::getId); products.forEach(product -> { if (!skuMap.containsKey(product.getSkuId())) { throw exception(SKU_NOT_EXISTS); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java index bb997a012..d722f4676 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java @@ -7,7 +7,6 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; import cn.iocoder.yudao.module.member.api.user.MemberUserApi; import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; @@ -80,12 +79,11 @@ public class CouponServiceImpl implements CouponService { public PageResult getCouponPage(CouponPageReqVO pageReqVO) { // 获得用户编号 if (StrUtil.isNotEmpty(pageReqVO.getNickname())) { - Set userIds = CollectionUtils.convertSet(memberUserApi.getUserListByNickname(pageReqVO.getNickname()).getCheckedData(), - MemberUserRespDTO::getId); - if (CollUtil.isEmpty(userIds)) { + List users = memberUserApi.getUserListByNickname(pageReqVO.getNickname()).getCheckedData(); + if (CollUtil.isEmpty(users)) { return PageResult.empty(); } - pageReqVO.setUserIds(userIds); + pageReqVO.setUserIds(convertSet(users, MemberUserRespDTO::getId)); } // 分页查询 return couponMapper.selectPage(pageReqVO); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java index 745e50868..9aa9ee588 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java @@ -123,8 +123,8 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { } // 2. 校验商品 sku 都存在 - Map skuMap = convertMap(productSkuApi.getSkuListBySpuId(singletonList(spuId)).getCheckedData(), - ProductSkuRespDTO::getId); + List skus = productSkuApi.getSkuListBySpuId(singletonList(spuId)).getCheckedData(); + Map skuMap = convertMap(skus, ProductSkuRespDTO::getId); products.forEach(product -> { if (!skuMap.containsKey(product.getSkuId())) { throw exception(SKU_NOT_EXISTS); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/article/ArticleCategoryServiceImplTest.java b/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/article/ArticleCategoryServiceImplTest.java deleted file mode 100644 index 2f710d30e..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/article/ArticleCategoryServiceImplTest.java +++ /dev/null @@ -1,139 +0,0 @@ -package cn.iocoder.yudao.module.promotion.service.article; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.category.ArticleCategoryCreateReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.category.ArticleCategoryPageReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.category.ArticleCategoryUpdateReqVO; -import cn.iocoder.yudao.module.promotion.dal.dataobject.article.ArticleCategoryDO; -import cn.iocoder.yudao.module.promotion.dal.mysql.article.ArticleCategoryMapper; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; - -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.ARTICLE_CATEGORY_NOT_EXISTS; -import static org.junit.jupiter.api.Assertions.*; - -// TODO 芋艿:review 单测 -/** - * {@link ArticleCategoryServiceImpl} 的单元测试类 - * - * @author HUIHUI - */ -@Import(ArticleCategoryServiceImpl.class) -public class ArticleCategoryServiceImplTest extends BaseDbUnitTest { - - @Resource - private ArticleCategoryServiceImpl articleCategoryService; - - @Resource - private ArticleCategoryMapper articleCategoryMapper; - - @Test - public void testCreateArticleCategory_success() { - // 准备参数 - ArticleCategoryCreateReqVO reqVO = randomPojo(ArticleCategoryCreateReqVO.class); - - // 调用 - Long articleCategoryId = articleCategoryService.createArticleCategory(reqVO); - // 断言 - assertNotNull(articleCategoryId); - // 校验记录的属性是否正确 - ArticleCategoryDO articleCategory = articleCategoryMapper.selectById(articleCategoryId); - assertPojoEquals(reqVO, articleCategory); - } - - @Test - public void testUpdateArticleCategory_success() { - // mock 数据 - ArticleCategoryDO dbArticleCategory = randomPojo(ArticleCategoryDO.class); - articleCategoryMapper.insert(dbArticleCategory);// @Sql: 先插入出一条存在的数据 - // 准备参数 - ArticleCategoryUpdateReqVO reqVO = randomPojo(ArticleCategoryUpdateReqVO.class, o -> { - o.setId(dbArticleCategory.getId()); // 设置更新的 ID - }); - - // 调用 - articleCategoryService.updateArticleCategory(reqVO); - // 校验是否更新正确 - ArticleCategoryDO articleCategory = articleCategoryMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, articleCategory); - } - - @Test - public void testUpdateArticleCategory_notExists() { - // 准备参数 - ArticleCategoryUpdateReqVO reqVO = randomPojo(ArticleCategoryUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> articleCategoryService.updateArticleCategory(reqVO), ARTICLE_CATEGORY_NOT_EXISTS); - } - - @Test - public void testDeleteArticleCategory_success() { - // mock 数据 - ArticleCategoryDO dbArticleCategory = randomPojo(ArticleCategoryDO.class); - articleCategoryMapper.insert(dbArticleCategory);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbArticleCategory.getId(); - - // 调用 - articleCategoryService.deleteArticleCategory(id); - // 校验数据不存在了 - assertNull(articleCategoryMapper.selectById(id)); - } - - @Test - public void testDeleteArticleCategory_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> articleCategoryService.deleteArticleCategory(id), ARTICLE_CATEGORY_NOT_EXISTS); - } - - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetArticleCategoryPage() { - // mock 数据 - ArticleCategoryDO dbArticleCategory = randomPojo(ArticleCategoryDO.class, o -> { // 等会查询到 - o.setName(null); - o.setPicUrl(null); - o.setStatus(null); - o.setSort(null); - o.setCreateTime(null); - }); - articleCategoryMapper.insert(dbArticleCategory); - // 测试 name 不匹配 - articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setName(null))); - // 测试 picUrl 不匹配 - articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setPicUrl(null))); - // 测试 status 不匹配 - articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setStatus(null))); - // 测试 sort 不匹配 - articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setSort(null))); - // 测试 createTime 不匹配 - articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setCreateTime(null))); - // 准备参数 - ArticleCategoryPageReqVO reqVO = new ArticleCategoryPageReqVO(); - reqVO.setName(null); - reqVO.setStatus(null); - reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - - // 调用 - PageResult pageResult = articleCategoryService.getArticleCategoryPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbArticleCategory, pageResult.getList().get(0)); - } - -} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/article/ArticleServiceImplTest.java b/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/article/ArticleServiceImplTest.java deleted file mode 100644 index 718651700..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/article/ArticleServiceImplTest.java +++ /dev/null @@ -1,167 +0,0 @@ -package cn.iocoder.yudao.module.promotion.service.article; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.article.ArticleCreateReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.article.ArticlePageReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.article.vo.article.ArticleUpdateReqVO; -import cn.iocoder.yudao.module.promotion.dal.dataobject.article.ArticleDO; -import cn.iocoder.yudao.module.promotion.dal.mysql.article.ArticleMapper; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; - -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.ARTICLE_NOT_EXISTS; -import static org.junit.jupiter.api.Assertions.*; - -/** - * {@link ArticleServiceImpl} 的单元测试类 - * - * @author HUIHUI - */ -@Import(ArticleServiceImpl.class) -public class ArticleServiceImplTest extends BaseDbUnitTest { - - @Resource - private ArticleServiceImpl articleService; - - @Resource - private ArticleMapper articleMapper; - - @Test - public void testCreateArticle_success() { - // 准备参数 - ArticleCreateReqVO reqVO = randomPojo(ArticleCreateReqVO.class); - - // 调用 - Long articleId = articleService.createArticle(reqVO); - // 断言 - assertNotNull(articleId); - // 校验记录的属性是否正确 - ArticleDO article = articleMapper.selectById(articleId); - assertPojoEquals(reqVO, article); - } - - @Test - public void testUpdateArticle_success() { - // mock 数据 - ArticleDO dbArticle = randomPojo(ArticleDO.class); - articleMapper.insert(dbArticle);// @Sql: 先插入出一条存在的数据 - // 准备参数 - ArticleUpdateReqVO reqVO = randomPojo(ArticleUpdateReqVO.class, o -> { - o.setId(dbArticle.getId()); // 设置更新的 ID - }); - - // 调用 - articleService.updateArticle(reqVO); - // 校验是否更新正确 - ArticleDO article = articleMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, article); - } - - @Test - public void testUpdateArticle_notExists() { - // 准备参数 - ArticleUpdateReqVO reqVO = randomPojo(ArticleUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> articleService.updateArticle(reqVO), ARTICLE_NOT_EXISTS); - } - - @Test - public void testDeleteArticle_success() { - // mock 数据 - ArticleDO dbArticle = randomPojo(ArticleDO.class); - articleMapper.insert(dbArticle);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbArticle.getId(); - - // 调用 - articleService.deleteArticle(id); - // 校验数据不存在了 - assertNull(articleMapper.selectById(id)); - } - - @Test - public void testDeleteArticle_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> articleService.deleteArticle(id), ARTICLE_NOT_EXISTS); - } - - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetArticlePage() { - // mock 数据 - ArticleDO dbArticle = randomPojo(ArticleDO.class, o -> { // 等会查询到 - o.setCategoryId(null); - o.setTitle(null); - o.setAuthor(null); - o.setPicUrl(null); - o.setIntroduction(null); - o.setBrowseCount(null); - o.setSort(null); - o.setStatus(null); - o.setSpuId(null); - o.setRecommendHot(null); - o.setRecommendBanner(null); - o.setContent(null); - o.setCreateTime(null); - }); - articleMapper.insert(dbArticle); - // 测试 categoryId 不匹配 - articleMapper.insert(cloneIgnoreId(dbArticle, o -> o.setCategoryId(null))); - // 测试 title 不匹配 - articleMapper.insert(cloneIgnoreId(dbArticle, o -> o.setTitle(null))); - // 测试 author 不匹配 - articleMapper.insert(cloneIgnoreId(dbArticle, o -> o.setAuthor(null))); - // 测试 picUrl 不匹配 - articleMapper.insert(cloneIgnoreId(dbArticle, o -> o.setPicUrl(null))); - // 测试 introduction 不匹配 - articleMapper.insert(cloneIgnoreId(dbArticle, o -> o.setIntroduction(null))); - // 测试 browseCount 不匹配 - articleMapper.insert(cloneIgnoreId(dbArticle, o -> o.setBrowseCount(null))); - // 测试 sort 不匹配 - articleMapper.insert(cloneIgnoreId(dbArticle, o -> o.setSort(null))); - // 测试 status 不匹配 - articleMapper.insert(cloneIgnoreId(dbArticle, o -> o.setStatus(null))); - // 测试 spuId 不匹配 - articleMapper.insert(cloneIgnoreId(dbArticle, o -> o.setSpuId(null))); - // 测试 recommendHot 不匹配 - articleMapper.insert(cloneIgnoreId(dbArticle, o -> o.setRecommendHot(null))); - // 测试 recommendBanner 不匹配 - articleMapper.insert(cloneIgnoreId(dbArticle, o -> o.setRecommendBanner(null))); - // 测试 content 不匹配 - articleMapper.insert(cloneIgnoreId(dbArticle, o -> o.setContent(null))); - // 测试 createTime 不匹配 - articleMapper.insert(cloneIgnoreId(dbArticle, o -> o.setCreateTime(null))); - // 准备参数 - ArticlePageReqVO reqVO = new ArticlePageReqVO(); - reqVO.setCategoryId(null); - reqVO.setTitle(null); - reqVO.setAuthor(null); - reqVO.setStatus(null); - reqVO.setSpuId(null); - reqVO.setRecommendHot(null); - reqVO.setRecommendBanner(null); - reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - - // 调用 - PageResult pageResult = articleService.getArticlePage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbArticle, pageResult.getList().get(0)); - } - -} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImplTest.java b/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImplTest.java deleted file mode 100644 index f1605dec8..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImplTest.java +++ /dev/null @@ -1,198 +0,0 @@ -package cn.iocoder.yudao.module.promotion.service.combination; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityCreateReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityUpdateReqVO; -import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO; -import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationActivityMapper; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; - -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COMBINATION_ACTIVITY_NOT_EXISTS; -import static org.junit.jupiter.api.Assertions.*; - -// TODO 芋艿:等完成后,在补全单测 -/** - * {@link CombinationActivityServiceImpl} 的单元测试类 - * - * @author HUIHUI - */ -@Import(CombinationActivityServiceImpl.class) -public class CombinationActivityServiceImplTest extends BaseDbUnitTest { - - @Resource - private CombinationActivityServiceImpl combinationActivityService; - - @Resource - private CombinationActivityMapper combinationActivityMapper; - - @Test - public void testCreateCombinationActivity_success() { - // 准备参数 - CombinationActivityCreateReqVO reqVO = randomPojo(CombinationActivityCreateReqVO.class); - - // 调用 - Long combinationActivityId = combinationActivityService.createCombinationActivity(reqVO); - // 断言 - assertNotNull(combinationActivityId); - // 校验记录的属性是否正确 - CombinationActivityDO combinationActivity = combinationActivityMapper.selectById(combinationActivityId); - assertPojoEquals(reqVO, combinationActivity); - } - - @Test - public void testUpdateCombinationActivity_success() { - // mock 数据 - CombinationActivityDO dbCombinationActivity = randomPojo(CombinationActivityDO.class); - combinationActivityMapper.insert(dbCombinationActivity);// @Sql: 先插入出一条存在的数据 - // 准备参数 - CombinationActivityUpdateReqVO reqVO = randomPojo(CombinationActivityUpdateReqVO.class, o -> { - o.setId(dbCombinationActivity.getId()); // 设置更新的 ID - }); - - // 调用 - combinationActivityService.updateCombinationActivity(reqVO); - // 校验是否更新正确 - CombinationActivityDO combinationActivity = combinationActivityMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, combinationActivity); - } - - @Test - public void testUpdateCombinationActivity_notExists() { - // 准备参数 - CombinationActivityUpdateReqVO reqVO = randomPojo(CombinationActivityUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> combinationActivityService.updateCombinationActivity(reqVO), COMBINATION_ACTIVITY_NOT_EXISTS); - } - - @Test - public void testDeleteCombinationActivity_success() { - // mock 数据 - CombinationActivityDO dbCombinationActivity = randomPojo(CombinationActivityDO.class); - combinationActivityMapper.insert(dbCombinationActivity);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbCombinationActivity.getId(); - - // 调用 - combinationActivityService.deleteCombinationActivity(id); - // 校验数据不存在了 - assertNull(combinationActivityMapper.selectById(id)); - } - - @Test - public void testDeleteCombinationActivity_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> combinationActivityService.deleteCombinationActivity(id), COMBINATION_ACTIVITY_NOT_EXISTS); - } - - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetCombinationActivityPage() { - // mock 数据 - CombinationActivityDO dbCombinationActivity = randomPojo(CombinationActivityDO.class, o -> { // 等会查询到 - o.setName(null); - //o.setSpuId(null); - o.setTotalLimitCount(null); - o.setSingleLimitCount(null); - o.setStartTime(null); - o.setEndTime(null); - o.setUserSize(null); - o.setVirtualGroup(null); - o.setStatus(null); - o.setLimitDuration(null); - o.setCreateTime(null); - }); - combinationActivityMapper.insert(dbCombinationActivity); - // 测试 name 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setName(null))); - // 测试 spuId 不匹配 - //combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setSpuId(null))); - // 测试 totalLimitCount 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setTotalLimitCount(null))); - // 测试 singleLimitCount 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setSingleLimitCount(null))); - // 测试 startTime 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setStartTime(null))); - // 测试 endTime 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setEndTime(null))); - // 测试 userSize 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setUserSize(null))); - // 测试 virtualGroup 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setVirtualGroup(null))); - // 测试 status 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setStatus(null))); - // 测试 limitDuration 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setLimitDuration(null))); - // 测试 createTime 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setCreateTime(null))); - // 准备参数 - CombinationActivityPageReqVO reqVO = new CombinationActivityPageReqVO(); - reqVO.setName(null); - reqVO.setStatus(null); - - // 调用 - PageResult pageResult = combinationActivityService.getCombinationActivityPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbCombinationActivity, pageResult.getList().get(0)); - } - - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetCombinationActivityList() { - // mock 数据 - CombinationActivityDO dbCombinationActivity = randomPojo(CombinationActivityDO.class, o -> { // 等会查询到 - o.setName(null); - //o.setSpuId(null); - o.setTotalLimitCount(null); - o.setSingleLimitCount(null); - o.setStartTime(null); - o.setEndTime(null); - o.setUserSize(null); - o.setVirtualGroup(null); - o.setStatus(null); - o.setLimitDuration(null); - o.setCreateTime(null); - }); - combinationActivityMapper.insert(dbCombinationActivity); - // 测试 name 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setName(null))); - // 测试 spuId 不匹配 - //combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setSpuId(null))); - // 测试 totalLimitCount 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setTotalLimitCount(null))); - // 测试 singleLimitCount 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setSingleLimitCount(null))); - // 测试 startTime 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setStartTime(null))); - // 测试 endTime 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setEndTime(null))); - // 测试 userSize 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setUserSize(null))); - // 测试 virtualGroup 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setVirtualGroup(null))); - // 测试 status 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setStatus(null))); - // 测试 limitDuration 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setLimitDuration(null))); - // 测试 createTime 不匹配 - combinationActivityMapper.insert(cloneIgnoreId(dbCombinationActivity, o -> o.setCreateTime(null))); - - } - -} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImplTest.java b/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImplTest.java deleted file mode 100755 index 5a41563e7..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponTemplateServiceImplTest.java +++ /dev/null @@ -1,147 +0,0 @@ -package cn.iocoder.yudao.module.promotion.service.coupon; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO; -import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; -import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponTemplateMapper; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; -import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; -import java.time.LocalDateTime; - -import static cn.hutool.core.util.RandomUtil.randomEle; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_NOT_EXISTS; -import static org.junit.jupiter.api.Assertions.*; - -/** -* {@link CouponTemplateServiceImpl} 的单元测试类 -* -* @author 芋道源码 -*/ -@Import(CouponTemplateServiceImpl.class) -public class CouponTemplateServiceImplTest extends BaseDbUnitTest { - - @Resource - private CouponTemplateServiceImpl couponTemplateService; - - @Resource - private CouponTemplateMapper couponTemplateMapper; - - @Test - public void testCreateCouponTemplate_success() { - // 准备参数 - CouponTemplateCreateReqVO reqVO = randomPojo(CouponTemplateCreateReqVO.class, - o -> o.setProductScope(randomEle(PromotionProductScopeEnum.values()).getScope()) - .setValidityType(randomEle(CouponTemplateValidityTypeEnum.values()).getType()) - .setDiscountType(randomEle(PromotionDiscountTypeEnum.values()).getType())); - - // 调用 - Long couponTemplateId = couponTemplateService.createCouponTemplate(reqVO); - // 断言 - assertNotNull(couponTemplateId); - // 校验记录的属性是否正确 - CouponTemplateDO couponTemplate = couponTemplateMapper.selectById(couponTemplateId); - assertPojoEquals(reqVO, couponTemplate); - } - - @Test - public void testUpdateCouponTemplate_success() { - // mock 数据 - CouponTemplateDO dbCouponTemplate = randomPojo(CouponTemplateDO.class); - couponTemplateMapper.insert(dbCouponTemplate);// @Sql: 先插入出一条存在的数据 - // 准备参数 - CouponTemplateUpdateReqVO reqVO = randomPojo(CouponTemplateUpdateReqVO.class, o -> { - o.setId(dbCouponTemplate.getId()); // 设置更新的 ID - // 其它通用字段 - o.setProductScope(randomEle(PromotionProductScopeEnum.values()).getScope()) - .setValidityType(randomEle(CouponTemplateValidityTypeEnum.values()).getType()) - .setDiscountType(randomEle(PromotionDiscountTypeEnum.values()).getType()); - }); - - // 调用 - couponTemplateService.updateCouponTemplate(reqVO); - // 校验是否更新正确 - CouponTemplateDO couponTemplate = couponTemplateMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, couponTemplate); - } - - @Test - public void testUpdateCouponTemplate_notExists() { - // 准备参数 - CouponTemplateUpdateReqVO reqVO = randomPojo(CouponTemplateUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> couponTemplateService.updateCouponTemplate(reqVO), COUPON_TEMPLATE_NOT_EXISTS); - } - - @Test - public void testDeleteCouponTemplate_success() { - // mock 数据 - CouponTemplateDO dbCouponTemplate = randomPojo(CouponTemplateDO.class); - couponTemplateMapper.insert(dbCouponTemplate);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbCouponTemplate.getId(); - - // 调用 - couponTemplateService.deleteCouponTemplate(id); - // 校验数据不存在了 - assertNull(couponTemplateMapper.selectById(id)); - } - - @Test - public void testDeleteCouponTemplate_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> couponTemplateService.deleteCouponTemplate(id), COUPON_TEMPLATE_NOT_EXISTS); - } - - @Test - public void testGetCouponTemplatePage() { - // mock 数据 - CouponTemplateDO dbCouponTemplate = randomPojo(CouponTemplateDO.class, o -> { // 等会查询到 - o.setName("芋艿"); - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - o.setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()); - o.setCreateTime(buildTime(2022, 2, 2)); - }); - couponTemplateMapper.insert(dbCouponTemplate); - // 测试 name 不匹配 - couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setName("土豆"))); - // 测试 status 不匹配 - couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); - // 测试 type 不匹配 - couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()))); - // 测试 createTime 不匹配 - couponTemplateMapper.insert(cloneIgnoreId(dbCouponTemplate, o -> o.setCreateTime(buildTime(2022, 1, 1)))); - // 准备参数 - CouponTemplatePageReqVO reqVO = new CouponTemplatePageReqVO(); - reqVO.setName("芋艿"); - reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); - reqVO.setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()); - reqVO.setCreateTime((new LocalDateTime[]{buildTime(2022, 2, 1), buildTime(2022, 2, 3)})); - - // 调用 - PageResult pageResult = couponTemplateService.getCouponTemplatePage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbCouponTemplate, pageResult.getList().get(0)); - } - -} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/decorate/DecorateComponentServiceImplTest.java b/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/decorate/DecorateComponentServiceImplTest.java deleted file mode 100644 index 95c542b67..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/decorate/DecorateComponentServiceImplTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.module.promotion.service.decorate; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.promotion.dal.mysql.decorate.DecorateComponentMapper; -import org.junit.jupiter.api.BeforeEach; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -// TODO @芋艿:后续 review 下 -/** - * @author jason - */ -public class DecorateComponentServiceImplTest extends BaseMockitoUnitTest { - - @InjectMocks - private DecorateComponentServiceImpl decoratePageService; - - @Mock - private DecorateComponentMapper decorateComponentMapper; - - @BeforeEach - public void init(){ - - } - -// @Test -// void testResp(){ -// List list = new ArrayList<>(1); -// DecorateComponentDO decorateDO = new DecorateComponentDO() -// .setPage(INDEX.getPage()).setValue("") -// .setCode(ROLLING_NEWS.getCode()).setId(1L); -// list.add(decorateDO); -// //mock 方法 -// Mockito.when(decorateComponentMapper.selectListByPageAndStatus(eq(1))).thenReturn(list); -// } -} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImplTest.java b/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImplTest.java deleted file mode 100755 index 5ad517463..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImplTest.java +++ /dev/null @@ -1,210 +0,0 @@ -package cn.iocoder.yudao.module.promotion.service.discount; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityBaseVO; -import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityCreateReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityPageReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityUpdateReqVO; -import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO; -import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO; -import cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountActivityMapper; -import cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountProductMapper; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.Date; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DISCOUNT_ACTIVITY_NOT_EXISTS; -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.*; - -/** -* {@link DiscountActivityServiceImpl} 的单元测试类 -* -* @author 芋道源码 -*/ -@Import(DiscountActivityServiceImpl.class) -public class DiscountActivityServiceImplTest extends BaseDbUnitTest { - - @Resource - private DiscountActivityServiceImpl discountActivityService; - - @Resource - private DiscountActivityMapper discountActivityMapper; - @Resource - private DiscountProductMapper discountProductMapper; - - @Test - public void testCreateDiscountActivity_success() { - // 准备参数 - DiscountActivityCreateReqVO reqVO = randomPojo(DiscountActivityCreateReqVO.class, o -> { - // 用于触发进行中的状态 - o.setStartTime(addTime(Duration.ofDays(1))).setEndTime(addTime(Duration.ofDays(2))); - // 设置商品 - o.setProducts(asList(new DiscountActivityBaseVO.Product().setSpuId(1L).setSkuId(2L) - .setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()).setDiscountPrice(3), - new DiscountActivityBaseVO.Product().setSpuId(10L).setSkuId(20L) - .setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()).setDiscountPercent(30))); - }); - - // 调用 - Long discountActivityId = discountActivityService.createDiscountActivity(reqVO); - // 断言 - assertNotNull(discountActivityId); - // 校验活动 - DiscountActivityDO discountActivity = discountActivityMapper.selectById(discountActivityId); - assertPojoEquals(reqVO, discountActivity); - assertEquals(discountActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus()); - // 校验商品 - List discountProducts = discountProductMapper.selectList(DiscountProductDO::getActivityId, discountActivity.getId()); - assertEquals(discountProducts.size(), reqVO.getProducts().size()); - for (int i = 0; i < reqVO.getProducts().size(); i++) { - DiscountActivityBaseVO.Product product = reqVO.getProducts().get(i); - DiscountProductDO discountProduct = discountProducts.get(i); - assertEquals(discountProduct.getActivityId(), discountActivity.getId()); - assertEquals(discountProduct.getSpuId(), product.getSpuId()); - assertEquals(discountProduct.getSkuId(), product.getSkuId()); - assertEquals(discountProduct.getDiscountType(), product.getDiscountType()); - assertEquals(discountProduct.getDiscountPrice(), product.getDiscountPrice()); - assertEquals(discountProduct.getDiscountPercent(), product.getDiscountPercent()); - } - } - - @Test - public void testUpdateDiscountActivity_success() { - // mock 数据(商品) - DiscountActivityDO dbDiscountActivity = randomPojo(DiscountActivityDO.class); - discountActivityMapper.insert(dbDiscountActivity);// @Sql: 先插入出一条存在的数据 - // mock 数据(活动) - DiscountProductDO dbDiscountProduct01 = randomPojo(DiscountProductDO.class, o -> o.setActivityId(dbDiscountActivity.getId()) - .setSpuId(1L).setSkuId(2L).setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()).setDiscountPrice(3).setDiscountPercent(null)); - DiscountProductDO dbDiscountProduct02 = randomPojo(DiscountProductDO.class, o -> o.setActivityId(dbDiscountActivity.getId()) - .setSpuId(10L).setSkuId(20L).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()).setDiscountPercent(30).setDiscountPrice(null)); - discountProductMapper.insert(dbDiscountProduct01); - discountProductMapper.insert(dbDiscountProduct02); - // 准备参数 - DiscountActivityUpdateReqVO reqVO = randomPojo(DiscountActivityUpdateReqVO.class, o -> { - o.setId(dbDiscountActivity.getId()); // 设置更新的 ID - // 用于触发进行中的状态 - o.setStartTime(addTime(Duration.ofDays(1))).setEndTime(addTime(Duration.ofDays(2))); - // 设置商品 - o.setProducts(asList(new DiscountActivityBaseVO.Product().setSpuId(1L).setSkuId(2L) - .setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()).setDiscountPrice(3).setDiscountPercent(null), - new DiscountActivityBaseVO.Product().setSpuId(100L).setSkuId(200L) - .setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()).setDiscountPercent(30).setDiscountPrice(null))); - }); - - // 调用 - discountActivityService.updateDiscountActivity(reqVO); - // 校验活动 - DiscountActivityDO discountActivity = discountActivityMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, discountActivity); - assertEquals(discountActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus()); - // 校验商品 - List discountProducts = discountProductMapper.selectList(DiscountProductDO::getActivityId, discountActivity.getId()); - assertEquals(discountProducts.size(), reqVO.getProducts().size()); - for (int i = 0; i < reqVO.getProducts().size(); i++) { - DiscountActivityBaseVO.Product product = reqVO.getProducts().get(i); - DiscountProductDO discountProduct = discountProducts.get(i); - assertEquals(discountProduct.getActivityId(), discountActivity.getId()); - assertEquals(discountProduct.getSpuId(), product.getSpuId()); - assertEquals(discountProduct.getSkuId(), product.getSkuId()); - assertEquals(discountProduct.getDiscountType(), product.getDiscountType()); - assertEquals(discountProduct.getDiscountPrice(), product.getDiscountPrice()); - assertEquals(discountProduct.getDiscountPercent(), product.getDiscountPercent()); - } - } - - @Test - public void testCloseDiscountActivity() { - // mock 数据 - DiscountActivityDO dbDiscountActivity = randomPojo(DiscountActivityDO.class, - o -> o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus())); - discountActivityMapper.insert(dbDiscountActivity);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbDiscountActivity.getId(); - - // 调用 - discountActivityService.closeRewardActivity(id); - // 校验状态 - DiscountActivityDO discountActivity = discountActivityMapper.selectById(id); - assertEquals(discountActivity.getStatus(), PromotionActivityStatusEnum.CLOSE.getStatus()); - } - - @Test - public void testUpdateDiscountActivity_notExists() { - // 准备参数 - DiscountActivityUpdateReqVO reqVO = randomPojo(DiscountActivityUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> discountActivityService.updateDiscountActivity(reqVO), DISCOUNT_ACTIVITY_NOT_EXISTS); - } - - @Test - public void testDeleteDiscountActivity_success() { - // mock 数据 - DiscountActivityDO dbDiscountActivity = randomPojo(DiscountActivityDO.class, - o -> o.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus())); - discountActivityMapper.insert(dbDiscountActivity);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbDiscountActivity.getId(); - - // 调用 - discountActivityService.deleteDiscountActivity(id); - // 校验数据不存在了 - assertNull(discountActivityMapper.selectById(id)); - } - - @Test - public void testDeleteDiscountActivity_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> discountActivityService.deleteDiscountActivity(id), DISCOUNT_ACTIVITY_NOT_EXISTS); - } - - @Test - public void testGetDiscountActivityPage() { - // mock 数据 - DiscountActivityDO dbDiscountActivity = randomPojo(DiscountActivityDO.class, o -> { // 等会查询到 - o.setName("芋艿"); - o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus()); - o.setCreateTime(buildTime(2021, 1, 15)); - }); - discountActivityMapper.insert(dbDiscountActivity); - // 测试 name 不匹配 - discountActivityMapper.insert(cloneIgnoreId(dbDiscountActivity, o -> o.setName("土豆"))); - // 测试 status 不匹配 - discountActivityMapper.insert(cloneIgnoreId(dbDiscountActivity, o -> o.setStatus(PromotionActivityStatusEnum.END.getStatus()))); - // 测试 createTime 不匹配 - discountActivityMapper.insert(cloneIgnoreId(dbDiscountActivity, o -> o.setCreateTime(buildTime(2021, 2, 10)))); - // 准备参数 - DiscountActivityPageReqVO reqVO = new DiscountActivityPageReqVO(); - reqVO.setName("芋艿"); - reqVO.setStatus(PromotionActivityStatusEnum.WAIT.getStatus()); - reqVO.setCreateTime((new LocalDateTime[]{buildTime(2021, 1, 1), buildTime(2021, 1, 31)})); - - // 调用 - PageResult pageResult = discountActivityService.getDiscountActivityPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbDiscountActivity, pageResult.getList().get(0)); - } - -} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java b/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java deleted file mode 100755 index f06104b22..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImplTest.java +++ /dev/null @@ -1,218 +0,0 @@ -package cn.iocoder.yudao.module.promotion.service.reward; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityCreateReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO; -import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; -import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; -import java.time.Duration; -import java.util.Map; -import java.util.Set; - -import static cn.hutool.core.util.RandomUtil.randomEle; -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.REWARD_ACTIVITY_NOT_EXISTS; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.*; - -/** -* {@link RewardActivityServiceImpl} 的单元测试类 -* -* @author 芋道源码 -*/ -@Import(RewardActivityServiceImpl.class) -public class RewardActivityServiceImplTest extends BaseDbUnitTest { - - @Resource - private RewardActivityServiceImpl rewardActivityService; - - @Resource - private RewardActivityMapper rewardActivityMapper; - - @Test - public void testCreateRewardActivity_success() { - // 准备参数 - RewardActivityCreateReqVO reqVO = randomPojo(RewardActivityCreateReqVO.class, o -> { - o.setConditionType(randomEle(PromotionConditionTypeEnum.values()).getType()); - o.setProductScope(randomEle(PromotionProductScopeEnum.values()).getScope()); - // 用于触发进行中的状态 - o.setStartTime(addTime(Duration.ofDays(1))).setEndTime(addTime(Duration.ofDays(2))); - }); - - // 调用 - Long rewardActivityId = rewardActivityService.createRewardActivity(reqVO); - // 断言 - assertNotNull(rewardActivityId); - // 校验记录的属性是否正确 - RewardActivityDO rewardActivity = rewardActivityMapper.selectById(rewardActivityId); - assertPojoEquals(reqVO, rewardActivity, "rules"); - assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus()); - for (int i = 0; i < reqVO.getRules().size(); i++) { - assertPojoEquals(reqVO.getRules().get(i), rewardActivity.getRules().get(i)); - } - } - - @Test - public void testUpdateRewardActivity_success() { - // mock 数据 - RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus())); - rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据 - // 准备参数 - RewardActivityUpdateReqVO reqVO = randomPojo(RewardActivityUpdateReqVO.class, o -> { - o.setId(dbRewardActivity.getId()); // 设置更新的 ID - o.setConditionType(randomEle(PromotionConditionTypeEnum.values()).getType()); - o.setProductScope(randomEle(PromotionProductScopeEnum.values()).getScope()); - // 用于触发进行中的状态 - o.setStartTime(addTime(Duration.ofDays(1))).setEndTime(addTime(Duration.ofDays(2))); - }); - - // 调用 - rewardActivityService.updateRewardActivity(reqVO); - // 校验是否更新正确 - RewardActivityDO rewardActivity = rewardActivityMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, rewardActivity, "rules"); - assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.WAIT.getStatus()); - for (int i = 0; i < reqVO.getRules().size(); i++) { - assertPojoEquals(reqVO.getRules().get(i), rewardActivity.getRules().get(i)); - } - } - - @Test - public void testCloseRewardActivity() { - // mock 数据 - RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.WAIT.getStatus())); - rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbRewardActivity.getId(); - - // 调用 - rewardActivityService.closeRewardActivity(id); - // 校验状态 - RewardActivityDO rewardActivity = rewardActivityMapper.selectById(id); - assertEquals(rewardActivity.getStatus(), PromotionActivityStatusEnum.CLOSE.getStatus()); - } - - @Test - public void testUpdateRewardActivity_notExists() { - // 准备参数 - RewardActivityUpdateReqVO reqVO = randomPojo(RewardActivityUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> rewardActivityService.updateRewardActivity(reqVO), REWARD_ACTIVITY_NOT_EXISTS); - } - - @Test - public void testDeleteRewardActivity_success() { - // mock 数据 - RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus())); - rewardActivityMapper.insert(dbRewardActivity);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbRewardActivity.getId(); - - // 调用 - rewardActivityService.deleteRewardActivity(id); - // 校验数据不存在了 - assertNull(rewardActivityMapper.selectById(id)); - } - - @Test - public void testDeleteRewardActivity_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> rewardActivityService.deleteRewardActivity(id), REWARD_ACTIVITY_NOT_EXISTS); - } - - @Test - public void testGetRewardActivityPage() { - // mock 数据 - RewardActivityDO dbRewardActivity = randomPojo(RewardActivityDO.class, o -> { // 等会查询到 - o.setName("芋艿"); - o.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus()); - }); - rewardActivityMapper.insert(dbRewardActivity); - // 测试 name 不匹配 - rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setName("土豆"))); - // 测试 status 不匹配 - rewardActivityMapper.insert(cloneIgnoreId(dbRewardActivity, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus()))); - // 准备参数 - RewardActivityPageReqVO reqVO = new RewardActivityPageReqVO(); - reqVO.setName("芋艿"); - reqVO.setStatus(PromotionActivityStatusEnum.CLOSE.getStatus()); - - // 调用 - PageResult pageResult = rewardActivityService.getRewardActivityPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbRewardActivity, pageResult.getList().get(0), "rules"); - } - - @Test - public void testGetRewardActivities_all() { - // mock 数据 - RewardActivityDO allActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus()) - .setProductScope(PromotionProductScopeEnum.ALL.getScope())); - rewardActivityMapper.insert(allActivity); - RewardActivityDO productActivity = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus()) - .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L))); - rewardActivityMapper.insert(productActivity); - // 准备参数 - Set spuIds = asSet(1L, 2L); - - // 调用 TODO getMatchRewardActivities 没有这个方法,但是找到了 getMatchRewardActivityList - //Map> matchRewardActivities = rewardActivityService.getMatchRewardActivities(spuIds); - // 断言 - //assertEquals(matchRewardActivities.size(), 1); - //Map.Entry> next = matchRewardActivities.entrySet().iterator().next(); - //assertPojoEquals(next.getKey(), allActivity); - //assertEquals(next.getValue(), spuIds); - } - - @Test - public void testGetRewardActivities_product() { - // mock 数据 - RewardActivityDO productActivity01 = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus()) - .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(asList(1L, 2L))); - rewardActivityMapper.insert(productActivity01); - RewardActivityDO productActivity02 = randomPojo(RewardActivityDO.class, o -> o.setStatus(PromotionActivityStatusEnum.RUN.getStatus()) - .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductSpuIds(singletonList(3L))); - rewardActivityMapper.insert(productActivity02); - // 准备参数 - Set spuIds = asSet(1L, 2L, 3L); - - // 调用 TODO getMatchRewardActivities 没有这个方法,但是找到了 getMatchRewardActivityList - //Map> matchRewardActivities = rewardActivityService.getMatchRewardActivities(spuIds); - // 断言 - //assertEquals(matchRewardActivities.size(), 2); - //matchRewardActivities.forEach((activity, activitySpuIds) -> { - // if (activity.getId().equals(productActivity01.getId())) { - // assertPojoEquals(activity, productActivity01); - // assertEquals(activitySpuIds, asSet(1L, 2L)); - // } else if (activity.getId().equals(productActivity02.getId())) { - // assertPojoEquals(activity, productActivity02); - // assertEquals(activitySpuIds, asSet(3L)); - // } else { - // fail(); - // } - //}); - } - -} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/seckillactivity/SeckillActivityServiceImplTest.java b/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/seckillactivity/SeckillActivityServiceImplTest.java deleted file mode 100644 index 93dbecad9..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/seckillactivity/SeckillActivityServiceImplTest.java +++ /dev/null @@ -1,171 +0,0 @@ -package cn.iocoder.yudao.module.promotion.service.seckillactivity; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO; -import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; -import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillActivityMapper; -import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityServiceImpl; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.SECKILL_ACTIVITY_NOT_EXISTS; -import static org.junit.jupiter.api.Assertions.*; - -/** -* {@link SeckillActivityServiceImpl} 的单元测试类 -* -* @author 芋道源码 -*/ -@Import(SeckillActivityServiceImpl.class) -@Disabled // TODO 芋艿:未来开启 -public class SeckillActivityServiceImplTest extends BaseDbUnitTest { - - @Resource - private SeckillActivityServiceImpl seckillActivityService; - - @Resource - private SeckillActivityMapper seckillActivityMapper; - - @Test - public void testCreateSeckillActivity_success() { - // 准备参数 - SeckillActivityCreateReqVO reqVO = randomPojo(SeckillActivityCreateReqVO.class); - - // 调用 - Long seckillActivityId = seckillActivityService.createSeckillActivity(reqVO); - // 断言 - assertNotNull(seckillActivityId); - // 校验记录的属性是否正确 - SeckillActivityDO seckillActivity = seckillActivityMapper.selectById(seckillActivityId); - assertPojoEquals(reqVO, seckillActivity); - } - - @Test - public void testUpdateSeckillActivity_success() { - // mock 数据 - SeckillActivityDO dbSeckillActivity = randomPojo(SeckillActivityDO.class); - seckillActivityMapper.insert(dbSeckillActivity);// @Sql: 先插入出一条存在的数据 - // 准备参数 - SeckillActivityUpdateReqVO reqVO = randomPojo(SeckillActivityUpdateReqVO.class, o -> { - o.setId(dbSeckillActivity.getId()); // 设置更新的 ID - }); - - // 调用 - seckillActivityService.updateSeckillActivity(reqVO); - // 校验是否更新正确 - SeckillActivityDO seckillActivity = seckillActivityMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, seckillActivity); - } - - @Test - public void testUpdateSeckillActivity_notExists() { - // 准备参数 - SeckillActivityUpdateReqVO reqVO = randomPojo(SeckillActivityUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> seckillActivityService.updateSeckillActivity(reqVO), SECKILL_ACTIVITY_NOT_EXISTS); - } - - @Test - public void testDeleteSeckillActivity_success() { - // mock 数据 - SeckillActivityDO dbSeckillActivity = randomPojo(SeckillActivityDO.class); - seckillActivityMapper.insert(dbSeckillActivity);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbSeckillActivity.getId(); - - // 调用 - seckillActivityService.deleteSeckillActivity(id); - // 校验数据不存在了 - assertNull(seckillActivityMapper.selectById(id)); - } - - @Test - public void testDeleteSeckillActivity_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> seckillActivityService.deleteSeckillActivity(id), SECKILL_ACTIVITY_NOT_EXISTS); - } - - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetSeckillActivityPage() { - // mock 数据 - SeckillActivityDO dbSeckillActivity = randomPojo(SeckillActivityDO.class, o -> { // 等会查询到 - o.setName(null); - o.setStatus(null); - o.setConfigIds(null); - o.setCreateTime(null); - }); - seckillActivityMapper.insert(dbSeckillActivity); - // 测试 name 不匹配 - seckillActivityMapper.insert(cloneIgnoreId(dbSeckillActivity, o -> o.setName(null))); - // 测试 status 不匹配 - seckillActivityMapper.insert(cloneIgnoreId(dbSeckillActivity, o -> o.setStatus(null))); - // 测试 timeId 不匹配 - seckillActivityMapper.insert(cloneIgnoreId(dbSeckillActivity, o -> o.setConfigIds(null))); - // 测试 createTime 不匹配 - seckillActivityMapper.insert(cloneIgnoreId(dbSeckillActivity, o -> o.setCreateTime(null))); - // 准备参数 - SeckillActivityPageReqVO reqVO = new SeckillActivityPageReqVO(); - reqVO.setName(null); - reqVO.setStatus(null); - reqVO.setConfigId(null); - reqVO.setCreateTime((new LocalDateTime[]{})); - - // 调用 - PageResult pageResult = seckillActivityService.getSeckillActivityPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbSeckillActivity, pageResult.getList().get(0)); - } - - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetSeckillActivityList() { - // mock 数据 - SeckillActivityDO dbSeckillActivity = randomPojo(SeckillActivityDO.class, o -> { // 等会查询到 - o.setName(null); - o.setStatus(null); - o.setConfigIds(null); - o.setCreateTime(null); - }); - seckillActivityMapper.insert(dbSeckillActivity); - // 测试 name 不匹配 - seckillActivityMapper.insert(cloneIgnoreId(dbSeckillActivity, o -> o.setName(null))); - // 测试 status 不匹配 - seckillActivityMapper.insert(cloneIgnoreId(dbSeckillActivity, o -> o.setStatus(null))); - // 测试 timeId 不匹配 - seckillActivityMapper.insert(cloneIgnoreId(dbSeckillActivity, o -> o.setConfigIds(null))); - // 测试 createTime 不匹配 - seckillActivityMapper.insert(cloneIgnoreId(dbSeckillActivity, o -> o.setCreateTime(null))); - // 准备参数 -// SeckillActivityExportReqVO reqVO = new SeckillActivityExportReqVO(); -// reqVO.setName(null); -// reqVO.setStatus(null); -// reqVO.setTimeId(null); -// reqVO.setCreateTime((new Date[]{})); -// -// // 调用 -// List list = seckillActivityService.getSeckillActivityList(reqVO); -// // 断言 -// assertEquals(1, list.size()); -// assertPojoEquals(dbSeckillActivity, list.get(0)); - } - -} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/seckillconfig/SeckillConfigServiceImplTest.java b/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/seckillconfig/SeckillConfigServiceImplTest.java deleted file mode 100644 index 28dee3382..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/seckillconfig/SeckillConfigServiceImplTest.java +++ /dev/null @@ -1,190 +0,0 @@ -package cn.iocoder.yudao.module.promotion.service.seckillconfig; - -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigCreateReqVO; -import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config.SeckillConfigUpdateReqVO; -import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO; -import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillconfig.SeckillConfigMapper; -import cn.iocoder.yudao.module.promotion.service.seckill.SeckillConfigServiceImpl; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; - -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.SECKILL_CONFIG_NOT_EXISTS; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -/** - * {@link SeckillConfigServiceImpl} 的单元测试类 - * - * @author 芋道源码 - */ -@Import(SeckillConfigServiceImpl.class) -@Disabled // TODO 芋艿:未来开启;后续要 review 下 -public class SeckillConfigServiceImplTest extends BaseDbUnitTest { - - @Resource - private SeckillConfigServiceImpl SeckillConfigService; - - @Resource - private SeckillConfigMapper seckillConfigMapper; - - @Resource - private ObjectMapper objectMapper; - - @Test - public void testJacksonSerializ() { - - // 准备参数 - SeckillConfigCreateReqVO reqVO = randomPojo(SeckillConfigCreateReqVO.class); -// ObjectMapper objectMapper = new ObjectMapper(); - try { - String string = objectMapper.writeValueAsString(reqVO); - System.out.println(string); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } - - - } - - @Test - public void testCreateSeckillConfig_success() { - // 准备参数 - SeckillConfigCreateReqVO reqVO = randomPojo(SeckillConfigCreateReqVO.class); - - // 调用 - Long SeckillConfigId = SeckillConfigService.createSeckillConfig(reqVO); - // 断言 - assertNotNull(SeckillConfigId); - // 校验记录的属性是否正确 - SeckillConfigDO SeckillConfig = seckillConfigMapper.selectById(SeckillConfigId); - assertPojoEquals(reqVO, SeckillConfig); - } - - @Test - public void testUpdateSeckillConfig_success() { - // mock 数据 - SeckillConfigDO dbSeckillConfig = randomPojo(SeckillConfigDO.class); - seckillConfigMapper.insert(dbSeckillConfig);// @Sql: 先插入出一条存在的数据 - // 准备参数 - SeckillConfigUpdateReqVO reqVO = randomPojo(SeckillConfigUpdateReqVO.class, o -> { - o.setId(dbSeckillConfig.getId()); // 设置更新的 ID - }); - - // 调用 - SeckillConfigService.updateSeckillConfig(reqVO); - // 校验是否更新正确 - SeckillConfigDO SeckillConfig = seckillConfigMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, SeckillConfig); - } - - @Test - public void testUpdateSeckillConfig_notExists() { - // 准备参数 - SeckillConfigUpdateReqVO reqVO = randomPojo(SeckillConfigUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> SeckillConfigService.updateSeckillConfig(reqVO), SECKILL_CONFIG_NOT_EXISTS); - } - - @Test - public void testDeleteSeckillConfig_success() { - // mock 数据 - SeckillConfigDO dbSeckillConfig = randomPojo(SeckillConfigDO.class); - seckillConfigMapper.insert(dbSeckillConfig);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbSeckillConfig.getId(); - - // 调用 - SeckillConfigService.deleteSeckillConfig(id); - // 校验数据不存在了 - assertNull(seckillConfigMapper.selectById(id)); - } - - @Test - public void testDeleteSeckillConfig_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> SeckillConfigService.deleteSeckillConfig(id), SECKILL_CONFIG_NOT_EXISTS); - } - - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetSeckillConfigPage() { - // mock 数据 -// SeckillConfigDO dbSeckillConfig = randomPojo(SeckillConfigDO.class, o -> { // 等会查询到 -// o.setName(null); -// o.setStartTime(null); -// o.setEndTime(null); -// o.setCreateTime(null); -// }); -// seckillConfigMapper.insert(dbSeckillConfig); -// // 测试 name 不匹配 -// seckillConfigMapper.insert(cloneIgnoreId(dbSeckillConfig, o -> o.setName(null))); -// // 测试 startTime 不匹配 -// seckillConfigMapper.insert(cloneIgnoreId(dbSeckillConfig, o -> o.setStartTime(null))); -// // 测试 endTime 不匹配 -// seckillConfigMapper.insert(cloneIgnoreId(dbSeckillConfig, o -> o.setEndTime(null))); -// // 测试 createTime 不匹配 -// seckillConfigMapper.insert(cloneIgnoreId(dbSeckillConfig, o -> o.setCreateTime(null))); -// // 准备参数 -// SeckillConfigPageReqVO reqVO = new SeckillConfigPageReqVO(); -// reqVO.setName(null); -//// reqVO.setStartTime((new LocalTime())); -//// reqVO.setEndTime((new LocalTime[]{})); -//// reqVO.setCreateTime((new Date[]{})); -// -// // 调用 -// PageResult pageResult = SeckillConfigService.getSeckillConfigPage(reqVO); -// // 断言 -// assertEquals(1, pageResult.getTotal()); -// assertEquals(1, pageResult.getList().size()); -// assertPojoEquals(dbSeckillConfig, pageResult.getList().get(0)); - } - - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetSeckillConfigList() { - // mock 数据 - SeckillConfigDO dbSeckillConfig = randomPojo(SeckillConfigDO.class, o -> { // 等会查询到 - o.setName(null); - o.setStartTime(null); - o.setEndTime(null); - o.setCreateTime(null); - }); - seckillConfigMapper.insert(dbSeckillConfig); - // 测试 name 不匹配 - seckillConfigMapper.insert(cloneIgnoreId(dbSeckillConfig, o -> o.setName(null))); - // 测试 startTime 不匹配 - seckillConfigMapper.insert(cloneIgnoreId(dbSeckillConfig, o -> o.setStartTime(null))); - // 测试 endTime 不匹配 - seckillConfigMapper.insert(cloneIgnoreId(dbSeckillConfig, o -> o.setEndTime(null))); - // 测试 createTime 不匹配 - seckillConfigMapper.insert(cloneIgnoreId(dbSeckillConfig, o -> o.setCreateTime(null))); - // 准备参数 -// SeckillConfigExportReqVO reqVO = new SeckillConfigExportReqVO(); -// reqVO.setName(null); -// reqVO.setStartTime((new LocalTime[]{})); -// reqVO.setEndTime((new LocalTime[]{})); -// reqVO.setCreateTime((new Date[]{})); -// -// // 调用 -// List list = SeckillConfigService.getSeckillConfigList(reqVO); -// // 断言 -// assertEquals(1, list.size()); -// assertPojoEquals(dbSeckillConfig, list.get(0)); - } - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageUserController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageUserController.java index 0f1224d30..7844b93e5 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageUserController.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageUserController.java @@ -77,6 +77,7 @@ public class BrokerageUserController { @PreAuthorize("@ss.hasPermission('trade:brokerage-user:query')") public CommonResult getBrokerageUser(@RequestParam("id") Long id) { BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(id); + // TODO @疯狂:是不是搞成一个统一的 convert? BrokerageUserRespVO respVO = BrokerageUserConvert.INSTANCE.convert(brokerageUser); return success(BrokerageUserConvert.INSTANCE.copyTo(memberUserApi.getUser(id).getCheckedData(), respVO)); } 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 1d8d00753..2ed9ec2de 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 @@ -232,7 +232,8 @@ public class BrokerageUserServiceImpl implements BrokerageUserService { return PageResult.empty(); } // 1.2 根据昵称过滤下级用户 - Map userMap = convertMapByFilter(memberUserApi.getUserList(childIds).getCheckedData(), + List users = memberUserApi.getUserList(childIds).getCheckedData(); + Map userMap = convertMapByFilter(users, user -> StrUtil.contains(user.getNickname(), pageReqVO.getNickname()), MemberUserRespDTO::getId); if (CollUtil.isEmpty(userMap)) { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/Kd100ExpressClientIntegrationTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/Kd100ExpressClientIntegrationTest.java deleted file mode 100644 index 11b027bc8..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/Kd100ExpressClientIntegrationTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package cn.iocoder.yudao.module.trade.framework.delivery.core.client.impl; - -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressProperties; -import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO; -import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO; -import cn.iocoder.yudao.module.trade.framework.delivery.core.client.impl.kd100.Kd100ExpressClient; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.web.client.RestTemplate; - -import java.util.List; - -/** - * {@link Kd100ExpressClient} 的集成测试 - * - * @author jason - */ -@Slf4j -public class Kd100ExpressClientIntegrationTest { - - private Kd100ExpressClient client; - - @BeforeEach - public void init() { - RestTemplate restTemplate = new RestTemplateBuilder().build(); - TradeExpressProperties.Kd100Config config = new TradeExpressProperties.Kd100Config() - .setKey("pLXUGAwK5305") - .setCustomer("E77DF18BE109F454A5CD319E44BF5177"); - client = new Kd100ExpressClient(restTemplate, config); - } - - @Test - @Disabled("集成测试,暂时忽略") - public void testGetExpressTrackList() { - ExpressTrackQueryReqDTO reqDTO = new ExpressTrackQueryReqDTO(); - reqDTO.setExpressCode("STO"); - reqDTO.setLogisticsNo("773220402764314"); - List tracks = client.getExpressTrackList(reqDTO); - System.out.println(JsonUtils.toJsonPrettyString(tracks)); - } - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/KdNiaoExpressClientIntegrationTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/KdNiaoExpressClientIntegrationTest.java deleted file mode 100644 index a270c4b84..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/framework/delivery/core/client/impl/KdNiaoExpressClientIntegrationTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package cn.iocoder.yudao.module.trade.framework.delivery.core.client.impl; - -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.trade.framework.delivery.config.TradeExpressProperties; -import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO; -import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO; -import cn.iocoder.yudao.module.trade.framework.delivery.core.client.impl.kdniao.KdNiaoExpressClient; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.web.client.RestTemplate; - -import java.util.List; - -/** - * {@link KdNiaoExpressClient} 的集成测试 - * - * @author jason - */ -@Slf4j -public class KdNiaoExpressClientIntegrationTest { - - private KdNiaoExpressClient client; - - @BeforeEach - public void init() { - RestTemplate restTemplate = new RestTemplateBuilder().build(); - TradeExpressProperties.KdNiaoConfig config = new TradeExpressProperties.KdNiaoConfig() - .setApiKey("cb022f1e-48f1-4c4a-a723-9001ac9676b8") - .setBusinessId("1809751"); - client = new KdNiaoExpressClient(restTemplate, config); - } - - @Test - @Disabled("集成测试,暂时忽略") - public void testGetExpressTrackList() { - ExpressTrackQueryReqDTO reqDTO = new ExpressTrackQueryReqDTO(); - reqDTO.setExpressCode("STO"); - reqDTO.setLogisticsNo("777168349863987"); - List tracks = client.getExpressTrackList(reqDTO); - System.out.println(JsonUtils.toJsonPrettyString(tracks)); - } - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceTest.java deleted file mode 100644 index 3f28bccb5..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceTest.java +++ /dev/null @@ -1,156 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.aftersale; - -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi; -import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO; -import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO; -import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO; -import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleLogDO; -import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; -import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; -import cn.iocoder.yudao.module.trade.dal.mysql.aftersale.AfterSaleLogMapper; -import cn.iocoder.yudao.module.trade.dal.mysql.aftersale.AfterSaleMapper; -import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleStatusEnum; -import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleTypeEnum; -import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleWayEnum; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; -import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; -import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; -import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -/** - * {@link AfterSaleService} 的单元测试 - * - * @author 芋道源码 - */ -@Import(AfterSaleServiceImpl.class) -public class AfterSaleServiceTest extends BaseDbUnitTest { - - @Resource - private AfterSaleServiceImpl tradeAfterSaleService; - - @Resource - private AfterSaleMapper tradeAfterSaleMapper; - @Resource - private AfterSaleLogMapper tradeAfterSaleLogMapper; - - @MockBean - private TradeOrderUpdateService tradeOrderUpdateService; - @Resource - private TradeOrderQueryService tradeOrderQueryService; - - @MockBean - private PayRefundApi payRefundApi; - - @MockBean - private TradeOrderProperties tradeOrderProperties; - - @Test - public void testCreateAfterSale() { - // 准备参数 - Long userId = 1024L; - AppAfterSaleCreateReqVO createReqVO = new AppAfterSaleCreateReqVO() - .setOrderItemId(1L).setRefundPrice(100).setWay(AfterSaleWayEnum.RETURN_AND_REFUND.getWay()) - .setApplyReason("退钱").setApplyDescription("快退") - .setApplyPicUrls(asList("https://www.baidu.com/1.png", "https://www.baidu.com/2.png")); - // mock 方法(交易订单项) - TradeOrderItemDO orderItem = randomPojo(TradeOrderItemDO.class, o -> { - o.setOrderId(111L).setUserId(userId).setPayPrice(200); - o.setAfterSaleStatus(TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); - }); - when(tradeOrderQueryService.getOrderItem(eq(1024L), eq(1L))) - .thenReturn(orderItem); - // mock 方法(交易订单) - TradeOrderDO order = randomPojo(TradeOrderDO.class, o -> o.setStatus(TradeOrderStatusEnum.DELIVERED.getStatus()) - .setNo("202211301234")); - when(tradeOrderQueryService.getOrder(eq(1024L), eq(111L))).thenReturn(order); - - // 调用 - Long afterSaleId = tradeAfterSaleService.createAfterSale(userId, createReqVO); - // 断言(TradeAfterSaleDO) - AfterSaleDO afterSale = tradeAfterSaleMapper.selectById(afterSaleId); - assertNotNull(afterSale.getNo()); - assertEquals(afterSale.getStatus(), AfterSaleStatusEnum.APPLY.getStatus()); - assertEquals(afterSale.getType(), AfterSaleTypeEnum.IN_SALE.getType()); - assertPojoEquals(afterSale, createReqVO); - assertEquals(afterSale.getUserId(), 1024L); - assertPojoEquals(afterSale, orderItem, "id", "creator", "createTime", "updater", "updateTime"); - assertEquals(afterSale.getOrderNo(), "202211301234"); - assertNull(afterSale.getPayRefundId()); - assertNull(afterSale.getRefundTime()); - assertNull(afterSale.getLogisticsId()); - assertNull(afterSale.getLogisticsNo()); - assertNull(afterSale.getDeliveryTime()); - assertNull(afterSale.getReceiveReason()); - // 断言(TradeAfterSaleLogDO) - AfterSaleLogDO afterSaleLog = tradeAfterSaleLogMapper.selectList().get(0); - assertEquals(afterSaleLog.getUserId(), userId); - assertEquals(afterSaleLog.getUserType(), UserTypeEnum.MEMBER.getValue()); - assertEquals(afterSaleLog.getAfterSaleId(), afterSaleId); - assertPojoEquals(afterSale, orderItem, "id", "creator", "createTime", "updater", "updateTime"); - assertEquals(afterSaleLog.getContent(), AfterSaleStatusEnum.APPLY.getContent()); - } - - @Test - public void testGetAfterSalePage() { - // mock 数据 - AfterSaleDO dbAfterSale = randomPojo(AfterSaleDO.class, o -> { // 等会查询到 - o.setNo("202211190847450020500077"); - o.setStatus(AfterSaleStatusEnum.APPLY.getStatus()); - o.setWay(AfterSaleWayEnum.RETURN_AND_REFUND.getWay()); - o.setType(AfterSaleTypeEnum.IN_SALE.getType()); - o.setOrderNo("202211190847450020500011"); - o.setSpuName("芋艿"); - o.setCreateTime(buildTime(2022, 1, 15)); - }); - tradeAfterSaleMapper.insert(dbAfterSale); - // 测试 no 不匹配 - tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setNo("202211190847450020500066"))); - // 测试 status 不匹配 - tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setStatus(AfterSaleStatusEnum.SELLER_REFUSE.getStatus()))); - // 测试 way 不匹配 - tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setWay(AfterSaleWayEnum.REFUND.getWay()))); - // 测试 type 不匹配 - tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setType(AfterSaleTypeEnum.AFTER_SALE.getType()))); - // 测试 orderNo 不匹配 - tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setOrderNo("202211190847450020500022"))); - // 测试 spuName 不匹配 - tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setSpuName("土豆"))); - // 测试 createTime 不匹配 - tradeAfterSaleMapper.insert(cloneIgnoreId(dbAfterSale, o -> o.setCreateTime(buildTime(2022, 1, 20)))); - // 准备参数 - AfterSalePageReqVO reqVO = new AfterSalePageReqVO(); - reqVO.setNo("20221119084745002050007"); - reqVO.setStatus(AfterSaleStatusEnum.APPLY.getStatus()); - reqVO.setWay(AfterSaleWayEnum.RETURN_AND_REFUND.getWay()); - reqVO.setType(AfterSaleTypeEnum.IN_SALE.getType()); - reqVO.setOrderNo("20221119084745002050001"); - reqVO.setSpuName("芋"); - reqVO.setCreateTime(new LocalDateTime[]{buildTime(2022, 1, 1), buildTime(2022, 1, 16)}); - - // 调用 - PageResult pageResult = tradeAfterSaleService.getAfterSalePage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbAfterSale, pageResult.getList().get(0)); - } -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImplTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImplTest.java deleted file mode 100644 index 99a815e5e..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImplTest.java +++ /dev/null @@ -1,118 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.brokerage; - -import cn.hutool.core.util.NumberUtil; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.record.BrokerageRecordPageReqVO; -import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO; -import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageRecordMapper; -import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordServiceImpl; -import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService; -import cn.iocoder.yudao.module.trade.service.config.TradeConfigService; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; -import java.math.RoundingMode; - -import static cn.hutool.core.util.RandomUtil.randomEle; -import static cn.hutool.core.util.RandomUtil.randomInt; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomInteger; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static org.junit.jupiter.api.Assertions.assertEquals; - -// TODO @芋艿:单测后续看看 -/** - * {@link BrokerageRecordServiceImpl} 的单元测试类 - * - * @author owen - */ -@Import(BrokerageRecordServiceImpl.class) -public class BrokerageRecordServiceImplTest extends BaseDbUnitTest { - - @Resource - private BrokerageRecordServiceImpl brokerageRecordService; - @Resource - private BrokerageRecordMapper brokerageRecordMapper; - - @MockBean - private TradeConfigService tradeConfigService; - @MockBean - private BrokerageUserService brokerageUserService; - - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetBrokerageRecordPage() { - // mock 数据 - BrokerageRecordDO dbBrokerageRecord = randomPojo(BrokerageRecordDO.class, o -> { // 等会查询到 - o.setUserId(null); - o.setBizType(null); - o.setStatus(null); - o.setCreateTime(null); - }); - brokerageRecordMapper.insert(dbBrokerageRecord); - // 测试 userId 不匹配 - brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setUserId(null))); - // 测试 bizType 不匹配 - brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setBizType(null))); - // 测试 status 不匹配 - brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setStatus(null))); - // 测试 createTime 不匹配 - brokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setCreateTime(null))); - // 准备参数 - BrokerageRecordPageReqVO reqVO = new BrokerageRecordPageReqVO(); - reqVO.setUserId(null); - reqVO.setBizType(null); - reqVO.setStatus(null); - reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - - // 调用 - PageResult pageResult = brokerageRecordService.getBrokerageRecordPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbBrokerageRecord, pageResult.getList().get(0)); - } - - @Test - public void testCalculatePrice_useFixedPrice() { - // mock 数据 - Integer payPrice = randomInteger(); - Integer percent = randomInt(1, 101); - Integer fixedPrice = randomInt(); - // 调用 - int brokerage = brokerageRecordService.calculatePrice(payPrice, percent, fixedPrice); - // 断言 - assertEquals(brokerage, fixedPrice); - } - - @Test - public void testCalculatePrice_usePercent() { - // mock 数据 - Integer payPrice = randomInteger(); - Integer percent = randomInt(1, 101); - Integer fixedPrice = randomEle(new Integer[]{0, null}); - System.out.println("fixedPrice=" + fixedPrice); - // 调用 - int brokerage = brokerageRecordService.calculatePrice(payPrice, percent, fixedPrice); - // 断言 - assertEquals(brokerage, NumberUtil.div(NumberUtil.mul(payPrice, percent), 100, 0, RoundingMode.DOWN).intValue()); - } - - @Test - public void testCalculatePrice_equalsZero() { - // mock 数据 - Integer payPrice = null; - Integer percent = null; - Integer fixedPrice = null; - // 调用 - int brokerage = brokerageRecordService.calculatePrice(payPrice, percent, fixedPrice); - // 断言 - assertEquals(brokerage, 0); - } -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImplTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImplTest.java deleted file mode 100644 index 88157c204..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImplTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.brokerage; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user.BrokerageUserPageReqVO; -import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO; -import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageUserMapper; -import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserServiceImpl; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; - -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static org.junit.jupiter.api.Assertions.assertEquals; - -// TODO @芋艿:单测后续看看 -/** - * {@link BrokerageUserServiceImpl} 的单元测试类 - * - * @author owen - */ -@Import(BrokerageUserServiceImpl.class) -public class BrokerageUserServiceImplTest extends BaseDbUnitTest { - - @Resource - private BrokerageUserServiceImpl brokerageUserService; - - @Resource - private BrokerageUserMapper brokerageUserMapper; - - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetBrokerageUserPage() { - // mock 数据 - BrokerageUserDO dbBrokerageUser = randomPojo(BrokerageUserDO.class, o -> { // 等会查询到 - o.setBindUserId(null); - o.setBrokerageEnabled(null); - o.setCreateTime(null); - }); - brokerageUserMapper.insert(dbBrokerageUser); - // 测试 brokerageUserId 不匹配 - brokerageUserMapper.insert(cloneIgnoreId(dbBrokerageUser, o -> o.setBindUserId(null))); - // 测试 brokerageEnabled 不匹配 - brokerageUserMapper.insert(cloneIgnoreId(dbBrokerageUser, o -> o.setBrokerageEnabled(null))); - // 测试 createTime 不匹配 - brokerageUserMapper.insert(cloneIgnoreId(dbBrokerageUser, o -> o.setCreateTime(null))); - // 准备参数 - BrokerageUserPageReqVO reqVO = new BrokerageUserPageReqVO(); - reqVO.setBindUserId(null); - reqVO.setBrokerageEnabled(null); - reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - - // 调用 - PageResult pageResult = brokerageUserService.getBrokerageUserPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbBrokerageUser, pageResult.getList().get(0)); - } - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java deleted file mode 100644 index af0cdbb1a..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.brokerage; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; -import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO; -import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO; -import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageWithdrawMapper; -import cn.iocoder.yudao.module.trade.service.config.TradeConfigService; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; -import javax.validation.Validator; - -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static org.junit.jupiter.api.Assertions.assertEquals; - -// TODO 芋艿:后续 review -/** - * {@link BrokerageWithdrawServiceImpl} 的单元测试类 - * - * @author 芋道源码 - */ -@Import(BrokerageWithdrawServiceImpl.class) -public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest { - - @Resource - private BrokerageWithdrawServiceImpl brokerageWithdrawService; - - @Resource - private BrokerageWithdrawMapper brokerageWithdrawMapper; - - @MockBean - private BrokerageRecordService brokerageRecordService; - @MockBean - private BrokerageUserService brokerageUserService; - @MockBean - private TradeConfigService tradeConfigService; - - @MockBean - private NotifyMessageSendApi notifyMessageSendApi; - - @Resource - private Validator validator; - - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetBrokerageWithdrawPage() { - // mock 数据 - BrokerageWithdrawDO dbBrokerageWithdraw = randomPojo(BrokerageWithdrawDO.class, o -> { // 等会查询到 - o.setUserId(null); - o.setType(null); - o.setName(null); - o.setAccountNo(null); - o.setBankName(null); - o.setStatus(null); - o.setCreateTime(null); - }); - brokerageWithdrawMapper.insert(dbBrokerageWithdraw); - // 测试 userId 不匹配 - brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setUserId(null))); - // 测试 type 不匹配 - brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setType(null))); - // 测试 name 不匹配 - brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setName(null))); - // 测试 accountNo 不匹配 - brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setAccountNo(null))); - // 测试 bankName 不匹配 - brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setBankName(null))); - // 测试 status 不匹配 - brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setStatus(null))); - // 测试 auditReason 不匹配 - brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setAuditReason(null))); - // 测试 auditTime 不匹配 - brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setAuditTime(null))); - // 测试 remark 不匹配 - brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setRemark(null))); - // 测试 createTime 不匹配 - brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setCreateTime(null))); - // 准备参数 - BrokerageWithdrawPageReqVO reqVO = new BrokerageWithdrawPageReqVO(); - reqVO.setUserId(null); - reqVO.setType(null); - reqVO.setName(null); - reqVO.setAccountNo(null); - reqVO.setBankName(null); - reqVO.setStatus(null); - reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - - // 调用 - PageResult pageResult = brokerageWithdrawService.getBrokerageWithdrawPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbBrokerageWithdraw, pageResult.getList().get(0)); - } - - @Test - public void testCalculateFeePrice() { - Integer withdrawPrice = 100; - // 测试手续费比例未设置 - Integer percent = null; - assertEquals(brokerageWithdrawService.calculateFeePrice(withdrawPrice, percent), 0); - // 测试手续费给为0 - percent = 0; - assertEquals(brokerageWithdrawService.calculateFeePrice(withdrawPrice, percent), 0); - // 测试手续费 - percent = 1; - assertEquals(brokerageWithdrawService.calculateFeePrice(withdrawPrice, percent), 1); - } -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java deleted file mode 100644 index b0e901023..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java +++ /dev/null @@ -1,316 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.order; - -import cn.iocoder.yudao.framework.common.enums.TerminalEnum; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; -import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO; -import cn.iocoder.yudao.module.member.api.user.MemberUserApi; -import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; -import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; -import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; -import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; -import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; -import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; -import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; -import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; -import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; -import cn.iocoder.yudao.module.promotion.api.price.PriceApi; -import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO; -import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; -import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO; -import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; -import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; -import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; -import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; -import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderConfig; -import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; -import java.time.Duration; -import java.util.Arrays; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * {@link TradeOrderUpdateServiceImpl} 的单元测试类 - * - * @author LeeYan9 - * @since 2022-09-07 - */ -@Import({TradeOrderUpdateServiceImpl.class, TradeOrderConfig.class}) -public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { - - @Resource - private TradeOrderUpdateServiceImpl tradeOrderUpdateService; - - @Resource - private TradeOrderMapper tradeOrderMapper; - @Resource - private TradeOrderItemMapper tradeOrderItemMapper; - - @MockBean - private MemberUserApi memberUserApi; - @MockBean - private ProductSpuApi productSpuApi; - @MockBean - private ProductSkuApi productSkuApi; - @MockBean - private PriceApi priceApi; - @MockBean - private PayOrderApi payOrderApi; - @MockBean - private MemberAddressApi addressApi; - @MockBean - private CouponApi couponApi; - - @MockBean - private TradeOrderProperties tradeOrderProperties; - - @BeforeEach - public void setUp() { - when(tradeOrderProperties.getAppId()).thenReturn(888L); - when(tradeOrderProperties.getPayExpireTime()).thenReturn(Duration.ofDays(1)); - } - - @Test - public void testCreateTradeOrder_success() { - // 准备参数 - Long userId = 100L; - String userIp = "127.0.0.1"; -// AppTradeOrderCreateReqVO reqVO = new AppTradeOrderCreateReqVO() -// .setAddressId(10L).setCouponId(101L).setRemark("我是备注").setFromCart(true) -// .setItems(Arrays.asList(new AppTradeOrderCreateReqVO.Item().setSkuId(1L).setCount(3), -// new AppTradeOrderCreateReqVO.Item().setSkuId(2L).setCount(4))); - AppTradeOrderCreateReqVO reqVO = null; - // TODO 芋艿:重新高下 - // mock 方法(商品 SKU 检查) - ProductSkuRespDTO sku01 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(1L).setSpuId(11L) - .setPrice(50).setStock(100) - .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(111L).setValueId(222L)))); - ProductSkuRespDTO sku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(2L).setSpuId(21L) - .setPrice(20).setStock(50)) - .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(333L).setValueId(444L))); - when(productSkuApi.getSkuList(eq(asSet(1L, 2L)))).thenReturn(Arrays.asList(sku01, sku02)); - // mock 方法(商品 SPU 检查) - ProductSpuRespDTO spu01 = randomPojo(ProductSpuRespDTO.class, o -> o.setId(11L) - .setStatus(ProductSpuStatusEnum.ENABLE.getStatus()).setName("商品 1")); - ProductSpuRespDTO spu02 = randomPojo(ProductSpuRespDTO.class, o -> o.setId(21L) - .setStatus(ProductSpuStatusEnum.ENABLE.getStatus())); - when(productSpuApi.getSpuList(eq(asSet(11L, 21L)))).thenReturn(Arrays.asList(spu01, spu02)); - // mock 方法(用户收件地址的校验) - MemberAddressRespDTO addressRespDTO = new MemberAddressRespDTO().setId(10L).setUserId(userId).setName("芋艿") - .setMobile("15601691300").setAreaId(3306).setDetailAddress("土豆村"); - when(addressApi.getAddress(eq(10L), eq(userId))).thenReturn(addressRespDTO); - // mock 方法(价格计算) - PriceCalculateRespDTO.OrderItem priceOrderItem01 = new PriceCalculateRespDTO.OrderItem() - .setSpuId(11L).setSkuId(1L).setCount(3).setOriginalPrice(150).setOriginalUnitPrice(50) - .setDiscountPrice(20).setPayPrice(130).setOrderPartPrice(7).setOrderDividePrice(35); - PriceCalculateRespDTO.OrderItem priceOrderItem02 = new PriceCalculateRespDTO.OrderItem() - .setSpuId(21L).setSkuId(2L).setCount(4).setOriginalPrice(80).setOriginalUnitPrice(20) - .setDiscountPrice(40).setPayPrice(40).setOrderPartPrice(15).setOrderDividePrice(25); - PriceCalculateRespDTO.Order priceOrder = new PriceCalculateRespDTO.Order() - .setTotalPrice(230).setDiscountPrice(0).setCouponPrice(30) - .setPointPrice(10).setDeliveryPrice(20).setPayPrice(80).setCouponId(101L).setCouponPrice(30) - .setItems(Arrays.asList(priceOrderItem01, priceOrderItem02)); - when(priceApi.calculatePrice(argThat(priceCalculateReqDTO -> { - assertEquals(priceCalculateReqDTO.getUserId(), 100L); - assertEquals(priceCalculateReqDTO.getCouponId(), 101L); - assertEquals(priceCalculateReqDTO.getItems().get(0).getSkuId(), 1L); - assertEquals(priceCalculateReqDTO.getItems().get(0).getCount(), 3); - assertEquals(priceCalculateReqDTO.getItems().get(1).getSkuId(), 2L); - assertEquals(priceCalculateReqDTO.getItems().get(1).getCount(), 4); - return true; - }))).thenReturn(new PriceCalculateRespDTO().setOrder(priceOrder)); - // mock 方法(创建支付单) - when(payOrderApi.createOrder(argThat(createReqDTO -> { - assertEquals(createReqDTO.getAppId(), 888L); - assertEquals(createReqDTO.getUserIp(), userIp); - assertNotNull(createReqDTO.getMerchantOrderId()); // 由于 tradeOrderId 后生成,只能校验非空 - assertEquals(createReqDTO.getSubject(), "商品 1 等多件"); - assertNull(createReqDTO.getBody()); - assertEquals(createReqDTO.getPrice(), 80); - assertNotNull(createReqDTO.getExpireTime()); - return true; - }))).thenReturn(1000L); - - // 调用方法 - TradeOrderDO order = tradeOrderUpdateService.createOrder(userId, userIp, reqVO, null); - // 断言 TradeOrderDO 订单 - List tradeOrderDOs = tradeOrderMapper.selectList(); - assertEquals(tradeOrderDOs.size(), 1); - TradeOrderDO tradeOrderDO = tradeOrderDOs.get(0); - assertEquals(tradeOrderDO.getId(), order.getId()); - assertNotNull(tradeOrderDO.getNo()); - assertEquals(tradeOrderDO.getType(), TradeOrderTypeEnum.NORMAL.getType()); - assertEquals(tradeOrderDO.getTerminal(), TerminalEnum.H5.getTerminal()); - assertEquals(tradeOrderDO.getUserId(), userId); - assertEquals(tradeOrderDO.getUserIp(), userIp); - assertEquals(tradeOrderDO.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus()); - assertEquals(tradeOrderDO.getProductCount(), 7); - assertNull(tradeOrderDO.getFinishTime()); - assertNull(tradeOrderDO.getCancelTime()); - assertNull(tradeOrderDO.getCancelType()); - assertEquals(tradeOrderDO.getUserRemark(), "我是备注"); - assertNull(tradeOrderDO.getRemark()); - assertFalse(tradeOrderDO.getPayStatus()); - assertNull(tradeOrderDO.getPayTime()); - assertEquals(tradeOrderDO.getTotalPrice(), 230); - assertEquals(tradeOrderDO.getDiscountPrice(), 0); - assertEquals(tradeOrderDO.getAdjustPrice(), 0); - assertEquals(tradeOrderDO.getPayPrice(), 80); - assertEquals(tradeOrderDO.getPayOrderId(), 1000L); - assertNull(tradeOrderDO.getPayChannelCode()); - assertNull(tradeOrderDO.getLogisticsId()); - assertNull(tradeOrderDO.getDeliveryTime()); - assertNull(tradeOrderDO.getReceiveTime()); - assertEquals(tradeOrderDO.getReceiverName(), "芋艿"); - assertEquals(tradeOrderDO.getReceiverMobile(), "15601691300"); - assertEquals(tradeOrderDO.getReceiverAreaId(), 3306); - assertEquals(tradeOrderDO.getReceiverDetailAddress(), "土豆村"); - assertEquals(tradeOrderDO.getRefundStatus(), TradeOrderRefundStatusEnum.NONE.getStatus()); - assertEquals(tradeOrderDO.getRefundPrice(), 0); - assertEquals(tradeOrderDO.getCouponPrice(), 30); - assertEquals(tradeOrderDO.getPointPrice(), 10); - // 断言 TradeOrderItemDO 订单(第 1 个) - List tradeOrderItemDOs = tradeOrderItemMapper.selectList(); - assertEquals(tradeOrderItemDOs.size(), 2); - TradeOrderItemDO tradeOrderItemDO01 = tradeOrderItemDOs.get(0); - assertNotNull(tradeOrderItemDO01.getId()); - assertEquals(tradeOrderItemDO01.getUserId(), userId); - assertEquals(tradeOrderItemDO01.getOrderId(), order.getId()); - assertEquals(tradeOrderItemDO01.getSpuId(), 11L); - assertEquals(tradeOrderItemDO01.getSkuId(), 1L); - assertEquals(tradeOrderItemDO01.getProperties().size(), 1); - assertEquals(tradeOrderItemDO01.getProperties().get(0).getPropertyId(), 111L); - assertEquals(tradeOrderItemDO01.getProperties().get(0).getValueId(), 222L); - //assertEquals(tradeOrderItemDO01.getSpuName(), sku01.getSpuName()); TODO 找不到spuName - assertEquals(tradeOrderItemDO01.getPicUrl(), sku01.getPicUrl()); - assertEquals(tradeOrderItemDO01.getCount(), 3); -// assertEquals(tradeOrderItemDO01.getOriginalPrice(), 150); - assertEquals(tradeOrderItemDO01.getPrice(), 50); - assertEquals(tradeOrderItemDO01.getDiscountPrice(), 20); - assertEquals(tradeOrderItemDO01.getPayPrice(), 130); - assertEquals(tradeOrderItemDO01.getAfterSaleStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); - // 断言 TradeOrderItemDO 订单(第 2 个) - TradeOrderItemDO tradeOrderItemDO02 = tradeOrderItemDOs.get(1); - assertNotNull(tradeOrderItemDO02.getId()); - assertEquals(tradeOrderItemDO02.getUserId(), userId); - assertEquals(tradeOrderItemDO02.getOrderId(), order.getId()); - assertEquals(tradeOrderItemDO02.getSpuId(), 21L); - assertEquals(tradeOrderItemDO02.getSkuId(), 2L); - assertEquals(tradeOrderItemDO02.getProperties().size(), 1); - assertEquals(tradeOrderItemDO02.getProperties().get(0).getPropertyId(), 333L); - assertEquals(tradeOrderItemDO02.getProperties().get(0).getValueId(), 444L); - //assertEquals(tradeOrderItemDO02.getSpuName(), sku02.getSpuName()); TODO 找不到spuName - assertEquals(tradeOrderItemDO02.getPicUrl(), sku02.getPicUrl()); - assertEquals(tradeOrderItemDO02.getCount(), 4); -// assertEquals(tradeOrderItemDO02.getOriginalPrice(), 80); - assertEquals(tradeOrderItemDO02.getPrice(), 20); - assertEquals(tradeOrderItemDO02.getDiscountPrice(), 40); - assertEquals(tradeOrderItemDO02.getPayPrice(), 40); - assertEquals(tradeOrderItemDO02.getAfterSaleStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); - // 校验调用 - verify(productSkuApi).updateSkuStock(argThat(updateStockReqDTO -> { - assertEquals(updateStockReqDTO.getItems().size(), 2); - assertEquals(updateStockReqDTO.getItems().get(0).getId(), 1L); - assertEquals(updateStockReqDTO.getItems().get(0).getIncrCount(), 3); - assertEquals(updateStockReqDTO.getItems().get(1).getId(), 2L); - assertEquals(updateStockReqDTO.getItems().get(1).getIncrCount(), 4); - return true; - })); - verify(couponApi).useCoupon(argThat(reqDTO -> { - assertEquals(reqDTO.getId(), reqVO.getCouponId()); - assertEquals(reqDTO.getUserId(), userId); - assertEquals(reqDTO.getOrderId(), order.getId()); - return true; - })); - } - - @Test - public void testUpdateOrderPaid() { - // mock 数据(TradeOrder) - TradeOrderDO order = randomPojo(TradeOrderDO.class, o -> { - o.setId(1L).setStatus(TradeOrderStatusEnum.UNPAID.getStatus()); - o.setPayOrderId(10L).setPayStatus(false).setPayPrice(100).setPayTime(null); - }); - tradeOrderMapper.insert(order); - // 准备参数 - Long id = 1L; - Long payOrderId = 10L; - // mock 方法(支付单) - when(payOrderApi.getOrder(eq(10L))).thenReturn(randomPojo(PayOrderRespDTO.class, - o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()).setChannelCode("wx_pub") - .setMerchantOrderId("1")).setPrice(100)); - - // 调用 - tradeOrderUpdateService.updateOrderPaid(id, payOrderId); - // 断言 - TradeOrderDO dbOrder = tradeOrderMapper.selectById(id); - assertEquals(dbOrder.getStatus(), TradeOrderStatusEnum.UNDELIVERED.getStatus()); - assertTrue(dbOrder.getPayStatus()); - assertNotNull(dbOrder.getPayTime()); - assertEquals(dbOrder.getPayChannelCode(), "wx_pub"); - } - - @Test - public void testDeliveryOrder() { - // mock 数据(TradeOrder) - TradeOrderDO order = randomPojo(TradeOrderDO.class, o -> { - o.setId(1L).setStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus()); - o.setLogisticsId(null).setLogisticsNo(null).setDeliveryTime(null); - }); - tradeOrderMapper.insert(order); - // 准备参数 - TradeOrderDeliveryReqVO deliveryReqVO = new TradeOrderDeliveryReqVO().setId(1L) - .setLogisticsId(10L).setLogisticsNo("100"); - // mock 方法(支付单) - - // 调用 - tradeOrderUpdateService.deliveryOrder(deliveryReqVO); - // 断言 - TradeOrderDO dbOrder = tradeOrderMapper.selectById(1L); - assertEquals(dbOrder.getStatus(), TradeOrderStatusEnum.DELIVERED.getStatus()); - assertPojoEquals(dbOrder, deliveryReqVO); - assertNotNull(dbOrder.getDeliveryTime()); - } - - @Test - public void testReceiveOrder() { - // mock 数据(TradeOrder) - TradeOrderDO order = randomPojo(TradeOrderDO.class, o -> { - o.setId(1L).setUserId(10L).setStatus(TradeOrderStatusEnum.DELIVERED.getStatus()); - o.setReceiveTime(null); - }); - tradeOrderMapper.insert(order); - // 准备参数 - Long id = 1L; - Long userId = 10L; - // mock 方法(支付单) - - // 调用 - tradeOrderUpdateService.receiveOrderByMember(userId, id); - // 断言 - TradeOrderDO dbOrder = tradeOrderMapper.selectById(1L); - assertEquals(dbOrder.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus()); - assertNotNull(dbOrder.getReceiveTime()); - } - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java deleted file mode 100644 index b998f87b1..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java +++ /dev/null @@ -1,135 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.price; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; -import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; -import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; -import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; -import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; -import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; -import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; - -import java.util.Arrays; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.when; - -/** - * {@link TradePriceServiceImpl} 的单元测试 - * - * @author 芋道源码 - */ -public class TradePriceServiceImplTest extends BaseMockitoUnitTest { - - @InjectMocks - private TradePriceServiceImpl tradePriceService; - - @Mock - private ProductSkuApi productSkuApi; - @Mock - private ProductSpuApi productSpuApi; - @Mock - private List priceCalculators; - - @Test - public void testCalculatePrice() { - // 准备参数 - TradePriceCalculateReqBO calculateReqBO = new TradePriceCalculateReqBO() - .setUserId(10L) - .setCouponId(20L).setAddressId(30L) - .setItems(Arrays.asList( - new TradePriceCalculateReqBO.Item().setSkuId(100L).setCount(1).setSelected(true), - new TradePriceCalculateReqBO.Item().setSkuId(200L).setCount(3).setSelected(true), - new TradePriceCalculateReqBO.Item().setSkuId(300L).setCount(6).setCartId(233L).setSelected(false) - )); - // mock 方法 - List skuList = Arrays.asList( - new ProductSkuRespDTO().setId(100L).setStock(500).setPrice(1000).setPicUrl("https://t.cn/1.png").setSpuId(1001L) - .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色") - .setValueId(2L).setValueName("红色"))), - new ProductSkuRespDTO().setId(200L).setStock(400).setPrice(2000).setPicUrl("https://t.cn/2.png").setSpuId(1001L) - .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色") - .setValueId(3L).setValueName("黄色"))), - new ProductSkuRespDTO().setId(300L).setStock(600).setPrice(3000).setPicUrl("https://t.cn/3.png").setSpuId(1001L) - .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色") - .setValueId(4L).setValueName("黑色"))) - ); - when(productSkuApi.getSkuList(Mockito.eq(asSet(100L, 200L, 300L)))).thenReturn(skuList); - when(productSpuApi.getSpuList(Mockito.eq(asSet(1001L)))) - .thenReturn(singletonList(new ProductSpuRespDTO().setId(1001L).setName("小菜").setCategoryId(666L) - .setStatus(ProductSpuStatusEnum.ENABLE.getStatus()))); - - // 调用 - TradePriceCalculateRespBO calculateRespBO = tradePriceService.calculatePrice(calculateReqBO); - // 断言 - assertEquals(TradeOrderTypeEnum.NORMAL.getType(), calculateRespBO.getType()); - assertEquals(0, calculateRespBO.getPromotions().size()); - assertNull(calculateRespBO.getCouponId()); - // 断言:订单价格 - assertEquals(7000, calculateRespBO.getPrice().getTotalPrice()); - assertEquals(0, calculateRespBO.getPrice().getDiscountPrice()); - assertEquals(0, calculateRespBO.getPrice().getDeliveryPrice()); - assertEquals(0, calculateRespBO.getPrice().getCouponPrice()); - assertEquals(0, calculateRespBO.getPrice().getPointPrice()); - assertEquals(7000, calculateRespBO.getPrice().getPayPrice()); - // 断言:SKU 1 - assertEquals(1001L, calculateRespBO.getItems().get(0).getSpuId()); - assertEquals(100L, calculateRespBO.getItems().get(0).getSkuId()); - assertEquals(1, calculateRespBO.getItems().get(0).getCount()); - assertNull(calculateRespBO.getItems().get(0).getCartId()); - assertTrue(calculateRespBO.getItems().get(0).getSelected()); - assertEquals(1000, calculateRespBO.getItems().get(0).getPrice()); - assertEquals(0, calculateRespBO.getItems().get(0).getDiscountPrice()); - assertEquals(0, calculateRespBO.getItems().get(0).getDeliveryPrice()); - assertEquals(0, calculateRespBO.getItems().get(0).getCouponPrice()); - assertEquals(0, calculateRespBO.getItems().get(0).getPointPrice()); - assertEquals(1000, calculateRespBO.getItems().get(0).getPayPrice()); - assertEquals("小菜", calculateRespBO.getItems().get(0).getSpuName()); - assertEquals("https://t.cn/1.png", calculateRespBO.getItems().get(0).getPicUrl()); - assertEquals(666L, calculateRespBO.getItems().get(0).getCategoryId()); - assertEquals(skuList.get(0).getProperties(), calculateRespBO.getItems().get(0).getProperties()); - // 断言:SKU 2 - assertEquals(1001L, calculateRespBO.getItems().get(1).getSpuId()); - assertEquals(200L, calculateRespBO.getItems().get(1).getSkuId()); - assertEquals(3, calculateRespBO.getItems().get(1).getCount()); - assertNull(calculateRespBO.getItems().get(1).getCartId()); - assertTrue(calculateRespBO.getItems().get(1).getSelected()); - assertEquals(2000, calculateRespBO.getItems().get(1).getPrice()); - assertEquals(0, calculateRespBO.getItems().get(1).getDiscountPrice()); - assertEquals(0, calculateRespBO.getItems().get(1).getDeliveryPrice()); - assertEquals(0, calculateRespBO.getItems().get(1).getCouponPrice()); - assertEquals(0, calculateRespBO.getItems().get(1).getPointPrice()); - assertEquals(6000, calculateRespBO.getItems().get(1).getPayPrice()); - assertEquals("小菜", calculateRespBO.getItems().get(1).getSpuName()); - assertEquals("https://t.cn/2.png", calculateRespBO.getItems().get(1).getPicUrl()); - assertEquals(666L, calculateRespBO.getItems().get(1).getCategoryId()); - assertEquals(skuList.get(1).getProperties(), calculateRespBO.getItems().get(1).getProperties()); - // 断言:SKU 3 - assertEquals(1001L, calculateRespBO.getItems().get(2).getSpuId()); - assertEquals(300L, calculateRespBO.getItems().get(2).getSkuId()); - assertEquals(6, calculateRespBO.getItems().get(2).getCount()); - assertEquals(233L, calculateRespBO.getItems().get(2).getCartId()); - assertFalse(calculateRespBO.getItems().get(2).getSelected()); - assertEquals(3000, calculateRespBO.getItems().get(2).getPrice()); - assertEquals(0, calculateRespBO.getItems().get(2).getDiscountPrice()); - assertEquals(0, calculateRespBO.getItems().get(2).getDeliveryPrice()); - assertEquals(0, calculateRespBO.getItems().get(2).getCouponPrice()); - assertEquals(0, calculateRespBO.getItems().get(2).getPointPrice()); - assertEquals(18000, calculateRespBO.getItems().get(2).getPayPrice()); - assertEquals("小菜", calculateRespBO.getItems().get(2).getSpuName()); - assertEquals("https://t.cn/3.png", calculateRespBO.getItems().get(2).getPicUrl()); - assertEquals(666L, calculateRespBO.getItems().get(2).getCategoryId()); - assertEquals(skuList.get(2).getProperties(), calculateRespBO.getItems().get(2).getProperties()); - } - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java deleted file mode 100644 index 06655e0b2..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java +++ /dev/null @@ -1,146 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.price.calculator; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; -import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO; -import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.ArrayList; - -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -/** - * {@link TradeCouponPriceCalculator} 的单元测试类 - * - * @author 芋道源码 - */ -public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest { - - @InjectMocks - private TradeCouponPriceCalculator tradeCouponPriceCalculator; - - @Mock - private CouponApi couponApi; - - @Test - public void testCalculate() { - // 准备参数 - TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() - .setUserId(233L).setCouponId(1024L) - .setItems(asList( - new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配优惠劵 - new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 匹配优惠劵 - new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true), // 不匹配优惠劵 - new TradePriceCalculateReqBO.Item().setSkuId(40L).setCount(5).setSelected(false) // 匹配优惠劵,但是未选中 - )); - TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() - .setType(TradeOrderTypeEnum.NORMAL.getType()) - .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()) - .setItems(asList( - new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) - .setPrice(100).setSpuId(1L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) - .setPrice(50).setSpuId(2L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(4).setSelected(true) - .setPrice(30).setSpuId(3L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(40L).setCount(5).setSelected(false) - .setPrice(60).setSpuId(1L) - )); - // 保证价格被初始化上 - TradePriceCalculatorHelper.recountPayPrice(result.getItems()); - TradePriceCalculatorHelper.recountAllPrice(result); - - // mock 方法(优惠劵 Coupon 信息) - CouponRespDTO coupon = randomPojo(CouponRespDTO.class, o -> o.setId(1024L).setName("程序员节") - .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L)) - .setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()) - .setDiscountPercent(50).setDiscountLimitPrice(70)); - when(couponApi.validateCoupon(eq(new CouponValidReqDTO().setId(1024L).setUserId(233L)))).thenReturn(coupon); - - // 调用 - tradeCouponPriceCalculator.calculate(param, result); - // 断言 - assertEquals(result.getCouponId(), 1024L); - // 断言:Price 部分 - TradePriceCalculateRespBO.Price price = result.getPrice(); - assertEquals(price.getTotalPrice(), 470); - assertEquals(price.getDiscountPrice(), 0); - assertEquals(price.getPointPrice(), 0); - assertEquals(price.getDeliveryPrice(), 0); - assertEquals(price.getCouponPrice(), 70); - assertEquals(price.getPayPrice(), 400); - // 断言:SKU 1 - TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); - assertEquals(orderItem01.getSkuId(), 10L); - assertEquals(orderItem01.getCount(), 2); - assertEquals(orderItem01.getPrice(), 100); - assertEquals(orderItem01.getDiscountPrice(), 0); - assertEquals(orderItem01.getDeliveryPrice(), 0); - assertEquals(orderItem01.getCouponPrice(), 40); - assertEquals(orderItem01.getPointPrice(), 0); - assertEquals(orderItem01.getPayPrice(), 160); - // 断言:SKU 2 - TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); - assertEquals(orderItem02.getSkuId(), 20L); - assertEquals(orderItem02.getCount(), 3); - assertEquals(orderItem02.getPrice(), 50); - assertEquals(orderItem02.getDiscountPrice(), 0); - assertEquals(orderItem02.getDeliveryPrice(), 0); - assertEquals(orderItem02.getCouponPrice(), 30); - assertEquals(orderItem02.getPointPrice(), 0); - assertEquals(orderItem02.getPayPrice(), 120); - // 断言:SKU 3 - TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2); - assertEquals(orderItem03.getSkuId(), 30L); - assertEquals(orderItem03.getCount(), 4); - assertEquals(orderItem03.getPrice(), 30); - assertEquals(orderItem03.getDiscountPrice(), 0); - assertEquals(orderItem03.getCouponPrice(), 0); - assertEquals(orderItem03.getPointPrice(), 0); - assertEquals(orderItem03.getPayPrice(), 120); - // 断言:SKU 4 - TradePriceCalculateRespBO.OrderItem orderItem04 = result.getItems().get(3); - assertEquals(orderItem04.getSkuId(), 40L); - assertEquals(orderItem04.getCount(), 5); - assertEquals(orderItem04.getPrice(), 60); - assertEquals(orderItem04.getDiscountPrice(), 0); - assertEquals(orderItem04.getCouponPrice(), 0); - assertEquals(orderItem04.getPointPrice(), 0); - assertEquals(orderItem04.getPayPrice(), 300); - // 断言:Promotion 部分 - assertEquals(result.getPromotions().size(), 1); - TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); - assertEquals(promotion01.getId(), 1024L); - assertEquals(promotion01.getName(), "程序员节"); - assertEquals(promotion01.getType(), PromotionTypeEnum.COUPON.getType()); - assertEquals(promotion01.getTotalPrice(), 350); - assertEquals(promotion01.getDiscountPrice(), 70); - assertTrue(promotion01.getMatch()); - assertEquals(promotion01.getDescription(), "优惠劵:省 0.70 元"); - assertEquals(promotion01.getItems().size(), 2); - TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0); - assertEquals(promotionItem011.getSkuId(), 10L); - assertEquals(promotionItem011.getTotalPrice(), 200); - assertEquals(promotionItem011.getDiscountPrice(), 40); - TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1); - assertEquals(promotionItem012.getSkuId(), 20L); - assertEquals(promotionItem012.getTotalPrice(), 150); - assertEquals(promotionItem012.getDiscountPrice(), 30); - } - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java deleted file mode 100644 index 9441e473f..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java +++ /dev/null @@ -1,193 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.price.calculator; - -import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; -import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO; -import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO; -import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum; -import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; -import cn.iocoder.yudao.module.trade.service.config.TradeConfigService; -import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService; -import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateRespBO; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.ArrayList; - -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -/** - * {@link TradeDeliveryPriceCalculator} 的单元测试 - * - * @author jason - */ -public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { - - @InjectMocks - private TradeDeliveryPriceCalculator calculator; - - @Mock - private MemberAddressApi addressApi; - - @Mock - private DeliveryExpressTemplateService deliveryExpressTemplateService; - @Mock - private TradeConfigService tradeConfigService; - - private TradePriceCalculateReqBO reqBO; - private TradePriceCalculateRespBO resultBO; - - private DeliveryExpressTemplateRespBO templateRespBO; - private DeliveryExpressTemplateRespBO.Charge chargeBO; - private DeliveryExpressTemplateRespBO.Free freeBO; - - @BeforeEach - public void init(){ - // 准备参数 - reqBO = new TradePriceCalculateReqBO() - .setDeliveryType(DeliveryTypeEnum.EXPRESS.getType()) - .setAddressId(10L) - .setUserId(1L) - .setItems(asList( - new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), - new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(10).setSelected(true), - new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(false) // 未选中 - )); - resultBO = new TradePriceCalculateRespBO() - .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()) - .setItems(asList( - new TradePriceCalculateRespBO.OrderItem().setDeliveryTemplateId(1L).setSkuId(10L).setCount(2).setSelected(true) - .setWeight(10d).setVolume(10d).setPrice(100), - new TradePriceCalculateRespBO.OrderItem().setDeliveryTemplateId(1L).setSkuId(20L).setCount(10).setSelected(true) - .setWeight(10d).setVolume(10d).setPrice(200), - new TradePriceCalculateRespBO.OrderItem().setDeliveryTemplateId(1L).setSkuId(30L).setCount(1).setSelected(false) - .setWeight(10d).setVolume(10d).setPrice(300) - )); - // 保证价格被初始化上 - TradePriceCalculatorHelper.recountPayPrice(resultBO.getItems()); - TradePriceCalculatorHelper.recountAllPrice(resultBO); - - // 准备收件地址数据 - MemberAddressRespDTO addressResp = randomPojo(MemberAddressRespDTO.class, item -> item.setAreaId(10)); - when(addressApi.getAddress(eq(10L), eq(1L))).thenReturn(addressResp); - - // 准备运费模板费用配置数据 - chargeBO = randomPojo(DeliveryExpressTemplateRespBO.Charge.class, - item -> item.setStartCount(10D).setStartPrice(1000).setExtraCount(10D).setExtraPrice(2000)); - // 准备运费模板包邮配置数据:订单总件数 < 包邮件数时 12 < 20 - freeBO = randomPojo(DeliveryExpressTemplateRespBO.Free.class, - item -> item.setFreeCount(20).setFreePrice(100)); - // 准备 SP 运费模板数据 - templateRespBO = randomPojo(DeliveryExpressTemplateRespBO.class, - item -> item.setChargeMode(DeliveryExpressChargeModeEnum.COUNT.getType()) - .setCharge(chargeBO).setFree(freeBO)); - } - - @Test - @DisplayName("全场包邮") - public void testCalculate_expressGlobalFree() { - // mock 方法(全场包邮) - when(tradeConfigService.getTradeConfig()).thenReturn(new TradeConfigDO().setDeliveryExpressFreeEnabled(true) - .setDeliveryExpressFreePrice(2200)); - - // 调用 - calculator.calculate(reqBO, resultBO); - TradePriceCalculateRespBO.Price price = resultBO.getPrice(); - assertThat(price) - .extracting("totalPrice","discountPrice","couponPrice","pointPrice","deliveryPrice","payPrice") - .containsExactly(2200, 0, 0, 0, 0, 2200); - assertThat(resultBO.getItems()).hasSize(3); - // 断言:SKU1 - assertThat(resultBO.getItems().get(0)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") - .containsExactly(100, 2, 0, 0, 0, 0, 200); - // 断言:SKU2 - assertThat(resultBO.getItems().get(1)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") - .containsExactly(200, 10, 0, 0, 0, 0, 2000); - // 断言:SKU3 未选中 - assertThat(resultBO.getItems().get(2)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") - .containsExactly(300, 1, 0, 0, 0, 0, 300); - } - - @Test - @DisplayName("按件计算运费不包邮的情况") - public void testCalculate_expressTemplateCharge() { - // SKU 1 : 100 * 2 = 200 - // SKU 2 :200 * 10 = 2000 - // 运费 首件 1000 + 续件 2000 = 3000 - // mock 方法 - when(deliveryExpressTemplateService.getExpressTemplateMapByIdsAndArea(eq(asSet(1L)), eq(10))) - .thenReturn(MapUtil.of(1L, templateRespBO)); - - // 调用 - calculator.calculate(reqBO, resultBO); - // 断言 - TradePriceCalculateRespBO.Price price = resultBO.getPrice(); - assertThat(price) - .extracting("totalPrice","discountPrice","couponPrice","pointPrice","deliveryPrice","payPrice") - .containsExactly(2200, 0, 0, 0, 3000, 5200); - assertThat(resultBO.getItems()).hasSize(3); - // 断言:SKU1 - assertThat(resultBO.getItems().get(0)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") - .containsExactly(100, 2, 0, 0, 0, 500, 700); - // 断言:SKU2 - assertThat(resultBO.getItems().get(1)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") - .containsExactly(200, 10, 0, 0, 0, 2500, 4500); - // 断言:SKU3 未选中 - assertThat(resultBO.getItems().get(2)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") - .containsExactly(300, 1, 0, 0, 0, 0, 300); - } - - @Test - @DisplayName("按件计算运费包邮的情况") - public void testCalculate_expressTemplateFree() { - // SKU 1 : 100 * 2 = 200 - // SKU 2 :200 * 10 = 2000 - // 运费 0 - // mock 方法 - // 准备运费模板包邮配置数据 包邮 订单总件数 > 包邮件数时 12 > 10 - templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class, - item -> item.setFreeCount(10).setFreePrice(1000))); - when(deliveryExpressTemplateService.getExpressTemplateMapByIdsAndArea(eq(asSet(1L)), eq(10))) - .thenReturn(MapUtil.of(1L, templateRespBO)); - - // 调用 - calculator.calculate(reqBO, resultBO); - // 断言 - TradePriceCalculateRespBO.Price price = resultBO.getPrice(); - assertThat(price) - .extracting("totalPrice","discountPrice","couponPrice","pointPrice","deliveryPrice","payPrice") - .containsExactly(2200, 0, 0, 0, 0, 2200); - assertThat(resultBO.getItems()).hasSize(3); - // 断言:SKU1 - assertThat(resultBO.getItems().get(0)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") - .containsExactly(100, 2, 0, 0, 0, 0, 200); - // 断言:SKU2 - assertThat(resultBO.getItems().get(1)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") - .containsExactly(200, 10, 0, 0, 0, 0, 2000); - // 断言:SKU3 未选中 - assertThat(resultBO.getItems().get(2)) - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") - .containsExactly(300, 1, 0, 0, 0, 0, 300); - } - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculatorTest.java deleted file mode 100644 index 21760217c..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculatorTest.java +++ /dev/null @@ -1,120 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.price.calculator; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi; -import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.ArrayList; - -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -/** - * {@link TradeDiscountActivityPriceCalculator} 的单元测试类 - * - * @author 芋道源码 - */ -public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTest { - - @InjectMocks - private TradeDiscountActivityPriceCalculator tradeDiscountActivityPriceCalculator; - - @Mock - private DiscountActivityApi discountActivityApi; - - @Test - public void testCalculate() { - // 准备参数 - TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() - .setItems(asList( - new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配活动,且已选中 - new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(false) // 匹配活动,但未选中 - )); - TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() - .setType(TradeOrderTypeEnum.NORMAL.getType()) - .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()) - .setItems(asList( - new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) - .setPrice(100), - new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(false) - .setPrice(50) - )); - // 保证价格被初始化上 - TradePriceCalculatorHelper.recountPayPrice(result.getItems()); - TradePriceCalculatorHelper.recountAllPrice(result); - - // mock 方法(限时折扣活动) - when(discountActivityApi.getMatchDiscountProductList(eq(asSet(10L, 20L)))).thenReturn(asList( - randomPojo(DiscountProductRespDTO.class, o -> o.setActivityId(1000L) - .setActivityName("活动 1000 号").setSkuId(10L) - .setDiscountType(PromotionDiscountTypeEnum.PRICE.getType()).setDiscountPrice(40)), - randomPojo(DiscountProductRespDTO.class, o -> o.setActivityId(2000L) - .setActivityName("活动 2000 号").setSkuId(20L) - .setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()).setDiscountPercent(60)) - )); - // 10L: 100 * 2 - 40 * 2 = 120 - // 20L:50 * 3 - 50 * 3 * 0.4 = 90 - - // 调用 - tradeDiscountActivityPriceCalculator.calculate(param, result); - // 断言:Price 部分 - TradePriceCalculateRespBO.Price price = result.getPrice(); - assertEquals(price.getTotalPrice(), 200); - assertEquals(price.getDiscountPrice(), 80); - assertEquals(price.getPointPrice(), 0); - assertEquals(price.getDeliveryPrice(), 0); - assertEquals(price.getCouponPrice(), 0); - assertEquals(price.getPayPrice(), 120); - assertNull(result.getCouponId()); - // 断言:SKU 1 - assertEquals(result.getItems().size(), 2); - TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); - assertEquals(orderItem01.getSkuId(), 10L); - assertEquals(orderItem01.getCount(), 2); - assertEquals(orderItem01.getPrice(), 100); - assertEquals(orderItem01.getDiscountPrice(), 80); - assertEquals(orderItem01.getDeliveryPrice(), 0); - assertEquals(orderItem01.getCouponPrice(), 0); - assertEquals(orderItem01.getPointPrice(), 0); - assertEquals(orderItem01.getPayPrice(), 120); - // 断言:SKU 2 - TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); - assertEquals(orderItem02.getSkuId(), 20L); - assertEquals(orderItem02.getCount(), 3); - assertEquals(orderItem02.getPrice(), 50); - assertEquals(orderItem02.getDiscountPrice(), 60); - assertEquals(orderItem02.getDeliveryPrice(), 0); - assertEquals(orderItem02.getCouponPrice(), 0); - assertEquals(orderItem02.getPointPrice(), 0); - assertEquals(orderItem02.getPayPrice(), 90); - // 断言:Promotion 部分 - assertEquals(result.getPromotions().size(), 1); - TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); - assertEquals(promotion01.getId(), 1000L); - assertEquals(promotion01.getName(), "活动 1000 号"); - assertEquals(promotion01.getType(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType()); - assertEquals(promotion01.getTotalPrice(), 200); - assertEquals(promotion01.getDiscountPrice(), 80); - assertTrue(promotion01.getMatch()); - assertEquals(promotion01.getDescription(), "限时折扣:省 0.80 元"); - TradePriceCalculateRespBO.PromotionItem promotionItem01 = promotion01.getItems().get(0); - assertEquals(promotion01.getItems().size(), 1); - assertEquals(promotionItem01.getSkuId(), 10L); - assertEquals(promotionItem01.getTotalPrice(), 200); - assertEquals(promotionItem01.getDiscountPrice(), 80); - } - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeMemberLevelPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeMemberLevelPriceCalculatorTest.java deleted file mode 100644 index 44e783103..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeMemberLevelPriceCalculatorTest.java +++ /dev/null @@ -1,118 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.price.calculator; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.member.api.level.MemberLevelApi; -import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO; -import cn.iocoder.yudao.module.member.api.user.MemberUserApi; -import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.ArrayList; - -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -/** - * {@link TradeMemberLevelPriceCalculator} 的单元测试类 - * - * @author 芋道源码 - */ -public class TradeMemberLevelPriceCalculatorTest extends BaseMockitoUnitTest { - - @InjectMocks - private TradeMemberLevelPriceCalculator memberLevelPriceCalculator; - - @Mock - private MemberLevelApi memberLevelApi; - @Mock - private MemberUserApi memberUserApi; - - @Test - public void testCalculate() { - // 准备参数 - TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() - .setUserId(1024L) - .setItems(asList( - new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配活动,且已选中 - new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(false) // 匹配活动,但未选中 - )); - TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() - .setType(TradeOrderTypeEnum.NORMAL.getType()) - .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()) - .setItems(asList( - new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) - .setPrice(100), - new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(false) - .setPrice(50) - )); - // 保证价格被初始化上 - TradePriceCalculatorHelper.recountPayPrice(result.getItems()); - TradePriceCalculatorHelper.recountAllPrice(result); - - // mock 方法(会员等级) - when(memberUserApi.getUser(eq(1024L))).thenReturn(new MemberUserRespDTO().setLevelId(2048L)); - when(memberLevelApi.getMemberLevel(eq(2048L))).thenReturn( - new MemberLevelRespDTO().setId(2048L).setName("VIP 会员").setDiscountPercent(60)); - - // 调用 - memberLevelPriceCalculator.calculate(param, result); - // 断言:Price 部分 - TradePriceCalculateRespBO.Price price = result.getPrice(); - assertEquals(price.getTotalPrice(), 200); - assertEquals(price.getDiscountPrice(), 0); - assertEquals(price.getPointPrice(), 0); - assertEquals(price.getDeliveryPrice(), 0); - assertEquals(price.getCouponPrice(), 0); - assertEquals(price.getVipPrice(), 80); - assertEquals(price.getPayPrice(), 120); - assertNull(result.getCouponId()); - // 断言:SKU 1 - assertEquals(result.getItems().size(), 2); - TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); - assertEquals(orderItem01.getSkuId(), 10L); - assertEquals(orderItem01.getCount(), 2); - assertEquals(orderItem01.getPrice(), 100); - assertEquals(orderItem01.getDiscountPrice(), 0); - assertEquals(orderItem01.getDeliveryPrice(), 0); - assertEquals(orderItem01.getCouponPrice(), 0); - assertEquals(orderItem01.getPointPrice(), 0); - assertEquals(orderItem01.getVipPrice(), 80); - assertEquals(orderItem01.getPayPrice(), 120); - // 断言:SKU 2 - TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); - assertEquals(orderItem02.getSkuId(), 20L); - assertEquals(orderItem02.getCount(), 3); - assertEquals(orderItem02.getPrice(), 50); - assertEquals(orderItem02.getDiscountPrice(), 0); - assertEquals(orderItem02.getDeliveryPrice(), 0); - assertEquals(orderItem02.getCouponPrice(), 0); - assertEquals(orderItem02.getPointPrice(), 0); - assertEquals(orderItem02.getVipPrice(), 60); - assertEquals(orderItem02.getPayPrice(), 90); - // 断言:Promotion 部分 - assertEquals(result.getPromotions().size(), 1); - TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); - assertEquals(promotion01.getId(), 2048L); - assertEquals(promotion01.getName(), "VIP 会员"); - assertEquals(promotion01.getType(), PromotionTypeEnum.MEMBER_LEVEL.getType()); - assertEquals(promotion01.getTotalPrice(), 200); - assertEquals(promotion01.getDiscountPrice(), 80); - assertTrue(promotion01.getMatch()); - assertEquals(promotion01.getDescription(), "会员等级折扣:省 0.80 元"); - TradePriceCalculateRespBO.PromotionItem promotionItem01 = promotion01.getItems().get(0); - assertEquals(promotion01.getItems().size(), 1); - assertEquals(promotionItem01.getSkuId(), 10L); - assertEquals(promotionItem01.getTotalPrice(), 200); - assertEquals(promotionItem01.getDiscountPrice(), 80); - } - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculatorTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculatorTest.java deleted file mode 100644 index 910639ec1..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculatorTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.price.calculator; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.member.api.config.MemberConfigApi; -import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.ArrayList; - -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; - -// TODO 芋艿:晚点 review -/** - * {@link TradePointGiveCalculator} 的单元测试类 - * - * @author owen - */ -public class TradePointGiveCalculatorTest extends BaseMockitoUnitTest { - - @InjectMocks - private TradePointGiveCalculator tradePointGiveCalculator; - - @Mock - private MemberConfigApi memberConfigApi; - - @Test - public void testCalculate() { - - // 准备参数 - TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() - .setUserId(233L) - .setItems(asList( - new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 全局积分 - new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 全局积分 + SKU 积分 - new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(false), // 全局积分,但是未选中 - new TradePriceCalculateReqBO.Item().setSkuId(40L).setCount(5).setSelected(false) // 全局积分 + SKU 积分,但是未选中 - )); - TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() - .setType(TradeOrderTypeEnum.NORMAL.getType()) - .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()) - .setItems(asList( - new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) - .setPrice(100).setSpuId(1L).setGivePoint(0), - new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) - .setPrice(50).setSpuId(2L).setGivePoint(100), - new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(4).setSelected(false) - .setPrice(30).setSpuId(3L).setGivePoint(0), - new TradePriceCalculateRespBO.OrderItem().setSkuId(40L).setCount(5).setSelected(false) - .setPrice(60).setSpuId(1L).setGivePoint(100) - )); - // 保证价格被初始化上 - TradePriceCalculatorHelper.recountPayPrice(result.getItems()); - TradePriceCalculatorHelper.recountAllPrice(result); - - // mock 方法(积分配置 信息) - MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class, - o -> o.setPointTradeDeductEnable(true) // 启用积分折扣 - .setPointTradeGivePoint(100)); // 1 元赠送多少分 - when(memberConfigApi.getConfig()).thenReturn(memberConfig); - - // 调用 - tradePointGiveCalculator.calculate(param, result); - // 断言:Price 部分 - assertEquals(result.getGivePoint(), 2 * 100 + 3 * 50 + 100); - // 断言:SKU 1 - TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); - assertEquals(orderItem01.getSkuId(), 10L); - assertEquals(orderItem01.getCount(), 2); - assertEquals(orderItem01.getPrice(), 100); - assertEquals(orderItem01.getGivePoint(), 2 * 100); // 全局积分 - // 断言:SKU 2 - TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); - assertEquals(orderItem02.getSkuId(), 20L); - assertEquals(orderItem02.getCount(), 3); - assertEquals(orderItem02.getPrice(), 50); - assertEquals(orderItem02.getGivePoint(), 3 * 50 + 100); // 全局积分 + SKU 积分 - // 断言:SKU 3 - TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2); - assertEquals(orderItem03.getSkuId(), 30L); - assertEquals(orderItem03.getCount(), 4); - assertEquals(orderItem03.getPrice(), 30); - assertEquals(orderItem03.getGivePoint(), 0); // 全局积分,但是未选中 - // 断言:SKU 4 - TradePriceCalculateRespBO.OrderItem orderItem04 = result.getItems().get(3); - assertEquals(orderItem04.getSkuId(), 40L); - assertEquals(orderItem04.getCount(), 5); - assertEquals(orderItem04.getPrice(), 60); - assertEquals(orderItem04.getGivePoint(), 100); // 全局积分 + SKU 积分,但是未选中 - } -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculatorTest.java deleted file mode 100644 index fe679b408..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculatorTest.java +++ /dev/null @@ -1,333 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.price.calculator; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.member.api.config.MemberConfigApi; -import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO; -import cn.iocoder.yudao.module.member.api.user.MemberUserApi; -import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.ArrayList; - -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.when; - -// TODO 芋艿:晚点 review -/** - * {@link TradePointUsePriceCalculator } 的单元测试类 - * - * @author owen - */ -public class TradePointUsePriceCalculatorTest extends BaseMockitoUnitTest { - - @InjectMocks - private TradePointUsePriceCalculator tradePointUsePriceCalculator; - - @Mock - private MemberConfigApi memberConfigApi; - @Mock - private MemberUserApi memberUserApi; - - @Test - public void testCalculate_success() { - // 准备参数 - TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() - .setUserId(233L).setPointStatus(true) // 是否使用积分 - .setItems(asList( - new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分 - new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分 - new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中,不使用积分 - )); - TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() - .setType(TradeOrderTypeEnum.NORMAL.getType()) - .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()) - .setItems(asList( - new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) - .setPrice(100).setSpuId(1L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) - .setPrice(50).setSpuId(2L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false) - .setPrice(30).setSpuId(3L) - )); - // 保证价格被初始化上 - TradePriceCalculatorHelper.recountPayPrice(result.getItems()); - TradePriceCalculatorHelper.recountAllPrice(result); - - // mock 方法(积分配置 信息) - MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class, - o -> o.setPointTradeDeductEnable(true) // 启用积分折扣 - .setPointTradeDeductUnitPrice(1) // 1 积分抵扣多少金额(单位分) - .setPointTradeDeductMaxPrice(100)); // 积分抵扣最大值 - when(memberConfigApi.getConfig()).thenReturn(memberConfig); - // mock 方法(会员 信息) - MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(100)); - when(memberUserApi.getUser(user.getId())).thenReturn(user); - - // 调用 - tradePointUsePriceCalculator.calculate(param, result); - // 断言:使用了多少积分 - assertEquals(result.getUsePoint(), 100); - // 断言:Price 部分 - TradePriceCalculateRespBO.Price price = result.getPrice(); - assertEquals(price.getTotalPrice(), 350); - assertEquals(price.getPayPrice(), 250); - assertEquals(price.getPointPrice(), 100); - // 断言:SKU 1 - TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); - assertEquals(orderItem01.getSkuId(), 10L); - assertEquals(orderItem01.getCount(), 2); - assertEquals(orderItem01.getPrice(), 100); - assertEquals(orderItem01.getPointPrice(), 57); - assertEquals(orderItem01.getPayPrice(), 143); - // 断言:SKU 2 - TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); - assertEquals(orderItem02.getSkuId(), 20L); - assertEquals(orderItem02.getCount(), 3); - assertEquals(orderItem02.getPrice(), 50); - assertEquals(orderItem02.getPointPrice(), 43); - assertEquals(orderItem02.getPayPrice(), 107); - // 断言:SKU 3 - TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2); - assertEquals(orderItem03.getSkuId(), 30L); - assertEquals(orderItem03.getCount(), 5); - assertEquals(orderItem03.getPrice(), 30); - assertEquals(orderItem03.getPointPrice(), 0); - assertEquals(orderItem03.getPayPrice(), 150); - // 断言:Promotion 部分 - assertEquals(result.getPromotions().size(), 1); - TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); - assertEquals(promotion01.getId(), user.getId()); - assertEquals(promotion01.getName(), "积分抵扣"); - assertEquals(promotion01.getType(), PromotionTypeEnum.POINT.getType()); - assertEquals(promotion01.getTotalPrice(), 350); - assertEquals(promotion01.getDiscountPrice(), 100); - assertTrue(promotion01.getMatch()); - assertEquals(promotion01.getDescription(), "积分抵扣:省 1.00 元"); - assertEquals(promotion01.getItems().size(), 2); - TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0); - assertEquals(promotionItem011.getSkuId(), 10L); - assertEquals(promotionItem011.getTotalPrice(), 200); - assertEquals(promotionItem011.getDiscountPrice(), 57); - TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1); - assertEquals(promotionItem012.getSkuId(), 20L); - assertEquals(promotionItem012.getTotalPrice(), 150); - assertEquals(promotionItem012.getDiscountPrice(), 43); - } - - /** - * 当用户积分充足时,抵扣的金额为:配置表的“积分抵扣最大值” - */ - @Test - public void testCalculate_TradeDeductMaxPrice() { - // 准备参数 - TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() - .setUserId(233L).setPointStatus(true) // 是否使用积分 - .setItems(asList( - new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分 - new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分 - new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中,不使用积分 - )); - TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() - .setType(TradeOrderTypeEnum.NORMAL.getType()) - .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()) - .setItems(asList( - new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) - .setPrice(100).setSpuId(1L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) - .setPrice(50).setSpuId(2L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false) - .setPrice(30).setSpuId(3L) - )); - // 保证价格被初始化上 - TradePriceCalculatorHelper.recountPayPrice(result.getItems()); - TradePriceCalculatorHelper.recountAllPrice(result); - - // mock 方法(积分配置 信息) - MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class, - o -> o.setPointTradeDeductEnable(true) // 启用积分折扣 - .setPointTradeDeductUnitPrice(1) // 1 积分抵扣多少金额(单位分) - .setPointTradeDeductMaxPrice(50)); // 积分抵扣最大值 - when(memberConfigApi.getConfig()).thenReturn(memberConfig); - // mock 方法(会员 信息) - MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(100)); - when(memberUserApi.getUser(user.getId())).thenReturn(user); - - // 调用 - tradePointUsePriceCalculator.calculate(param, result); - // 断言:使用了多少积分 - assertEquals(result.getUsePoint(), 50); - // 断言:Price 部分 - TradePriceCalculateRespBO.Price price = result.getPrice(); - assertEquals(price.getTotalPrice(), 350); - assertEquals(price.getPayPrice(), 300); - assertEquals(price.getPointPrice(), 50); - // 断言:SKU 1 - TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); - assertEquals(orderItem01.getSkuId(), 10L); - assertEquals(orderItem01.getCount(), 2); - assertEquals(orderItem01.getPrice(), 100); - assertEquals(orderItem01.getPointPrice(), 28); - assertEquals(orderItem01.getPayPrice(), 172); - // 断言:SKU 2 - TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); - assertEquals(orderItem02.getSkuId(), 20L); - assertEquals(orderItem02.getCount(), 3); - assertEquals(orderItem02.getPrice(), 50); - assertEquals(orderItem02.getPointPrice(), 22); - assertEquals(orderItem02.getPayPrice(), 128); - // 断言:SKU 3 - TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2); - assertEquals(orderItem03.getSkuId(), 30L); - assertEquals(orderItem03.getCount(), 5); - assertEquals(orderItem03.getPrice(), 30); - assertEquals(orderItem03.getPointPrice(), 0); - assertEquals(orderItem03.getPayPrice(), 150); - // 断言:Promotion 部分 - assertEquals(result.getPromotions().size(), 1); - TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); - assertEquals(promotion01.getId(), user.getId()); - assertEquals(promotion01.getName(), "积分抵扣"); - assertEquals(promotion01.getType(), PromotionTypeEnum.POINT.getType()); - assertEquals(promotion01.getTotalPrice(), 350); - assertEquals(promotion01.getDiscountPrice(), 50); - assertTrue(promotion01.getMatch()); - assertEquals(promotion01.getDescription(), "积分抵扣:省 0.50 元"); - assertEquals(promotion01.getItems().size(), 2); - TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0); - assertEquals(promotionItem011.getSkuId(), 10L); - assertEquals(promotionItem011.getTotalPrice(), 200); - assertEquals(promotionItem011.getDiscountPrice(), 28); - TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1); - assertEquals(promotionItem012.getSkuId(), 20L); - assertEquals(promotionItem012.getTotalPrice(), 150); - assertEquals(promotionItem012.getDiscountPrice(), 22); - } - - /** - * 订单不使用积分,不会产生优惠 - */ - @Test - public void testCalculate_PointStatusFalse() { - // 准备参数 - TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() - .setUserId(233L).setPointStatus(false) // 是否使用积分 - .setItems(asList( - new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分 - new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分 - new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中,不使用积分 - )); - TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() - .setType(TradeOrderTypeEnum.NORMAL.getType()) - .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()) - .setItems(asList( - new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) - .setPrice(100).setSpuId(1L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) - .setPrice(50).setSpuId(2L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false) - .setPrice(30).setSpuId(3L) - )); - // 保证价格被初始化上 - TradePriceCalculatorHelper.recountPayPrice(result.getItems()); - TradePriceCalculatorHelper.recountAllPrice(result); - - // 调用 - tradePointUsePriceCalculator.calculate(param, result); - // 断言:没有使用积分 - assertNotUsePoint(result); - } - - /** - * 会员积分不足,不会产生优惠 - */ - @Test - public void testCalculate_UserPointNotEnough() { - // 准备参数 - TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() - .setUserId(233L).setPointStatus(true) // 是否使用积分 - .setItems(asList( - new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分 - new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分 - new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中,不使用积分 - )); - TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() - .setType(TradeOrderTypeEnum.NORMAL.getType()) - .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()) - .setItems(asList( - new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) - .setPrice(100).setSpuId(1L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) - .setPrice(50).setSpuId(2L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false) - .setPrice(30).setSpuId(3L) - )); - // 保证价格被初始化上 - TradePriceCalculatorHelper.recountPayPrice(result.getItems()); - TradePriceCalculatorHelper.recountAllPrice(result); - - // mock 方法(积分配置 信息) - MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class, - o -> o.setPointTradeDeductEnable(true) // 启用积分折扣 - .setPointTradeDeductUnitPrice(1) // 1 积分抵扣多少金额(单位分) - .setPointTradeDeductMaxPrice(100)); // 积分抵扣最大值 - when(memberConfigApi.getConfig()).thenReturn(memberConfig); - // mock 方法(会员 信息) - MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(0)); - when(memberUserApi.getUser(user.getId())).thenReturn(user); - - // 调用 - tradePointUsePriceCalculator.calculate(param, result); - - // 断言:没有使用积分 - assertNotUsePoint(result); - } - - /** - * 断言:没有使用积分 - */ - private static void assertNotUsePoint(TradePriceCalculateRespBO result) { - // 断言:使用了多少积分 - assertEquals(result.getUsePoint(), 0); - // 断言:Price 部分 - TradePriceCalculateRespBO.Price price = result.getPrice(); - assertEquals(price.getTotalPrice(), 350); - assertEquals(price.getPayPrice(), 350); - assertEquals(price.getPointPrice(), 0); - // 断言:SKU 1 - TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); - assertEquals(orderItem01.getSkuId(), 10L); - assertEquals(orderItem01.getCount(), 2); - assertEquals(orderItem01.getPrice(), 100); - assertEquals(orderItem01.getPointPrice(), 0); - assertEquals(orderItem01.getPayPrice(), 200); - // 断言:SKU 2 - TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); - assertEquals(orderItem02.getSkuId(), 20L); - assertEquals(orderItem02.getCount(), 3); - assertEquals(orderItem02.getPrice(), 50); - assertEquals(orderItem02.getPointPrice(), 0); - assertEquals(orderItem02.getPayPrice(), 150); - // 断言:SKU 3 - TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2); - assertEquals(orderItem03.getSkuId(), 30L); - assertEquals(orderItem03.getCount(), 5); - assertEquals(orderItem03.getPrice(), 30); - assertEquals(orderItem03.getPointPrice(), 0); - assertEquals(orderItem03.getPayPrice(), 150); - // 断言:Promotion 部分 - assertEquals(result.getPromotions().size(), 0); - } -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java deleted file mode 100644 index de72ed616..000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java +++ /dev/null @@ -1,235 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.price.calculator; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi; -import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum; -import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; -import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.ArrayList; - -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -/** - * {@link TradeRewardActivityPriceCalculator} 的单元测试类 - * - * @author 芋道源码 - */ -public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest { - - @InjectMocks - private TradeRewardActivityPriceCalculator tradeRewardActivityPriceCalculator; - - @Mock - private RewardActivityApi rewardActivityApi; - - @Test - public void testCalculate_match() { - // 准备参数 - TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() - .setItems(asList( - new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配活动 1 - new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 匹配活动 1 - new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true) // 匹配活动 2 - )); - TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() - .setType(TradeOrderTypeEnum.NORMAL.getType()) - .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()) - .setItems(asList( - new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) - .setPrice(100).setSpuId(1L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) - .setPrice(50).setSpuId(2L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(4).setSelected(true) - .setPrice(30).setSpuId(3L) - )); - // 保证价格被初始化上 - TradePriceCalculatorHelper.recountPayPrice(result.getItems()); - TradePriceCalculatorHelper.recountAllPrice(result); - - // mock 方法(限时折扣 DiscountActivity 信息) - when(rewardActivityApi.getMatchRewardActivityList(eq(asSet(1L, 2L, 3L)))).thenReturn(asList( - randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号") - .setSpuIds(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType()) - .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(200).setDiscountPrice(70)))), - randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(2000L).setName("活动 2000 号") - .setSpuIds(singletonList(3L)).setConditionType(PromotionConditionTypeEnum.COUNT.getType()) - .setRules(asList(new RewardActivityMatchRespDTO.Rule().setLimit(1).setDiscountPrice(10), - new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60), // 最大可满足,因为是 4 个 - new RewardActivityMatchRespDTO.Rule().setLimit(10).setDiscountPrice(100)))) - )); - - // 调用 - tradeRewardActivityPriceCalculator.calculate(param, result); - // 断言 Order 部分 - TradePriceCalculateRespBO.Price price = result.getPrice(); - assertEquals(price.getTotalPrice(), 470); - assertEquals(price.getDiscountPrice(), 130); - assertEquals(price.getPointPrice(), 0); - assertEquals(price.getDeliveryPrice(), 0); - assertEquals(price.getCouponPrice(), 0); - assertEquals(price.getPayPrice(), 340); - assertNull(result.getCouponId()); - // 断言:SKU 1 - assertEquals(result.getItems().size(), 3); - TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); - assertEquals(orderItem01.getSkuId(), 10L); - assertEquals(orderItem01.getCount(), 2); - assertEquals(orderItem01.getPrice(), 100); - assertEquals(orderItem01.getDiscountPrice(), 40); - assertEquals(orderItem01.getDeliveryPrice(), 0); - assertEquals(orderItem01.getCouponPrice(), 0); - assertEquals(orderItem01.getPointPrice(), 0); - assertEquals(orderItem01.getPayPrice(), 160); - // 断言:SKU 2 - TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); - assertEquals(orderItem02.getSkuId(), 20L); - assertEquals(orderItem02.getCount(), 3); - assertEquals(orderItem02.getPrice(), 50); - assertEquals(orderItem02.getDiscountPrice(), 30); - assertEquals(orderItem02.getDeliveryPrice(), 0); - assertEquals(orderItem02.getCouponPrice(), 0); - assertEquals(orderItem02.getPointPrice(), 0); - assertEquals(orderItem02.getPayPrice(), 120); - // 断言:SKU 3 - TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2); - assertEquals(orderItem03.getSkuId(), 30L); - assertEquals(orderItem03.getCount(), 4); - assertEquals(orderItem03.getPrice(), 30); - assertEquals(orderItem03.getDiscountPrice(), 60); - assertEquals(orderItem03.getDeliveryPrice(), 0); - assertEquals(orderItem03.getCouponPrice(), 0); - assertEquals(orderItem03.getPointPrice(), 0); - assertEquals(orderItem03.getPayPrice(), 60); - // 断言:Promotion 部分(第一个) - assertEquals(result.getPromotions().size(), 2); - TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); - assertEquals(promotion01.getId(), 1000L); - assertEquals(promotion01.getName(), "活动 1000 号"); - assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); - assertEquals(promotion01.getTotalPrice(), 350); - assertEquals(promotion01.getDiscountPrice(), 70); - assertTrue(promotion01.getMatch()); - assertEquals(promotion01.getDescription(), "满减送:省 0.70 元"); - assertEquals(promotion01.getItems().size(), 2); - TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0); - assertEquals(promotionItem011.getSkuId(), 10L); - assertEquals(promotionItem011.getTotalPrice(), 200); - assertEquals(promotionItem011.getDiscountPrice(), 40); - TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1); - assertEquals(promotionItem012.getSkuId(), 20L); - assertEquals(promotionItem012.getTotalPrice(), 150); - assertEquals(promotionItem012.getDiscountPrice(), 30); - // 断言:Promotion 部分(第二个) - TradePriceCalculateRespBO.Promotion promotion02 = result.getPromotions().get(1); - assertEquals(promotion02.getId(), 2000L); - assertEquals(promotion02.getName(), "活动 2000 号"); - assertEquals(promotion02.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); - assertEquals(promotion02.getTotalPrice(), 120); - assertEquals(promotion02.getDiscountPrice(), 60); - assertTrue(promotion02.getMatch()); - assertEquals(promotion02.getDescription(), "满减送:省 0.60 元"); - TradePriceCalculateRespBO.PromotionItem promotionItem02 = promotion02.getItems().get(0); - assertEquals(promotion02.getItems().size(), 1); - assertEquals(promotionItem02.getSkuId(), 30L); - assertEquals(promotionItem02.getTotalPrice(), 120); - assertEquals(promotionItem02.getDiscountPrice(), 60); - } - - @Test - public void testCalculate_notMatch() { - // 准备参数 - TradePriceCalculateReqBO param = new TradePriceCalculateReqBO() - .setItems(asList( - new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), - new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), - new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true) - )); - TradePriceCalculateRespBO result = new TradePriceCalculateRespBO() - .setType(TradeOrderTypeEnum.NORMAL.getType()) - .setPrice(new TradePriceCalculateRespBO.Price()) - .setPromotions(new ArrayList<>()) - .setItems(asList( - new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true) - .setPrice(100).setSpuId(1L), - new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true) - .setPrice(50).setSpuId(2L) - )); - // 保证价格被初始化上 - TradePriceCalculatorHelper.recountPayPrice(result.getItems()); - TradePriceCalculatorHelper.recountAllPrice(result); - - // mock 方法(限时折扣 DiscountActivity 信息) - when(rewardActivityApi.getMatchRewardActivityList(eq(asSet(1L, 2L)))).thenReturn(singletonList( - randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号") - .setSpuIds(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType()) - .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(351).setDiscountPrice(70)))) - )); - - // 调用 - tradeRewardActivityPriceCalculator.calculate(param, result); - // 断言 Order 部分 - TradePriceCalculateRespBO.Price price = result.getPrice(); - assertEquals(price.getTotalPrice(), 350); - assertEquals(price.getDiscountPrice(), 0); - assertEquals(price.getPointPrice(), 0); - assertEquals(price.getDeliveryPrice(), 0); - assertEquals(price.getCouponPrice(), 0); - assertEquals(price.getPayPrice(), 350); - assertNull(result.getCouponId()); - // 断言:SKU 1 - assertEquals(result.getItems().size(), 2); - TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0); - assertEquals(orderItem01.getSkuId(), 10L); - assertEquals(orderItem01.getCount(), 2); - assertEquals(orderItem01.getPrice(), 100); - assertEquals(orderItem01.getDiscountPrice(), 0); - assertEquals(orderItem01.getDeliveryPrice(), 0); - assertEquals(orderItem01.getCouponPrice(), 0); - assertEquals(orderItem01.getPointPrice(), 0); - assertEquals(orderItem01.getPayPrice(), 200); - // 断言:SKU 2 - TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); - assertEquals(orderItem02.getSkuId(), 20L); - assertEquals(orderItem02.getCount(), 3); - assertEquals(orderItem02.getPrice(), 50); - assertEquals(orderItem02.getDiscountPrice(), 0); - assertEquals(orderItem02.getDeliveryPrice(), 0); - assertEquals(orderItem02.getCouponPrice(), 0); - assertEquals(orderItem02.getPointPrice(), 0); - assertEquals(orderItem02.getPayPrice(), 150); - // 断言 Promotion 部分 - assertEquals(result.getPromotions().size(), 1); - TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); - assertEquals(promotion01.getId(), 1000L); - assertEquals(promotion01.getName(), "活动 1000 号"); - assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType()); - assertEquals(promotion01.getTotalPrice(), 350); - assertEquals(promotion01.getDiscountPrice(), 0); - assertFalse(promotion01.getMatch()); - assertEquals(promotion01.getDescription(), "TODO"); // TODO 芋艿:后面再想想 - assertEquals(promotion01.getItems().size(), 2); - TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0); - assertEquals(promotionItem011.getSkuId(), 10L); - assertEquals(promotionItem011.getTotalPrice(), 200); - assertEquals(promotionItem011.getDiscountPrice(), 0); - TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1); - assertEquals(promotionItem012.getSkuId(), 20L); - assertEquals(promotionItem012.getTotalPrice(), 150); - assertEquals(promotionItem012.getDiscountPrice(), 0); - } - -} diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java index c05eaa9fd..07cc581c6 100644 --- a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java +++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java @@ -39,7 +39,8 @@ public interface MemberUserApi { * @return 会员用户 Map */ default Map getUserMap(Collection ids) { - return convertMap(getUserList(ids).getCheckedData(), MemberUserRespDTO::getId); + List list = getUserList(ids).getCheckedData(); + return convertMap(list, MemberUserRespDTO::getId); } @GetMapping(PREFIX + "/list-by-nickname") diff --git a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/address/AddressServiceImplTest.java b/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/address/AddressServiceImplTest.java deleted file mode 100644 index e4337f2c9..000000000 --- a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/address/AddressServiceImplTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package cn.iocoder.yudao.module.member.service.address; - -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressCreateReqVO; -import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressUpdateReqVO; -import cn.iocoder.yudao.module.member.dal.dataobject.address.MemberAddressDO; -import cn.iocoder.yudao.module.member.dal.mysql.address.MemberAddressMapper; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; - -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.ADDRESS_NOT_EXISTS; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -/** - * {@link AddressServiceImpl} 的单元测试类 - * - * @author 芋道源码 - */ -@Import(AddressServiceImpl.class) -public class AddressServiceImplTest extends BaseDbUnitTest { - - @Resource - private AddressServiceImpl addressService; - - @Resource - private MemberAddressMapper addressMapper; - - @Test - public void testCreateAddress_success() { - // 准备参数 - AppAddressCreateReqVO reqVO = randomPojo(AppAddressCreateReqVO.class); - - // 调用 - Long addressId = addressService.createAddress(randomLongId(), reqVO); - // 断言 - assertNotNull(addressId); - // 校验记录的属性是否正确 - MemberAddressDO address = addressMapper.selectById(addressId); - assertPojoEquals(reqVO, address); - } - - @Test - public void testUpdateAddress_success() { - // mock 数据 - MemberAddressDO dbAddress = randomPojo(MemberAddressDO.class); - addressMapper.insert(dbAddress);// @Sql: 先插入出一条存在的数据 - // 准备参数 - AppAddressUpdateReqVO reqVO = randomPojo(AppAddressUpdateReqVO.class, o -> { - o.setId(dbAddress.getId()); // 设置更新的 ID - }); - - // 调用 - addressService.updateAddress(dbAddress.getUserId(), reqVO); - // 校验是否更新正确 - MemberAddressDO address = addressMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, address); - } - - @Test - public void testUpdateAddress_notExists() { - // 准备参数 - AppAddressUpdateReqVO reqVO = randomPojo(AppAddressUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> addressService.updateAddress(randomLongId(), reqVO), ADDRESS_NOT_EXISTS); - } - - @Test - public void testDeleteAddress_success() { - // mock 数据 - MemberAddressDO dbAddress = randomPojo(MemberAddressDO.class); - addressMapper.insert(dbAddress);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbAddress.getId(); - - // 调用 - addressService.deleteAddress(dbAddress.getUserId(), id); - // 校验数据不存在了 - assertNull(addressMapper.selectById(id)); - } - - @Test - public void testDeleteAddress_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> addressService.deleteAddress(randomLongId(), id), ADDRESS_NOT_EXISTS); - } - -} diff --git a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java b/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java deleted file mode 100644 index 78ddc5677..000000000 --- a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java +++ /dev/null @@ -1,118 +0,0 @@ -package cn.iocoder.yudao.module.member.service.auth; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; -import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; -import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; -import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper; -import cn.iocoder.yudao.module.member.service.user.MemberUserService; -import cn.iocoder.yudao.module.system.api.logger.LoginLogApi; -import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi; -import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; -import cn.iocoder.yudao.module.system.api.social.SocialUserApi; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; -import org.springframework.security.crypto.password.PasswordEncoder; - -import javax.annotation.Resource; -import java.util.function.Consumer; - -import static cn.hutool.core.util.RandomUtil.randomEle; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; - -// TODO @芋艿:单测的 review,等逻辑都达成一致后 -/** - * {@link MemberAuthService} 的单元测试类 - * - * @author 宋天 - */ -@Import({MemberAuthServiceImpl.class, YudaoRedisAutoConfiguration.class}) -public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest { - - // TODO @芋艿:登录相关的单测,待补全 - - @Resource - private MemberAuthServiceImpl authService; - - @MockBean - private MemberUserService userService; - @MockBean - private SmsCodeApi smsCodeApi; - @MockBean - private LoginLogApi loginLogApi; - @MockBean - private OAuth2TokenApi oauth2TokenApi; - @MockBean - private SocialUserApi socialUserApi; - @MockBean - private PasswordEncoder passwordEncoder; - - @Resource - private MemberUserMapper memberUserMapper; - - // TODO 芋艿:后续重构这个单测 -// @Test -// public void testUpdatePassword_success(){ -// // 准备参数 -// MemberUserDO userDO = randomUserDO(); -// memberUserMapper.insert(userDO); -// -// // 新密码 -// String newPassword = randomString(); -// -// // 请求实体 -// AppMemberUserUpdatePasswordReqVO reqVO = AppMemberUserUpdatePasswordReqVO.builder() -// .oldPassword(userDO.getPassword()) -// .password(newPassword) -// .build(); -// -// // 测试桩 -// // 这两个相等是为了返回ture这个结果 -// when(passwordEncoder.matches(reqVO.getOldPassword(),reqVO.getOldPassword())).thenReturn(true); -// when(passwordEncoder.encode(newPassword)).thenReturn(newPassword); -// -// // 更新用户密码 -// authService.updatePassword(userDO.getId(), reqVO); -// assertEquals(memberUserMapper.selectById(userDO.getId()).getPassword(),newPassword); -// } - - // TODO 芋艿:后续重构这个单测 -// @Test -// public void testResetPassword_success(){ -// // 准备参数 -// MemberUserDO userDO = randomUserDO(); -// memberUserMapper.insert(userDO); -// -// // 随机密码 -// String password = randomNumbers(11); -// // 随机验证码 -// String code = randomNumbers(4); -// -// // mock -// when(passwordEncoder.encode(password)).thenReturn(password); -// -// // 更新用户密码 -// AppMemberUserResetPasswordReqVO reqVO = new AppMemberUserResetPasswordReqVO(); -// reqVO.setMobile(userDO.getMobile()); -// reqVO.setPassword(password); -// reqVO.setCode(code); -// -// authService.resetPassword(reqVO); -// assertEquals(memberUserMapper.selectById(userDO.getId()).getPassword(),password); -// } - - // ========== 随机对象 ========== - - @SafeVarargs - private static MemberUserDO randomUserDO(Consumer... consumers) { - Consumer consumer = (o) -> { - o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 - o.setPassword(randomString()); - }; - return randomPojo(MemberUserDO.class, ArrayUtils.append(consumer, consumers)); - } - - -} diff --git a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImplTest.java b/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImplTest.java deleted file mode 100644 index 6d8e6f9ce..000000000 --- a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImplTest.java +++ /dev/null @@ -1,160 +0,0 @@ -package cn.iocoder.yudao.module.member.service.group; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupCreateReqVO; -import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupPageReqVO; -import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupUpdateReqVO; -import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO; -import cn.iocoder.yudao.module.member.dal.mysql.group.MemberGroupMapper; -import cn.iocoder.yudao.module.member.service.user.MemberUserService; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; - -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; -import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.GROUP_HAS_USER; -import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.GROUP_NOT_EXISTS; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -// TODO 芋艿:完全 review 完,在去 review 单测 -/** - * {@link MemberGroupServiceImpl} 的单元测试类 - * - * @author owen - */ -@Import(MemberGroupServiceImpl.class) -public class MemberGroupServiceImplTest extends BaseDbUnitTest { - - @Resource - private MemberGroupServiceImpl groupService; - - @Resource - private MemberGroupMapper groupMapper; - - @MockBean - private MemberUserService memberUserService; - - @Test - public void testCreateGroup_success() { - // 准备参数 - MemberGroupCreateReqVO reqVO = randomPojo(MemberGroupCreateReqVO.class, - o -> o.setStatus(randomCommonStatus())); - - // 调用 - Long groupId = groupService.createGroup(reqVO); - // 断言 - assertNotNull(groupId); - // 校验记录的属性是否正确 - MemberGroupDO group = groupMapper.selectById(groupId); - assertPojoEquals(reqVO, group); - } - - @Test - public void testUpdateGroup_success() { - // mock 数据 - MemberGroupDO dbGroup = randomPojo(MemberGroupDO.class); - groupMapper.insert(dbGroup);// @Sql: 先插入出一条存在的数据 - // 准备参数 - MemberGroupUpdateReqVO reqVO = randomPojo(MemberGroupUpdateReqVO.class, o -> { - o.setId(dbGroup.getId()); // 设置更新的 ID - o.setStatus(randomCommonStatus()); - }); - - // 调用 - groupService.updateGroup(reqVO); - // 校验是否更新正确 - MemberGroupDO group = groupMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, group); - } - - @Test - public void testUpdateGroup_notExists() { - // 准备参数 - MemberGroupUpdateReqVO reqVO = randomPojo(MemberGroupUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> groupService.updateGroup(reqVO), GROUP_NOT_EXISTS); - } - - @Test - public void testDeleteGroup_success() { - // mock 数据 - MemberGroupDO dbGroup = randomPojo(MemberGroupDO.class); - groupMapper.insert(dbGroup);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbGroup.getId(); - - // 调用 - groupService.deleteGroup(id); - // 校验数据不存在了 - assertNull(groupMapper.selectById(id)); - } - - @Test - public void testDeleteGroup_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> groupService.deleteGroup(id), GROUP_NOT_EXISTS); - } - - @Test - public void testDeleteGroup_hasUser() { - // mock 数据 - MemberGroupDO dbGroup = randomPojo(MemberGroupDO.class); - groupMapper.insert(dbGroup);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbGroup.getId(); - - // mock 会员数据 - when(memberUserService.getUserCountByGroupId(eq(id))).thenReturn(1L); - - // 调用, 并断言异常 - assertServiceException(() -> groupService.deleteGroup(id), GROUP_HAS_USER); - } - - @Test - public void testGetGroupPage() { - String name = randomString(); - int status = CommonStatusEnum.ENABLE.getStatus(); - - // mock 数据 - MemberGroupDO dbGroup = randomPojo(MemberGroupDO.class, o -> { // 等会查询到 - o.setName(name); - o.setStatus(status); - o.setCreateTime(buildTime(2023, 2, 18)); - }); - groupMapper.insert(dbGroup); - // 测试 name 不匹配 - groupMapper.insert(cloneIgnoreId(dbGroup, o -> o.setName(""))); - // 测试 status 不匹配 - groupMapper.insert(cloneIgnoreId(dbGroup, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); - // 测试 createTime 不匹配 - groupMapper.insert(cloneIgnoreId(dbGroup, o -> o.setCreateTime(null))); - // 准备参数 - MemberGroupPageReqVO reqVO = new MemberGroupPageReqVO(); - reqVO.setName(name); - reqVO.setStatus(status); - reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - - // 调用 - PageResult pageResult = groupService.getGroupPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbGroup, pageResult.getList().get(0)); - } - -} diff --git a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImplTest.java b/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImplTest.java deleted file mode 100644 index 439322913..000000000 --- a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImplTest.java +++ /dev/null @@ -1,268 +0,0 @@ -package cn.iocoder.yudao.module.member.service.level; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelCreateReqVO; -import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelListReqVO; -import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelUpdateReqVO; -import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO; -import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper; -import cn.iocoder.yudao.module.member.service.user.MemberUserService; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; -import java.util.List; -import java.util.function.Consumer; - -import static cn.hutool.core.util.RandomUtil.randomInt; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; -import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*; -import static org.junit.jupiter.api.Assertions.*; - -// TODO 芋艿:完全 review 完,在去 review 单测 -/** - * {@link MemberLevelServiceImpl} 的单元测试类 - * - * @author owen - */ -@Import(MemberLevelServiceImpl.class) -public class MemberLevelServiceImplTest extends BaseDbUnitTest { - - @Resource - private MemberLevelServiceImpl levelService; - - @Resource - private MemberLevelMapper memberlevelMapper; - - @MockBean - private MemberLevelRecordService memberLevelRecordService; - @MockBean - private MemberExperienceRecordService memberExperienceRecordService; - @MockBean - private MemberUserService memberUserService; - - @Test - public void testCreateLevel_success() { - // 准备参数 - MemberLevelCreateReqVO reqVO = randomPojo(MemberLevelCreateReqVO.class, o -> { - o.setDiscountPercent(randomInt()); - o.setIcon(randomURL()); - o.setBackgroundUrl(randomURL()); - o.setStatus(randomCommonStatus()); - }); - - // 调用 - Long levelId = levelService.createLevel(reqVO); - // 断言 - assertNotNull(levelId); - // 校验记录的属性是否正确 - MemberLevelDO level = memberlevelMapper.selectById(levelId); - assertPojoEquals(reqVO, level); - } - - @Test - public void testUpdateLevel_success() { - // mock 数据 - MemberLevelDO dbLevel = randomPojo(MemberLevelDO.class); - memberlevelMapper.insert(dbLevel);// @Sql: 先插入出一条存在的数据 - // 准备参数 - MemberLevelUpdateReqVO reqVO = randomPojo(MemberLevelUpdateReqVO.class, o -> { - o.setId(dbLevel.getId()); // 设置更新的 ID - //以下要保持一致 - o.setName(dbLevel.getName()); - o.setLevel(dbLevel.getLevel()); - o.setExperience(dbLevel.getExperience()); - //以下是要修改的字段 - o.setDiscountPercent(randomInt()); - o.setIcon(randomURL()); - o.setBackgroundUrl(randomURL()); - o.setStatus(randomCommonStatus()); - }); - - // 调用 - levelService.updateLevel(reqVO); - // 校验是否更新正确 - MemberLevelDO level = memberlevelMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, level); - } - - @Test - public void testUpdateLevel_notExists() { - // 准备参数 - MemberLevelUpdateReqVO reqVO = randomPojo(MemberLevelUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> levelService.updateLevel(reqVO), LEVEL_NOT_EXISTS); - } - - @Test - public void testDeleteLevel_success() { - // mock 数据 - MemberLevelDO dbLevel = randomPojo(MemberLevelDO.class); - memberlevelMapper.insert(dbLevel);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbLevel.getId(); - - // 调用 - levelService.deleteLevel(id); - // 校验数据不存在了 - assertNull(memberlevelMapper.selectById(id)); - } - - @Test - public void testDeleteLevel_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> levelService.deleteLevel(id), LEVEL_NOT_EXISTS); - } - - @Test - public void testGetLevelList() { - // mock 数据 - MemberLevelDO dbLevel = randomPojo(MemberLevelDO.class, o -> { // 等会查询到 - o.setName("黄金会员"); - o.setStatus(1); - }); - memberlevelMapper.insert(dbLevel); - // 测试 name 不匹配 - memberlevelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setName(""))); - // 测试 status 不匹配 - memberlevelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setStatus(0))); - // 准备参数 - MemberLevelListReqVO reqVO = new MemberLevelListReqVO(); - reqVO.setName("黄金会员"); - reqVO.setStatus(1); - - // 调用 - List list = levelService.getLevelList(reqVO); - // 断言 - assertEquals(1, list.size()); - assertPojoEquals(dbLevel, list.get(0)); - } - - @Test - public void testCreateLevel_nameUnique() { - // 准备参数 - String name = randomString(); - - // mock 数据 - memberlevelMapper.insert(randomLevelDO(o -> o.setName(name))); - - // 调用,校验异常 - List list = memberlevelMapper.selectList(); - assertServiceException(() -> levelService.validateNameUnique(list, null, name), LEVEL_NAME_EXISTS, name); - } - - @Test - public void testUpdateLevel_nameUnique() { - // 准备参数 - Long id = randomLongId(); - String name = randomString(); - - // mock 数据 - memberlevelMapper.insert(randomLevelDO(o -> o.setName(name))); - - // 调用,校验异常 - List list = memberlevelMapper.selectList(); - assertServiceException(() -> levelService.validateNameUnique(list, id, name), LEVEL_NAME_EXISTS, name); - } - - @Test - public void testCreateLevel_levelUnique() { - // 准备参数 - Integer level = randomInteger(); - String name = randomString(); - - // mock 数据 - memberlevelMapper.insert(randomLevelDO(o -> { - o.setLevel(level); - o.setName(name); - })); - - // 调用,校验异常 - List list = memberlevelMapper.selectList(); - assertServiceException(() -> levelService.validateLevelUnique(list, null, level), LEVEL_VALUE_EXISTS, level, name); - } - - @Test - public void testUpdateLevel_levelUnique() { - // 准备参数 - Long id = randomLongId(); - Integer level = randomInteger(); - String name = randomString(); - - // mock 数据 - memberlevelMapper.insert(randomLevelDO(o -> { - o.setLevel(level); - o.setName(name); - })); - - // 调用,校验异常 - List list = memberlevelMapper.selectList(); - assertServiceException(() -> levelService.validateLevelUnique(list, id, level), LEVEL_VALUE_EXISTS, level, name); - } - - @Test - public void testCreateLevel_experienceOutRange() { - // 准备参数 - int level = 10; - int experience = 10; - String name = randomString(); - - // mock 数据 - memberlevelMapper.insert(randomLevelDO(o -> { - o.setLevel(level); - o.setExperience(experience); - o.setName(name); - })); - List list = memberlevelMapper.selectList(); - - // 调用,校验异常 - assertServiceException(() -> levelService.validateExperienceOutRange(list, null, level + 1, experience - 1), LEVEL_EXPERIENCE_MIN, name, level); - // 调用,校验异常 - assertServiceException(() -> levelService.validateExperienceOutRange(list, null, level - 1, experience + 1), LEVEL_EXPERIENCE_MAX, name, level); - } - - @Test - public void testUpdateLevel_experienceOutRange() { - // 准备参数 - int level = 10; - int experience = 10; - Long id = randomLongId(); - String name = randomString(); - - // mock 数据 - memberlevelMapper.insert(randomLevelDO(o -> { - o.setLevel(level); - o.setExperience(experience); - o.setName(name); - })); - List list = memberlevelMapper.selectList(); - - // 调用,校验异常 - assertServiceException(() -> levelService.validateExperienceOutRange(list, id, level + 1, experience - 1), LEVEL_EXPERIENCE_MIN, name, level); - // 调用,校验异常 - assertServiceException(() -> levelService.validateExperienceOutRange(list, id, level - 1, experience + 1), LEVEL_EXPERIENCE_MAX, name, level); - } - - // ========== 随机对象 ========== - - @SafeVarargs - private static MemberLevelDO randomLevelDO(Consumer... consumers) { - Consumer consumer = (o) -> { - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - o.setDiscountPercent(randomInt(0, 100)); - o.setIcon(randomURL()); - o.setBackgroundUrl(randomURL()); - }; - return randomPojo(MemberLevelDO.class, ArrayUtils.append(consumer, consumers)); - } -} diff --git a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/tag/MemberTagServiceImplTest.java b/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/tag/MemberTagServiceImplTest.java deleted file mode 100644 index 5fd4f7a9c..000000000 --- a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/tag/MemberTagServiceImplTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package cn.iocoder.yudao.module.member.service.tag; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.member.controller.admin.tag.vo.MemberTagCreateReqVO; -import cn.iocoder.yudao.module.member.controller.admin.tag.vo.MemberTagPageReqVO; -import cn.iocoder.yudao.module.member.controller.admin.tag.vo.MemberTagUpdateReqVO; -import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO; -import cn.iocoder.yudao.module.member.dal.mysql.tag.MemberTagMapper; -import cn.iocoder.yudao.module.member.service.user.MemberUserService; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; - -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.TAG_NOT_EXISTS; -import static org.junit.jupiter.api.Assertions.*; - -// TODO 芋艿:完全 review 完,在去 review 单测 -/** - * {@link MemberTagServiceImpl} 的单元测试类 - * - * @author 芋道源码 - */ -@Import(MemberTagServiceImpl.class) -public class MemberTagServiceImplTest extends BaseDbUnitTest { - - @Resource - private MemberTagServiceImpl tagService; - - @Resource - private MemberTagMapper tagMapper; - - @MockBean - private MemberUserService memberUserService; - - @Test - public void testCreateTag_success() { - // 准备参数 - MemberTagCreateReqVO reqVO = randomPojo(MemberTagCreateReqVO.class); - - // 调用 - Long tagId = tagService.createTag(reqVO); - // 断言 - assertNotNull(tagId); - // 校验记录的属性是否正确 - MemberTagDO tag = tagMapper.selectById(tagId); - assertPojoEquals(reqVO, tag); - } - - @Test - public void testUpdateTag_success() { - // mock 数据 - MemberTagDO dbTag = randomPojo(MemberTagDO.class); - tagMapper.insert(dbTag);// @Sql: 先插入出一条存在的数据 - // 准备参数 - MemberTagUpdateReqVO reqVO = randomPojo(MemberTagUpdateReqVO.class, o -> { - o.setId(dbTag.getId()); // 设置更新的 ID - }); - - // 调用 - tagService.updateTag(reqVO); - // 校验是否更新正确 - MemberTagDO tag = tagMapper.selectById(reqVO.getId()); // 获取最新的 - assertPojoEquals(reqVO, tag); - } - - @Test - public void testUpdateTag_notExists() { - // 准备参数 - MemberTagUpdateReqVO reqVO = randomPojo(MemberTagUpdateReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> tagService.updateTag(reqVO), TAG_NOT_EXISTS); - } - - @Test - public void testDeleteTag_success() { - // mock 数据 - MemberTagDO dbTag = randomPojo(MemberTagDO.class); - tagMapper.insert(dbTag);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbTag.getId(); - - // 调用 - tagService.deleteTag(id); - // 校验数据不存在了 - assertNull(tagMapper.selectById(id)); - } - - @Test - public void testDeleteTag_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> tagService.deleteTag(id), TAG_NOT_EXISTS); - } - - @Test - public void testGetTagPage() { - // mock 数据 - MemberTagDO dbTag = randomPojo(MemberTagDO.class, o -> { // 等会查询到 - o.setName("test"); - o.setCreateTime(buildTime(2023, 2, 18)); - }); - tagMapper.insert(dbTag); - // 测试 name 不匹配 - tagMapper.insert(cloneIgnoreId(dbTag, o -> o.setName("ne"))); - // 测试 createTime 不匹配 - tagMapper.insert(cloneIgnoreId(dbTag, o -> o.setCreateTime(null))); - // 准备参数 - MemberTagPageReqVO reqVO = new MemberTagPageReqVO(); - reqVO.setName("test"); - reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - - // 调用 - PageResult pageResult = tagService.getTagPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbTag, pageResult.getList().get(0)); - } - -} diff --git a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImplTest.java b/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImplTest.java deleted file mode 100644 index bff2ae0c1..000000000 --- a/yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImplTest.java +++ /dev/null @@ -1,136 +0,0 @@ -package cn.iocoder.yudao.module.member.service.user; - -import cn.hutool.core.util.RandomUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; -import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; -import cn.iocoder.yudao.module.infra.api.file.FileApi; -import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserUpdateMobileReqVO; -import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; -import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper; -import cn.iocoder.yudao.module.member.service.auth.MemberAuthServiceImpl; -import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.security.crypto.password.PasswordEncoder; - -import javax.annotation.Resource; -import java.util.function.Consumer; - -import static cn.hutool.core.util.RandomUtil.randomEle; -import static cn.hutool.core.util.RandomUtil.randomNumbers; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static org.junit.jupiter.api.Assertions.assertEquals; - -// TODO @芋艿:单测的 review,等逻辑都达成一致后 -/** - * {@link MemberUserServiceImpl} 的单元测试类 - * - * @author 宋天 - */ -@Import({MemberUserServiceImpl.class, YudaoRedisAutoConfiguration.class}) -public class MemberUserServiceImplTest extends BaseDbAndRedisUnitTest { - - @Resource - private MemberUserServiceImpl memberUserService; - - @Resource - private StringRedisTemplate stringRedisTemplate; - - @Resource - private MemberUserMapper userMapper; - - @MockBean - private MemberAuthServiceImpl authService; - - @MockBean - private PasswordEncoder passwordEncoder; - - @MockBean - private SmsCodeApi smsCodeApi; - @MockBean - private FileApi fileApi; - - // TODO 芋艿:后续重构这个单测 -// @Test -// public void testUpdateNickName_success(){ -// // mock 数据 -// MemberUserDO userDO = randomUserDO(); -// userMapper.insert(userDO); -// -// // 随机昵称 -// String newNickName = randomString(); -// -// // 调用接口修改昵称 -// memberUserService.updateUser(userDO.getId(),newNickName); -// // 查询新修改后的昵称 -// String nickname = memberUserService.getUser(userDO.getId()).getNickname(); -// // 断言 -// assertEquals(newNickName,nickname); -// } -// -// @Test -// public void testUpdateAvatar_success() throws Exception { -// // mock 数据 -// MemberUserDO dbUser = randomUserDO(); -// userMapper.insert(dbUser); -// -// // 准备参数 -// Long userId = dbUser.getId(); -// byte[] avatarFileBytes = randomBytes(10); -// ByteArrayInputStream avatarFile = new ByteArrayInputStream(avatarFileBytes); -// // mock 方法 -// String avatar = randomString(); -// when(fileApi.createFile(eq(avatarFileBytes))).thenReturn(avatar); -// // 调用 -// String str = memberUserService.updateUserAvatar(userId, avatarFile); -// // 断言 -// assertEquals(avatar, str); -// } - - @Test - @Disabled // TODO 芋艿:后续再修复 - public void updateMobile_success(){ - // mock数据 - String oldMobile = randomNumbers(11); - MemberUserDO userDO = randomUserDO(); - userDO.setMobile(oldMobile); - userMapper.insert(userDO); - - // TODO 芋艿:需要修复该单元测试,重构多模块带来的 - // 旧手机和旧验证码 -// SmsCodeDO codeDO = new SmsCodeDO(); - String oldCode = RandomUtil.randomString(4); -// codeDO.setMobile(userDO.getMobile()); -// codeDO.setCode(oldCode); -// codeDO.setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()); -// codeDO.setUsed(Boolean.FALSE); -// when(smsCodeService.checkCodeIsExpired(codeDO.getMobile(),codeDO.getCode(),codeDO.getScene())).thenReturn(codeDO); - - // 更新手机号 - String newMobile = randomNumbers(11); - String newCode = randomNumbers(4); - AppMemberUserUpdateMobileReqVO reqVO = new AppMemberUserUpdateMobileReqVO(); - reqVO.setMobile(newMobile); - reqVO.setCode(newCode); - reqVO.setOldCode(oldCode); - memberUserService.updateUserMobile(userDO.getId(),reqVO); - - assertEquals(memberUserService.getUser(userDO.getId()).getMobile(),newMobile); - } - - // ========== 随机对象 ========== - - @SafeVarargs - private static MemberUserDO randomUserDO(Consumer... consumers) { - Consumer consumer = (o) -> { - o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 - }; - return randomPojo(MemberUserDO.class, ArrayUtils.append(consumer, consumers)); - } - -} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java index affa89ea1..7f558286f 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java @@ -44,7 +44,8 @@ public interface DeptApi { * @return 部门 Map */ default Map getDeptMap(Set ids) { - return CollectionUtils.convertMap(getDeptList(ids).getCheckedData(), DeptRespDTO::getId); + List list = getDeptList(ids).getCheckedData(); + return CollectionUtils.convertMap(list, DeptRespDTO::getId); } } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java index 14082ad68..e7f34bf52 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java @@ -49,7 +49,8 @@ public interface AdminUserApi { * @return 用户 Map */ default Map getUserMap(Collection ids) { - return CollectionUtils.convertMap(getUsers(ids).getCheckedData(), AdminUserRespDTO::getId); + List users = getUsers(ids).getCheckedData(); + return CollectionUtils.convertMap(users, AdminUserRespDTO::getId); } @GetMapping(PREFIX + "/valid") From 9fc31ac2ae8001cc24d829269fefa22a9cdbc7f8 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 24 Oct 2023 00:02:18 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=9A=84=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-module-mall/yudao-module-trade-api/pom.xml | 2 +- .../system/service/sms/SmsTemplateServiceImpl.java | 2 -- .../system/service/auth/AdminAuthServiceImplTest.java | 5 +++-- .../system/service/sms/SmsTemplateServiceImplTest.java | 9 +++------ 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-api/pom.xml b/yudao-module-mall/yudao-module-trade-api/pom.xml index 8aea6e2cb..84c99130d 100644 --- a/yudao-module-mall/yudao-module-trade-api/pom.xml +++ b/yudao-module-mall/yudao-module-trade-api/pom.xml @@ -5,7 +5,7 @@ cn.iocoder.cloud yudao-module-mall - 1.8.2-snapshot + ${revision} 4.0.0 yudao-module-trade-api diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImpl.java index 8eefb2a2e..d5317b544 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImpl.java @@ -5,7 +5,6 @@ import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.sms.core.client.SmsClient; -import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory; import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult; import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateCreateReqVO; @@ -54,7 +53,6 @@ public class SmsTemplateServiceImpl implements SmsTemplateService { @Resource private SmsChannelService smsChannelService; - @Override public Long createSmsTemplate(SmsTemplateCreateReqVO createReqVO) { // 校验短信渠道 diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java index b0331cff2..1009e9220 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; @@ -235,8 +236,8 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { AuthSocialLoginReqVO reqVO = randomPojo(AuthSocialLoginReqVO.class); // mock 方法(绑定的用户编号) Long userId = 1L; - when(socialUserService.getBindUserId(eq(UserTypeEnum.ADMIN.getValue()), eq(reqVO.getType()), - eq(reqVO.getCode()), eq(reqVO.getState()))).thenReturn(userId); + when(socialUserService.getSocialUser(eq(UserTypeEnum.ADMIN.getValue()), eq(reqVO.getType()), + eq(reqVO.getCode()), eq(reqVO.getState()))).thenReturn(new SocialUserRespDTO(randomString(), userId)); // mock(用户) AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(userId)); when(userService.getUser(eq(userId))).thenReturn(user); diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImplTest.java index 5aaf43b30..ad6b6e733 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImplTest.java @@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.sms.core.client.SmsClient; -import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory; import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult; import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; @@ -36,7 +35,7 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.when; @Import(SmsTemplateServiceImpl.class) public class SmsTemplateServiceImplTest extends BaseDbUnitTest { @@ -50,8 +49,6 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest { @MockBean private SmsChannelService smsChannelService; @MockBean - private SmsClientFactory smsClientFactory; - @MockBean private SmsClient smsClient; @Test @@ -82,7 +79,7 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest { }); when(smsChannelService.getSmsChannel(eq(channelDO.getId()))).thenReturn(channelDO); // mock 获得 API 短信模板成功 - when(smsClientFactory.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient); + when(smsChannelService.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient); when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(randomPojo(SmsCommonResult.class, SmsTemplateRespDTO.class, o -> o.setCode(GlobalErrorCodeConstants.SUCCESS.getCode()))); @@ -117,7 +114,7 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest { }); when(smsChannelService.getSmsChannel(eq(channelDO.getId()))).thenReturn(channelDO); // mock 获得 API 短信模板成功 - when(smsClientFactory.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient); + when(smsChannelService.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient); when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(randomPojo(SmsCommonResult.class, SmsTemplateRespDTO.class, o -> o.setCode(GlobalErrorCodeConstants.SUCCESS.getCode()))); From df2b1b45a41c11f989c2c899aff3c62534e33e36 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 24 Oct 2023 08:00:01 +0800 Subject: [PATCH 7/9] =?UTF-8?q?pay=EF=BC=9A=E5=90=8C=E6=AD=A5=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E5=8A=9F=E8=83=BD=E7=9A=84=E4=BB=A3=E7=A0=81=EF=BC=88?= =?UTF-8?q?=E9=92=B1=E5=8C=85=E3=80=81=E8=BD=AC=E8=B4=A6=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/pay/core/client/PayClient.java | 10 + .../pay/core/client/PayClientFactory.java | 10 + .../dto/transfer/PayTransferRespDTO.java | 96 ++++++ .../transfer/PayTransferUnifiedReqDTO.java | 66 ++++ .../core/client/impl/AbstractPayClient.java | 22 ++ .../core/client/impl/NonePayClientConfig.java | 31 ++ .../client/impl/PayClientFactoryImpl.java | 62 ++-- .../impl/alipay/AbstractAlipayPayClient.java | 111 ++++++- .../impl/alipay/AlipayAppPayClient.java | 1 - .../impl/alipay/AlipayBarPayClient.java | 19 +- .../client/impl/alipay/AlipayPcPayClient.java | 1 - .../client/impl/alipay/AlipayQrPayClient.java | 13 +- .../impl/alipay/AlipayWapPayClient.java | 1 - .../core/client/impl/mock/MockPayClient.java | 12 +- .../impl/weixin/AbstractWxPayClient.java | 6 + .../core/enums/channel/PayChannelEnum.java | 5 +- .../transfer/PayTransferStatusRespEnum.java | 41 +++ .../enums/transfer/PayTransferTypeEnum.java | 41 +++ .../impl/alipay/AbstractAlipayClientTest.java | 221 ++++++++++++++ .../impl/alipay/AlipayBarPayClientTest.java | 170 +++++++++++ .../impl/alipay/AlipayPcPayClientTest.java | 130 ++++++++ .../impl/alipay/AlipayQrPayClientTest.java | 200 ++++++++----- .../impl/alipay/AlipayWapPayClientTest.java | 110 +++++++ .../aftersale/AfterSaleServiceImpl.java | 2 +- .../order/TradeOrderUpdateServiceImpl.java | 4 +- .../module/pay/api/order/PayOrderApi.java | 11 +- .../module/pay/api/refund/PayRefundApi.java | 5 +- .../pay/api/transfer/PayTransferApi.java | 24 ++ .../transfer/dto/PayTransferCreateReqDTO.java | 43 +++ .../module/pay/enums/ErrorCodeConstants.java | 54 +++- .../enums/member/WalletOperateTypeEnum.java | 21 -- .../member/WalletTransactionGategoryEnum.java | 26 -- .../enums/transfer/PayTransferStatusEnum.java | 55 ++++ .../enums/transfer/PayTransferTypeEnum.java | 41 +++ yudao-module-pay/yudao-module-pay-biz/pom.xml | 5 + .../module/pay/api/order/PayOrderApiImpl.java | 16 +- .../pay/api/refund/PayRefundApiImpl.java | 13 +- .../pay/api/transfer/PayTransferApiImpl.java | 30 ++ .../admin/demo/PayDemoTransferController.java | 33 ++ .../transfer/PayDemoTransferCreateReqVO.java | 34 +++ .../admin/order/PayOrderController.java | 18 +- .../admin/order/vo/PayOrderDetailsRespVO.java | 8 +- .../admin/transfer/PayTransferController.java | 38 +++ .../transfer/vo/PayTransferSubmitReqVO.java | 25 ++ .../transfer/vo/PayTransferSubmitRespVO.java | 13 + .../admin/wallet/PayWalletController.java | 76 +++++ .../wallet/PayWalletRechargeController.java | 61 ++++ .../PayWalletRechargePackageController.java | 75 +++++ .../PayWalletTransactionController.java | 43 +++ .../WalletRechargePackageBaseVO.java | 31 ++ .../WalletRechargePackageCreateReqVO.java | 14 + .../WalletRechargePackagePageReqVO.java | 30 ++ .../WalletRechargePackageRespVO.java | 22 ++ .../WalletRechargePackageUpdateReqVO.java | 20 ++ .../PayWalletTransactionPageReqVO.java | 14 + .../PayWalletTransactionRespVO.java | 35 +++ .../wallet/vo/wallet/PayWalletBaseVO.java | 39 +++ .../wallet/vo/wallet/PayWalletPageReqVO.java | 31 ++ .../wallet/vo/wallet/PayWalletRespVO.java | 27 ++ .../wallet/vo/wallet/PayWalletUserReqVO.java | 16 + .../app/order/AppPayOrderController.java | 18 ++ .../app/wallet/AppPayWalletController.java | 44 +++ .../AppPayWalletRechargeController.java | 45 +++ ...AppPayWalletRechargePackageController.java | 35 +++ .../AppPayWalletTransactionController.java | 52 ++++ .../recharge/AppPayWalletPackageRespVO.java | 20 ++ .../AppPayWalletRechargeCreateReqVO.java | 25 ++ .../AppPayWalletRechargeCreateRespVO.java | 16 + .../AppPayWalletTransactionPageReqVO.java | 23 ++ .../AppPayWalletTransactionRespVO.java | 24 ++ .../wallet/vo/wallet/AppPayWalletRespVO.java | 19 ++ .../convert/transfer/PayTransferConvert.java | 20 ++ .../pay/convert/wallet/PayWalletConvert.java | 36 +++ .../wallet/PayWalletRechargeConvert.java | 19 ++ .../wallet/PayWalletTransactionConvert.java | 22 ++ .../wallet/WalletRechargePackageConvert.java | 28 ++ .../dataobject/demo/PayDemoTransferDO.java | 72 +++++ .../dal/dataobject/member/MemberWalletDO.java | 49 --- .../member/MemberWalletTransactionDO.java | 86 ------ .../dataobject/transfer/PayTransferDO.java | 106 +++++++ .../transfer/PayTransferExtensionDO.java | 68 +++++ .../dal/dataobject/wallet/PayWalletDO.java | 59 ++++ .../wallet/PayWalletRechargeDO.java | 116 ++++++++ .../wallet/PayWalletRechargePackageDO.java | 47 +++ .../wallet/PayWalletTransactionDO.java | 66 ++++ .../dal/mysql/demo/PayDemoTransferMapper.java | 10 + .../dal/mysql/member/MemberWalletMapper.java | 15 - .../member/MemberWalletTransactionMapper.java | 15 - .../transfer/PayTransferExtensionMapper.java | 26 ++ .../dal/mysql/transfer/PayTransferMapper.java | 22 ++ .../pay/dal/mysql/wallet/PayWalletMapper.java | 122 ++++++++ .../mysql/wallet/PayWalletRechargeMapper.java | 21 ++ .../PayWalletRechargePackageMapper.java | 27 ++ .../wallet/PayWalletTransactionMapper.java | 43 +++ .../pay/dal/redis/RedisKeyConstants.java | 2 +- .../pay/dal/redis/no/PayNoRedisDAO.java | 14 +- .../framework/pay/core/WalletPayClient.java | 184 ++++++++++++ .../pay/framework/pay/core/package-info.java | 4 - .../rpc/config/RpcConfiguration.java | 10 + .../pay/framework/rpc/package-info.java | 4 + .../config/SecurityConfiguration.java | 2 +- .../channel/PayChannelServiceImpl.java | 10 + .../service/demo/PayDemoOrderServiceImpl.java | 12 +- .../service/demo/PayDemoTransferService.java | 22 ++ .../demo/PayDemoTransferServiceImpl.java | 89 ++++++ .../pay/service/order/PayOrderService.java | 8 - .../service/order/PayOrderServiceImpl.java | 63 ++-- .../service/refund/PayRefundServiceImpl.java | 4 +- .../service/transfer/PayTransferService.java | 35 +++ .../transfer/PayTransferServiceImpl.java | 271 +++++++++++++++++ .../PayWalletRechargePackageService.java | 61 ++++ .../PayWalletRechargePackageServiceImpl.java | 106 +++++++ .../wallet/PayWalletRechargeService.java | 49 +++ .../wallet/PayWalletRechargeServiceImpl.java | 281 ++++++++++++++++++ .../pay/service/wallet/PayWalletService.java | 101 +++++++ .../service/wallet/PayWalletServiceImpl.java | 206 +++++++++++++ .../wallet/PayWalletTransactionService.java | 60 ++++ .../PayWalletTransactionServiceImpl.java | 71 +++++ .../bo/WalletTransactionCreateReqBO.java | 59 ++++ .../src/main/resources/application-local.yaml | 2 +- .../service/order/PayOrderServiceTest.java | 32 +- .../service/refund/PayRefundServiceTest.java | 6 +- 122 files changed, 5161 insertions(+), 455 deletions(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/NonePayClientConfig.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferStatusRespEnum.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/transfer/PayTransferTypeEnum.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java create mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java create mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletOperateTypeEnum.java delete mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletTransactionGategoryEnum.java create mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java create mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferTypeEnum.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoTransferController.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/transfer/PayDemoTransferCreateReqVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferSubmitReqVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferSubmitRespVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/transfer/PayTransferConvert.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/WalletRechargePackageConvert.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoTransferDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/member/MemberWalletDO.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/member/MemberWalletTransactionDO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferExtensionDO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoTransferMapper.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/member/MemberWalletMapper.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/member/MemberWalletTransactionMapper.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferExtensionMapper.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java delete mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/package-info.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/rpc/config/RpcConfiguration.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/rpc/package-info.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferService.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java index 4d163be55..18ae017d1 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java @@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; import java.util.Map; @@ -76,4 +78,12 @@ public interface PayClient { */ PayRefundRespDTO getRefund(String outTradeNo, String outRefundNo); + /** + * 调用渠道,进行转账 + * + * @param reqDTO 统一转账请求信息 + * @return 转账信息 + */ + PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO); + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientFactory.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientFactory.java index f6d0ca6b5..53f1a8c06 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientFactory.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientFactory.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.framework.pay.core.client; +import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; + /** * 支付客户端的工厂接口 * @@ -25,4 +27,12 @@ public interface PayClientFactory { void createOrUpdatePayClient(Long channelId, String channelCode, Config config); + /** + * 注册支付客户端 Class,用于模块中实现的 PayClient + * + * @param channel 支付渠道的编码的枚举 + * @param payClientClass 支付客户端 class + */ + void registerPayClientClass(PayChannelEnum channel, Class payClientClass); + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java new file mode 100644 index 000000000..400abb510 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java @@ -0,0 +1,96 @@ +package cn.iocoder.yudao.framework.pay.core.client.dto.transfer; + +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 统一转账 Response DTO + * + * @author jason + */ +@Data +public class PayTransferRespDTO { + + /** + * 转账状态 + * + * 关联 {@link PayTransferStatusRespEnum#getStatus()} + */ + private Integer status; + + /** + * 外部转账单号 + * + */ + private String outTransferNo; + + /** + * 支付渠道编号 + */ + private String channelOrderNo; + + /** + * 支付成功时间 + */ + private LocalDateTime successTime; + + /** + * 原始的返回结果 + */ + private Object rawData; + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + /** + * 调用渠道报错时,错误信息 + */ + private String channelErrorMsg; + + /** + * 创建【WAITING】状态的转账返回 + */ + public static PayTransferRespDTO waitingOf(String channelOrderNo, + String outTransferNo, Object rawData) { + PayTransferRespDTO respDTO = new PayTransferRespDTO(); + respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus(); + respDTO.channelOrderNo = channelOrderNo; + respDTO.outTransferNo = outTransferNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【CLOSED】状态的转账返回 + */ + public static PayTransferRespDTO closedOf(String channelErrorCode, String channelErrorMsg, + String outTransferNo, Object rawData) { + PayTransferRespDTO respDTO = new PayTransferRespDTO(); + respDTO.status = PayTransferStatusRespEnum.CLOSED.getStatus(); + respDTO.channelErrorCode = channelErrorCode; + respDTO.channelErrorMsg = channelErrorMsg; + // 相对通用的字段 + respDTO.outTransferNo = outTransferNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【SUCCESS】状态的转账返回 + */ + public static PayTransferRespDTO successOf(String channelTransferNo, LocalDateTime successTime, + String outTransferNo, Object rawData) { + PayTransferRespDTO respDTO = new PayTransferRespDTO(); + respDTO.status = PayTransferStatusRespEnum.SUCCESS.getStatus(); + respDTO.channelOrderNo = channelTransferNo; + respDTO.successTime = successTime; + // 相对通用的字段 + respDTO.outTransferNo = outTransferNo; + respDTO.rawData = rawData; + return respDTO; + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java new file mode 100644 index 000000000..17e47aa8b --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java @@ -0,0 +1,66 @@ +package cn.iocoder.yudao.framework.pay.core.client.dto.transfer; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Map; + +/** + * 统一转账 Request DTO + * + * @author jason + */ +@Data +public class PayTransferUnifiedReqDTO { + + /** + * 转账类型 + * + * 关联 {@link PayTransferTypeEnum#getType()} + */ + @NotNull(message = "转账类型不能为空") + @InEnum(PayTransferTypeEnum.class) + private Integer type; + + /** + * 用户 IP + */ + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + @NotEmpty(message = "外部转账单编号不能为空") + private String outTransferNo; + + /** + * 转账金额,单位:分 + */ + @NotNull(message = "转账金额不能为空") + @Min(value = 1, message = "转账金额必须大于零") + private Integer price; + + /** + * 转账标题 + */ + @NotEmpty(message = "转账标题不能为空") + @Length(max = 128, message = "转账标题不能超过 128") + private String title; + + /** + * 收款方信息。 + * + * 转账类型 {@link #type} 不同,收款方信息不同 + */ + @NotEmpty(message = "收款方信息 不能为空") + private Map payeeInfo; + + /** + * 支付渠道的额外参数 + */ + private Map channelExtras; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java index 4797336b1..49114fb5b 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java @@ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.exception.PayException; import lombok.extern.slf4j.Slf4j; @@ -181,6 +183,26 @@ public abstract class AbstractPayClient implemen protected abstract PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws Throwable; + @Override + public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { + ValidationUtils.validate(reqDTO); + PayTransferRespDTO resp; + try{ + resp = doUnifiedTransfer(reqDTO); + }catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + // 系统异常,则包装成 PayException 异常抛出 + log.error("[unifiedTransfer][客户端({}) request({}) 发起转账异常]", + getId(), toJsonString(reqDTO), ex); + throw buildPayException(ex); + } + return resp; + } + + protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) + throws Throwable; + // ========== 各种工具方法 ========== private PayException buildPayException(Throwable ex) { diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/NonePayClientConfig.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/NonePayClientConfig.java new file mode 100644 index 000000000..48319036c --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/NonePayClientConfig.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.framework.pay.core.client.impl; + +import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; +import lombok.Data; + +import javax.validation.Validator; + +/** + * 无需任何配置 PayClientConfig 实现类 + * + * @author jason + */ +@Data +public class NonePayClientConfig implements PayClientConfig { + + /** + * 配置名称 + *

+ * 如果不加任何属性,JsonUtils.parseObject2 解析会报错,所以暂时加个名称 + */ + private String name; + + public NonePayClientConfig(){ + this.name = "none-config"; + } + + @Override + public void validate(Validator validator) { + // 无任何配置不需要校验 + } +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java index 121aeb087..0b39587ab 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java @@ -1,19 +1,22 @@ package cn.iocoder.yudao.framework.pay.core.client.impl; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ReflectUtil; import cn.iocoder.yudao.framework.pay.core.client.PayClient; import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*; import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClient; -import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig; import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.*; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import lombok.extern.slf4j.Slf4j; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import static cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum.*; + /** * 支付客户端的工厂实现类 * @@ -24,10 +27,38 @@ public class PayClientFactoryImpl implements PayClientFactory { /** * 支付客户端 Map + * * key:渠道编号 */ private final ConcurrentMap> clients = new ConcurrentHashMap<>(); + /** + * 支付客户端 Class Map + */ + private final Map> clientClass = new ConcurrentHashMap<>(); + + public PayClientFactoryImpl() { + // 微信支付客户端 + clientClass.put(WX_PUB, WxPubPayClient.class); + clientClass.put(WX_LITE, WxLitePayClient.class); + clientClass.put(WX_APP, WxAppPayClient.class); + clientClass.put(WX_BAR, WxBarPayClient.class); + clientClass.put(WX_NATIVE, WxNativePayClient.class); + // 支付包支付客户端 + clientClass.put(ALIPAY_WAP, AlipayWapPayClient.class); + clientClass.put(ALIPAY_QR, AlipayQrPayClient.class); + clientClass.put(ALIPAY_APP, AlipayAppPayClient.class); + clientClass.put(ALIPAY_PC, AlipayPcPayClient.class); + clientClass.put(ALIPAY_BAR, AlipayBarPayClient.class); + // Mock 支付客户端 + clientClass.put(MOCK, MockPayClient.class); + } + + @Override + public void registerPayClientClass(PayChannelEnum channel, Class payClientClass) { + clientClass.put(channel, payClientClass); + } + @Override public PayClient getPayClient(Long channelId) { AbstractPayClient client = clients.get(channelId); @@ -52,30 +83,13 @@ public class PayClientFactoryImpl implements PayClientFactory { } @SuppressWarnings("unchecked") - private AbstractPayClient createPayClient( - Long channelId, String channelCode, Config config) { + private AbstractPayClient createPayClient(Long channelId, String channelCode, + Config config) { PayChannelEnum channelEnum = PayChannelEnum.getByCode(channelCode); - Assert.notNull(channelEnum, String.format("支付渠道(%s) 为空", channelEnum)); - // 创建客户端 - switch (channelEnum) { - // 微信支付 - case WX_PUB: return (AbstractPayClient) new WxPubPayClient(channelId, (WxPayClientConfig) config); - case WX_LITE: return (AbstractPayClient) new WxLitePayClient(channelId, (WxPayClientConfig) config); - case WX_APP: return (AbstractPayClient) new WxAppPayClient(channelId, (WxPayClientConfig) config); - case WX_BAR: return (AbstractPayClient) new WxBarPayClient(channelId, (WxPayClientConfig) config); - case WX_NATIVE: return (AbstractPayClient) new WxNativePayClient(channelId, (WxPayClientConfig) config); - // 支付宝支付 - case ALIPAY_WAP: return (AbstractPayClient) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config); - case ALIPAY_QR: return (AbstractPayClient) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config); - case ALIPAY_APP: return (AbstractPayClient) new AlipayAppPayClient(channelId, (AlipayPayClientConfig) config); - case ALIPAY_PC: return (AbstractPayClient) new AlipayPcPayClient(channelId, (AlipayPayClientConfig) config); - case ALIPAY_BAR: return (AbstractPayClient) new AlipayBarPayClient(channelId, (AlipayPayClientConfig) config); - // 其它支付 - case MOCK: return (AbstractPayClient) new MockPayClient(channelId, (MockPayClientConfig) config); - } - // 创建失败,错误日志 + 抛出异常 - log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config); - throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", config)); + Assert.notNull(channelEnum, String.format("支付渠道(%s) 为空", channelCode)); + Class payClientClass = clientClass.get(channelEnum); + Assert.notNull(payClientClass, String.format("支付渠道(%s) Class 为空", channelCode)); + return (AbstractPayClient) ReflectUtil.newInstance(payClientClass, channelId, config); } } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java index e92544729..cb8a0df6e 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java @@ -6,27 +6,32 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum; import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayConfig; import com.alipay.api.AlipayResponse; import com.alipay.api.DefaultAlipayClient; -import com.alipay.api.domain.AlipayTradeFastpayRefundQueryModel; -import com.alipay.api.domain.AlipayTradeQueryModel; -import com.alipay.api.domain.AlipayTradeRefundModel; +import com.alipay.api.domain.*; import com.alipay.api.internal.util.AlipaySignature; +import com.alipay.api.request.AlipayFundTransUniTransferRequest; import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest; import com.alipay.api.request.AlipayTradeQueryRequest; import com.alipay.api.request.AlipayTradeRefundRequest; +import com.alipay.api.response.AlipayFundTransUniTransferResponse; import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse; import com.alipay.api.response.AlipayTradeQueryResponse; import com.alipay.api.response.AlipayTradeRefundResponse; +import lombok.Getter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -38,6 +43,9 @@ import java.util.Objects; import java.util.function.Supplier; import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER; +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; +import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE; /** * 支付宝抽象类,实现支付宝统一的接口、以及部分实现(退款) @@ -47,6 +55,7 @@ import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER; @Slf4j public abstract class AbstractAlipayPayClient extends AbstractPayClient { + @Getter // 仅用于单测场景 protected DefaultAlipayClient client; public AbstractAlipayPayClient(Long channelId, String channelCode, AlipayPayClientConfig config) { @@ -103,16 +112,20 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient) () -> { + Assert.notNull(status, () -> { throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody())); }); return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()), @@ -146,9 +159,18 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient { +public class MockPayClient extends AbstractPayClient { private static final String MOCK_RESP_SUCCESS_DATA = "MOCK_SUCCESS"; - public MockPayClient(Long channelId, MockPayClientConfig config) { + public MockPayClient(Long channelId, NonePayClientConfig config) { super(channelId, PayChannelEnum.MOCK.getCode(), config); } @@ -63,4 +66,9 @@ public class MockPayClient extends AbstractPayClient { throw new UnsupportedOperationException("模拟支付无支付回调"); } + @Override + protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { + throw new UnsupportedOperationException("待实现"); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java index 87b9c4bc2..f4f326a65 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java @@ -12,6 +12,8 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; @@ -425,6 +427,10 @@ public abstract class AbstractWxPayClient extends AbstractPayClient item.getType().equals(type), values()); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java new file mode 100644 index 000000000..2d220079e --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java @@ -0,0 +1,221 @@ +package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.util.RandomUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; +import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.exception.PayException; +import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import com.alipay.api.AlipayApiException; +import com.alipay.api.DefaultAlipayClient; +import com.alipay.api.DefaultSigner; +import com.alipay.api.domain.AlipayTradeRefundModel; +import com.alipay.api.request.AlipayTradeRefundRequest; +import com.alipay.api.response.AlipayTradeRefundResponse; +import lombok.Setter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; + +import javax.validation.ConstraintViolationException; +import java.util.Date; + +import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_PUBLIC_KEY; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.when; + +/** + * 支付宝 Client 的测试基类 + * + * @author jason + */ +public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest { + + protected AlipayPayClientConfig config = randomPojo(AlipayPayClientConfig.class, o -> { + o.setServerUrl(randomURL()); + o.setPrivateKey(randomString()); + o.setMode(MODE_PUBLIC_KEY); + o.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT); + o.setAppCertContent(""); + o.setAlipayPublicCertContent(""); + o.setRootCertContent(""); + }); + + @Mock + protected DefaultAlipayClient defaultAlipayClient; + + @Setter + private AbstractAlipayPayClient client; + + /** + * 子类需要实现该方法. 设置 client 的具体实现 + */ + @BeforeEach + public abstract void setUp(); + + @Test + @DisplayName("支付宝 Client 初始化") + public void testDoInit() { + // 调用 + client.doInit(); + // 断言 + DefaultAlipayClient realClient = client.getClient(); + assertNotSame(defaultAlipayClient, realClient); + assertInstanceOf(DefaultSigner.class, realClient.getSigner()); + assertEquals(config.getPrivateKey(), ((DefaultSigner) realClient.getSigner()).getPrivateKey()); + } + + @Test + @DisplayName("支付宝 Client 统一退款:成功") + public void testUnifiedRefund_success() throws AlipayApiException { + // mock 方法 + String notifyUrl = randomURL(); + Date refundTime = randomDate(); + String outRefundNo = randomString(); + String outTradeNo = randomString(); + Integer refundAmount = randomInteger(); + AlipayTradeRefundResponse response = randomPojo(AlipayTradeRefundResponse.class, o -> { + o.setSubCode(""); + o.setGmtRefundPay(refundTime); + }); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { + assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel()); + AlipayTradeRefundModel bizModel = (AlipayTradeRefundModel) request.getBizModel(); + assertEquals(outRefundNo, bizModel.getOutRequestNo()); + assertEquals(outTradeNo, bizModel.getOutTradeNo()); + assertEquals(String.valueOf(refundAmount / 100.0), bizModel.getRefundAmount()); + return true; + }))).thenReturn(response); + // 准备请求参数 + PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> { + o.setOutRefundNo(outRefundNo); + o.setOutTradeNo(outTradeNo); + o.setNotifyUrl(notifyUrl); + o.setRefundPrice(refundAmount); + }); + + // 调用 + PayRefundRespDTO resp = client.unifiedRefund(refundReqDTO); + // 断言 + assertEquals(PayRefundStatusRespEnum.SUCCESS.getStatus(), resp.getStatus()); + assertEquals(outRefundNo, resp.getOutRefundNo()); + assertNull(resp.getChannelRefundNo()); + assertEquals(LocalDateTimeUtil.of(refundTime), resp.getSuccessTime()); + assertSame(response, resp.getRawData()); + assertNull(resp.getChannelErrorCode()); + assertNull(resp.getChannelErrorMsg()); + } + + @Test + @DisplayName("支付宝 Client 统一退款:渠道返回失败") + public void test_unified_refund_channel_failed() throws AlipayApiException { + // mock 方法 + String notifyUrl = randomURL(); + String subCode = randomString(); + String subMsg = randomString(); + AlipayTradeRefundResponse response = randomPojo(AlipayTradeRefundResponse.class, o -> { + o.setSubCode(subCode); + o.setSubMsg(subMsg); + }); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { + assertInstanceOf(AlipayTradeRefundModel.class, request.getBizModel()); + return true; + }))).thenReturn(response); + // 准备请求参数 + String outRefundNo = randomString(); + String outTradeNo = randomString(); + PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> { + o.setOutRefundNo(outRefundNo); + o.setOutTradeNo(outTradeNo); + o.setNotifyUrl(notifyUrl); + }); + + // 调用 + PayRefundRespDTO resp = client.unifiedRefund(refundReqDTO); + // 断言 + assertEquals(PayRefundStatusRespEnum.FAILURE.getStatus(), resp.getStatus()); + assertEquals(outRefundNo, resp.getOutRefundNo()); + assertNull(resp.getChannelRefundNo()); + assertNull(resp.getSuccessTime()); + assertSame(response, resp.getRawData()); + assertEquals(subCode, resp.getChannelErrorCode()); + assertEquals(subMsg, resp.getChannelErrorMsg()); + } + + @Test + @DisplayName("支付宝 Client 统一退款:参数校验不通过") + public void testUnifiedRefund_paramInvalidate() { + // 准备请求参数 + String notifyUrl = randomURL(); + PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> { + o.setOutTradeNo(""); + o.setNotifyUrl(notifyUrl); + }); + + // 调用,并断言 + assertThrows(ConstraintViolationException.class, () -> client.unifiedRefund(refundReqDTO)); + } + + @Test + @DisplayName("支付宝 Client 统一退款:抛出业务异常") + public void testUnifiedRefund_throwServiceException() throws AlipayApiException { + // mock 方法 + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> true))) + .thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR)); + // 准备请求参数 + String notifyUrl = randomURL(); + PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> o.setNotifyUrl(notifyUrl)); + + // 调用,并断言 + assertThrows(ServiceException.class, () -> client.unifiedRefund(refundReqDTO)); + } + + @Test + @DisplayName("支付宝 Client 统一退款:抛出系统异常") + public void testUnifiedRefund_throwPayException() throws AlipayApiException { + // mock 方法 + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> true))) + .thenThrow(new RuntimeException("系统异常")); + // 准备请求参数 + String notifyUrl = randomURL(); + PayRefundUnifiedReqDTO refundReqDTO = randomPojo(PayRefundUnifiedReqDTO.class, o -> o.setNotifyUrl(notifyUrl)); + + // 调用,并断言 + assertThrows(PayException.class, () -> client.unifiedRefund(refundReqDTO)); + } + + @Test + @DisplayName("支付宝 Client 统一下单:参数校验不通过") + public void testUnifiedOrder_paramInvalidate() { + // 准备请求参数 + String outTradeNo = randomString(); + String notifyUrl = randomURL(); + PayOrderUnifiedReqDTO reqDTO = randomPojo(PayOrderUnifiedReqDTO.class, o -> { + o.setOutTradeNo(outTradeNo); + o.setNotifyUrl(notifyUrl); + }); + + // 调用,并断言 + assertThrows(ConstraintViolationException.class, () -> client.unifiedOrder(reqDTO)); + } + + protected PayOrderUnifiedReqDTO buildOrderUnifiedReqDTO(String notifyUrl, String outTradeNo, Integer price) { + return randomPojo(PayOrderUnifiedReqDTO.class, o -> { + o.setOutTradeNo(outTradeNo); + o.setNotifyUrl(notifyUrl); + o.setPrice(price); + o.setSubject(RandomUtil.randomString(32)); + o.setBody(RandomUtil.randomString(32)); + }); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java new file mode 100644 index 000000000..47f10081c --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClientTest.java @@ -0,0 +1,170 @@ +package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; +import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradePayModel; +import com.alipay.api.request.AlipayTradePayRequest; +import com.alipay.api.response.AlipayTradePayResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.CLOSED; +import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.WAITING; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.when; + +/** + * {@link AlipayBarPayClient} 单元测试 + * + * @author jason + */ +public class AlipayBarPayClientTest extends AbstractAlipayClientTest { + + @InjectMocks + private AlipayBarPayClient client = new AlipayBarPayClient(randomLongId(), config); + + @Override + @BeforeEach + public void setUp() { + setClient(client); + } + + @Test + @DisplayName("支付宝条码支付:非免密码支付下单成功") + public void testUnifiedOrder_success() throws AlipayApiException { + // mock 方法 + String outTradeNo = randomString(); + String notifyUrl = randomURL(); + Integer price = randomInteger(); + String authCode = randomString(); + AlipayTradePayResponse response = randomPojo(AlipayTradePayResponse.class, o -> o.setSubCode("")); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { + assertInstanceOf(AlipayTradePayModel.class, request.getBizModel()); + assertEquals(notifyUrl, request.getNotifyUrl()); + AlipayTradePayModel model = (AlipayTradePayModel) request.getBizModel(); + assertEquals(outTradeNo, model.getOutTradeNo()); + assertEquals(String.valueOf(price / 100.0), model.getTotalAmount()); + assertEquals(authCode, model.getAuthCode()); + return true; + }))).thenReturn(response); + // 准备请求参数 + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); + Map extraParam = new HashMap<>(); + extraParam.put("auth_code", authCode); + reqDTO.setChannelExtras(extraParam); + + // 调用方法 + PayOrderRespDTO resp = client.unifiedOrder(reqDTO); + // 断言 + assertEquals(WAITING.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + assertNull(resp.getChannelOrderNo()); + assertNull(resp.getChannelUserId()); + assertNull(resp.getSuccessTime()); + assertEquals(PayOrderDisplayModeEnum.BAR_CODE.getMode(), resp.getDisplayMode()); + assertEquals("", resp.getDisplayContent()); + assertSame(response, resp.getRawData()); + assertNull(resp.getChannelErrorCode()); + assertNull(resp.getChannelErrorMsg()); + } + + @Test + @DisplayName("支付宝条码支付:免密码支付下单成功") + public void testUnifiedOrder_code10000Success() throws AlipayApiException { + // mock 方法 + String outTradeNo = randomString(); + String channelNo = randomString(); + String channelUserId = randomString(); + Date payTime = randomDate(); + AlipayTradePayResponse response = randomPojo(AlipayTradePayResponse.class, o -> { + o.setSubCode(""); + o.setCode("10000"); + o.setOutTradeNo(outTradeNo); + o.setTradeNo(channelNo); + o.setBuyerUserId(channelUserId); + o.setGmtPayment(payTime); + }); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> true))) + .thenReturn(response); + // 准备请求参数 + String authCode = randomString(); + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger()); + Map extraParam = new HashMap<>(); + extraParam.put("auth_code", authCode); + reqDTO.setChannelExtras(extraParam); + + // 下单请求 + PayOrderRespDTO resp = client.unifiedOrder(reqDTO); + // 断言 + assertEquals(PayOrderStatusRespEnum.SUCCESS.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + assertEquals(channelNo, resp.getChannelOrderNo()); + assertEquals(channelUserId, resp.getChannelUserId()); + assertEquals(LocalDateTimeUtil.of(payTime), resp.getSuccessTime()); + assertEquals(PayOrderDisplayModeEnum.BAR_CODE.getMode(), resp.getDisplayMode()); + assertEquals("", resp.getDisplayContent()); + assertSame(response, resp.getRawData()); + assertNull(resp.getChannelErrorCode()); + assertNull(resp.getChannelErrorMsg()); + } + + @Test + @DisplayName("支付宝条码支付:没有传条码") + public void testUnifiedOrder_emptyAuthCode() { + // 准备参数 + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger()); + + // 调用,并断言 + assertThrows(ServiceException.class, () -> client.unifiedOrder(reqDTO)); + } + + @Test + @DisplayName("支付宝条码支付:渠道返回失败") + public void test_unified_order_channel_failed() throws AlipayApiException { + // mock 方法 + String subCode = randomString(); + String subMsg = randomString(); + AlipayTradePayResponse response = randomPojo(AlipayTradePayResponse.class, o -> { + o.setSubCode(subCode); + o.setSubMsg(subMsg); + }); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> true))) + .thenReturn(response); + // 准备请求参数 + String authCode = randomString(); + String outTradeNo = randomString(); + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger()); + Map extraParam = new HashMap<>(); + extraParam.put("auth_code", authCode); + reqDTO.setChannelExtras(extraParam); + + // 调用方法 + PayOrderRespDTO resp = client.unifiedOrder(reqDTO); + // 断言 + assertEquals(CLOSED.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + assertNull(resp.getChannelOrderNo()); + assertNull(resp.getChannelUserId()); + assertNull(resp.getSuccessTime()); + assertNull(resp.getDisplayMode()); + assertNull(resp.getDisplayContent()); + assertSame(response, resp.getRawData()); + assertEquals(subCode, resp.getChannelErrorCode()); + assertEquals(subMsg, resp.getChannelErrorMsg()); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java new file mode 100644 index 000000000..a6b3d8743 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java @@ -0,0 +1,130 @@ +package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; + +import cn.hutool.http.Method; +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; +import com.alipay.api.AlipayApiException; +import com.alipay.api.request.AlipayTradePagePayRequest; +import com.alipay.api.response.AlipayTradePagePayResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; + +import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.CLOSED; +import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.WAITING; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +/** + * {@link AlipayPcPayClient} 单元测试 + * + * @author jason + */ +public class AlipayPcPayClientTest extends AbstractAlipayClientTest { + + @InjectMocks + private AlipayPcPayClient client = new AlipayPcPayClient(randomLongId(), config); + + @Override + @BeforeEach + public void setUp() { + setClient(client); + } + + @Test + @DisplayName("支付宝 PC 网站支付:URL Display Mode 下单成功") + public void testUnifiedOrder_urlSuccess() throws AlipayApiException { + // mock 方法 + String notifyUrl = randomURL(); + AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> o.setSubCode("")); + when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> true), + eq(Method.GET.name()))).thenReturn(response); + // 准备请求参数 + String outTradeNo = randomString(); + Integer price = randomInteger(); + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); + reqDTO.setDisplayMode(null); + + // 调用 + PayOrderRespDTO resp = client.unifiedOrder(reqDTO); + // 断言 + assertEquals(WAITING.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + assertNull(resp.getChannelOrderNo()); + assertNull(resp.getChannelUserId()); + assertNull(resp.getSuccessTime()); + assertEquals(PayOrderDisplayModeEnum.URL.getMode(), resp.getDisplayMode()); + assertEquals(response.getBody(), resp.getDisplayContent()); + assertSame(response, resp.getRawData()); + assertNull(resp.getChannelErrorCode()); + assertNull(resp.getChannelErrorMsg()); + } + + @Test + @DisplayName("支付宝 PC 网站支付:Form Display Mode 下单成功") + public void testUnifiedOrder_formSuccess() throws AlipayApiException { + // mock 方法 + String notifyUrl = randomURL(); + AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> o.setSubCode("")); + when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> true), + eq(Method.POST.name()))).thenReturn(response); + // 准备请求参数 + String outTradeNo = randomString(); + Integer price = randomInteger(); + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); + reqDTO.setDisplayMode(PayOrderDisplayModeEnum.FORM.getMode()); + + // 调用 + PayOrderRespDTO resp = client.unifiedOrder(reqDTO); + // 断言 + assertEquals(WAITING.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + assertNull(resp.getChannelOrderNo()); + assertNull(resp.getChannelUserId()); + assertNull(resp.getSuccessTime()); + assertEquals(PayOrderDisplayModeEnum.FORM.getMode(), resp.getDisplayMode()); + assertEquals(response.getBody(), resp.getDisplayContent()); + assertSame(response, resp.getRawData()); + assertNull(resp.getChannelErrorCode()); + assertNull(resp.getChannelErrorMsg()); + } + + @Test + @DisplayName("支付宝 PC 网站支付:渠道返回失败") + public void testUnifiedOrder_channelFailed() throws AlipayApiException { + // mock 方法 + String subCode = randomString(); + String subMsg = randomString(); + AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> { + o.setSubCode(subCode); + o.setSubMsg(subMsg); + }); + when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> true), + eq(Method.GET.name()))).thenReturn(response); + // 准备请求参数 + String outTradeNo = randomString(); + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger()); + reqDTO.setDisplayMode(PayOrderDisplayModeEnum.URL.getMode()); + + // 调用 + PayOrderRespDTO resp = client.unifiedOrder(reqDTO); + // 断言 + assertEquals(CLOSED.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + assertNull(resp.getChannelOrderNo()); + assertNull(resp.getChannelUserId()); + assertNull(resp.getSuccessTime()); + assertNull(resp.getDisplayMode()); + assertNull(resp.getDisplayContent()); + assertSame(response, resp.getRawData()); + assertEquals(subCode, resp.getChannelErrorCode()); + assertEquals(subMsg, resp.getChannelErrorMsg()); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java index 0eb354cd0..c7e1eb33f 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java @@ -1,99 +1,147 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; -import cn.hutool.core.util.ReflectUtil; + +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; +import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.framework.pay.core.client.exception.PayException; +import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; import com.alipay.api.AlipayApiException; -import com.alipay.api.DefaultAlipayClient; import com.alipay.api.request.AlipayTradePrecreateRequest; import com.alipay.api.response.AlipayTradePrecreateResponse; -import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatcher; import org.mockito.InjectMocks; -import org.mockito.Mock; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotSame; +import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.CLOSED; +import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.WAITING; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.when; -public class AlipayQrPayClientTest extends BaseMockitoUnitTest { - - private static final String SERVER_URL_SANDBOX = "https://openapi.alipaydev.com/gateway.do"; - - private final AlipayPayClientConfig config = new AlipayPayClientConfig() - .setAppId("2021000118634035") - .setServerUrl(SERVER_URL_SANDBOX) - .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT) - // TODO @tina:key 可以随机就好,简洁一点哈。 - .setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHsEV1cDupwJ" + - "v890x84qbppUtRIfhaKSwSVN0thCcsDCaAsGR5MZslDkO8NCT9V4r2SVXjyY7eJUZlZd1M0C8T" + - "01Tg4UOx5LUbic0O3A1uJMy6V1n9IyYwbAW3AEZhBd5bSbPgrqvmv3NeWSTQT6Anxnllf+2iDH" + - "6zyA2fPl7cYyQtbZoDJQFGqr4F+cGh2R6akzRKNoBkAeMYwoY6es2lX8sJxCVPWUmxNUoL3tScw" + - "lSpd7Bxw0q9c/X01jMwuQ0+Va358zgFiGERTE6yD01eu40OBDXOYO3z++y+TAYHlQQ2toMO63tr" + - "epo88X3xV3R44/1DH+k2pAm2IF5ixiLrAgMBAAECggEAPx3SoXcseaD7rmcGcE0p4SMfbsUDdk" + - "USmBBbtfF0GzwnqNLkWa+mgE0rWt9SmXngTQH97vByAYmLPl1s3G82ht1V7Sk7yQMe74lhFllr" + - "8eEyTjeVx3dTK1EEM4TwN+936DTXdFsr4TELJEcJJdD0KaxcCcfBLRDs2wnitEFZ9N+GoZybVmY8w" + - "0e0MI7PLObUZ2l0X4RurQnfG9ZxjXjC7PkeMVv7cGGylpNFi3BbvkRhdhLPDC2E6wqnr9e7zk+hiENi" + - "vAezXrtxtwKovzCtnWJ1r0IO14Rh47H509Ic0wFnj+o5YyUL4LdmpL7yaaH6fM7zcSLFjNZPHvZCKPw" + - "YcQKBgQDQFho98QvnL8ex4v6cry4VitGpjSXm1qP3vmMQk4rTsn8iPWtcxPjqGEqOQJjdi4Mi0VZKQO" + - "LFwlH0kl95wNrD/isJ4O1yeYfX7YAXApzHqYNINzM79HemO3Yx1qLMW3okRFJ9pPRzbQ9qkTpsaegsm" + - "yX316zOBhzGRYjKbutTYwKBgQCm7phr9XdFW5Vh+XR90mVs483nrLmMiDKg7YKxSLJ8amiDjzPejCn7i9" + - "5Hah08P+2MIZLIPbh2VLacczR6ltRRzN5bg5etFuqSgfkuHyxpoDmpjbe08+Q2h8JBYqcC5Nhv1AKU4iOU" + - "hVLHo/FBAQliMcGc/J3eiYTFC7EsNx382QKBgClb20doe7cttgFTXswBvaUmfFm45kmla924B7SpvrQpDD" + - "/f+VDtDZRp05fGmxuduSjYdtA3aVtpLiTwWu22OUUvZZqHDGruYOO4Hvdz23mL5b4ayqImCwoNU4bAZIc9v1" + - "8p/UNf3/55NNE3oGcf/bev9rH2OjCQ4nM+Ktwhg8CFAoGACSgvbkShzUkv0ZcIf9ppu+ZnJh1AdGgINvGwaJ" + - "8vQ0nm/8h8NOoFZ4oNoGc+wU5Ubops7dUM6FjPR5e+OjdJ4E7Xp7d5O4J1TaIZlCEbo5OpdhaTDDcQvrkFu+Z4e" + - "N0qzj+YAKjDAOOrXc4tbr5q0FsgXscwtcNfaBuzFVTUrUkCgYEAwzPnMNhWG3zOWLUs2QFA2GP4Y+J8cpUYfj6p" + - "bKKzeLwyG9qBwF1NJpN8m+q9q7V9P2LY+9Lp9e1mGsGeqt5HMEA3P6vIpcqLJLqE/4PBLLRzfccTcmqb1m71+erx" + - "TRhHBRkGS+I7dZEb3olQfnS1Y1tpMBxiwYwR3LW4oXuJwj8=") - .setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnq90KnF4dTnlzzmxpujbI05OYqi5WxAS6cL0" + - "gnZFv2gK51HExF8v/BaP7P979PhFMgWTqmOOI+Dtno5s+yD09XTY1WkshbLk6i4g2Xlr8fyW9ODnkU88RI2w9UdPhQU4cPPwBN" + - "lrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZ" + - "ikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB"); +/** + * {@link AlipayQrPayClient} 单元测试 + * + * @author jason + */ +public class AlipayQrPayClientTest extends AbstractAlipayClientTest { @InjectMocks - AlipayQrPayClient client = new AlipayQrPayClient(10L,config); + private AlipayQrPayClient client = new AlipayQrPayClient(randomLongId(), config); - @Mock - private DefaultAlipayClient defaultAlipayClient; - - @Test - public void testDoInit(){ - client.doInit(); - assertNotSame(defaultAlipayClient, ReflectUtil.getFieldValue(client, "defaultAlipayClient")); + @BeforeEach + public void setUp() { + setClient(client); } @Test - @Disabled // TODO 芋艿:临时禁用 - public void create() throws AlipayApiException { - // TODO @tina:参数可以尽量随机一点,使用随机方法。这样的好处是,避免对固定参数的依赖,导致可能仅仅满足固定参数的结果 - // 这里,设置可以直接随机整个对象。 - Long shopOrderId = System.currentTimeMillis(); - PayOrderUnifiedReqDTO reqDTO=new PayOrderUnifiedReqDTO(); - reqDTO.setOutTradeNo(String.valueOf(System.currentTimeMillis())); - reqDTO.setPrice(1); - reqDTO.setBody("内容:" + shopOrderId); - reqDTO.setSubject("标题:"+shopOrderId); - String notify="http://niubi.natapp1.cc/api/pay/order/notify"; - reqDTO.setNotifyUrl(notify); - - AlipayTradePrecreateResponse response=randomPojo(AlipayTradePrecreateResponse.class,o->o.setQrCode("success")); - - when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request ->{ - assertEquals(notify,request.getNotifyUrl()); + @DisplayName("支付宝扫描支付:下单成功") + public void testUnifiedOrder_success() throws AlipayApiException { + // mock 方法 + String notifyUrl = randomURL(); + String qrCode = randomString(); + Integer price = randomInteger(); + AlipayTradePrecreateResponse response = randomPojo(AlipayTradePrecreateResponse.class, o -> { + o.setQrCode(qrCode); + o.setSubCode(""); + }); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { + assertEquals(notifyUrl, request.getNotifyUrl()); return true; }))).thenReturn(response); + // 准备请求参数 + String outTradeNo = randomString(); + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); - -// PayCommonResult result = client.doUnifiedOrder(reqDTO); -// // 断言 -// assertEquals(response.getCode(), result.getApiCode()); -// assertEquals(response.getMsg(), result.getApiMsg()); -// // TODO @tina:这个断言木有过? -// assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode()); -// assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg()); - + // 调用 + PayOrderRespDTO resp = client.unifiedOrder(reqDTO); + // 断言 + assertEquals(WAITING.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + assertNull(resp.getChannelOrderNo()); + assertNull(resp.getChannelUserId()); + assertNull(resp.getSuccessTime()); + assertEquals(PayOrderDisplayModeEnum.QR_CODE.getMode(), resp.getDisplayMode()); + assertEquals(response.getQrCode(), resp.getDisplayContent()); + assertSame(response, resp.getRawData()); + assertNull(resp.getChannelErrorCode()); + assertNull(resp.getChannelErrorMsg()); } + + @Test + @DisplayName("支付宝扫描支付:渠道返回失败") + public void testUnifiedOrder_channelFailed() throws AlipayApiException { + // mock 方法 + String notifyUrl = randomURL(); + String subCode = randomString(); + String subMsg = randomString(); + Integer price = randomInteger(); + AlipayTradePrecreateResponse response = randomPojo(AlipayTradePrecreateResponse.class, o -> { + o.setSubCode(subCode); + o.setSubMsg(subMsg); + }); + // mock + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { + assertEquals(notifyUrl, request.getNotifyUrl()); + return true; + }))).thenReturn(response); + // 准备请求参数 + String outTradeNo = randomString(); + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); + + // 调用 + PayOrderRespDTO resp = client.unifiedOrder(reqDTO); + // 断言 + assertEquals(CLOSED.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + assertNull(resp.getChannelOrderNo()); + assertNull(resp.getChannelUserId()); + assertNull(resp.getSuccessTime()); + assertNull(resp.getDisplayMode()); + assertNull(resp.getDisplayContent()); + assertSame(response, resp.getRawData()); + assertEquals(subCode, resp.getChannelErrorCode()); + assertEquals(subMsg, resp.getChannelErrorMsg()); + } + + @Test + @DisplayName("支付宝扫描支付, 抛出系统异常") + public void testUnifiedOrder_throwPayException() throws AlipayApiException { + // mock 方法 + String outTradeNo = randomString(); + String notifyUrl = randomURL(); + Integer price = randomInteger(); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { + assertEquals(notifyUrl, request.getNotifyUrl()); + return true; + }))).thenThrow(new RuntimeException("系统异常")); + // 准备请求参数 + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo,price); + + // 调用,并断言 + assertThrows(PayException.class, () -> client.unifiedOrder(reqDTO)); + } + + @Test + @DisplayName("支付宝 Client 统一下单:抛出业务异常") + public void testUnifiedOrder_throwServiceException() throws AlipayApiException { + // mock 方法 + String outTradeNo = randomString(); + String notifyUrl = randomURL(); + Integer price = randomInteger(); + when(defaultAlipayClient.execute(argThat((ArgumentMatcher) request -> { + assertEquals(notifyUrl, request.getNotifyUrl()); + return true; + }))).thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR)); + // 准备请求参数 + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); + + // 调用,并断言 + assertThrows(ServiceException.class, () -> client.unifiedOrder(reqDTO)); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java new file mode 100644 index 000000000..cfe3001a5 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java @@ -0,0 +1,110 @@ +package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; + +import cn.hutool.http.Method; +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; +import com.alipay.api.AlipayApiException; +import com.alipay.api.domain.AlipayTradeWapPayModel; +import com.alipay.api.request.AlipayTradeWapPayRequest; +import com.alipay.api.response.AlipayTradeWapPayResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; + +import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.CLOSED; +import static cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum.WAITING; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +/** + * {@link AlipayWapPayClient} 单元测试 + * + * @author jason + */ +public class AlipayWapPayClientTest extends AbstractAlipayClientTest { + + /** + * 支付宝 H5 支付 Client + */ + @InjectMocks + private AlipayWapPayClient client = new AlipayWapPayClient(randomLongId(), config); + + @BeforeEach + public void setUp() { + setClient(client); + } + + @Test + @DisplayName("支付宝 H5 支付:下单成功") + public void testUnifiedOrder_success() throws AlipayApiException { + // mock 方法 + String h5Body = randomString(); + Integer price = randomInteger(); + AlipayTradeWapPayResponse response = randomPojo(AlipayTradeWapPayResponse.class, o -> { + o.setSubCode(""); + o.setBody(h5Body); + }); + String notifyUrl = randomURL(); + when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> { + assertInstanceOf(AlipayTradeWapPayModel.class, request.getBizModel()); + AlipayTradeWapPayModel bizModel = (AlipayTradeWapPayModel) request.getBizModel(); + assertEquals(String.valueOf(price / 100.0), bizModel.getTotalAmount()); + assertEquals(notifyUrl, request.getNotifyUrl()); + return true; + }), eq(Method.GET.name()))).thenReturn(response); + // 准备请求参数 + String outTradeNo = randomString(); + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price); + + // 调用 + PayOrderRespDTO resp = client.unifiedOrder(reqDTO); + // 断言 + assertEquals(WAITING.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + assertNull(resp.getChannelOrderNo()); + assertNull(resp.getChannelUserId()); + assertNull(resp.getSuccessTime()); + assertEquals(PayOrderDisplayModeEnum.URL.getMode(), resp.getDisplayMode()); + assertEquals(response.getBody(), resp.getDisplayContent()); + assertSame(response, resp.getRawData()); + assertNull(resp.getChannelErrorCode()); + assertNull(resp.getChannelErrorMsg()); + } + + @Test + @DisplayName("支付宝 H5 支付:渠道返回失败") + public void test_unified_order_channel_failed() throws AlipayApiException { + // mock 方法 + String subCode = randomString(); + String subMsg = randomString(); + AlipayTradeWapPayResponse response = randomPojo(AlipayTradeWapPayResponse.class, o -> { + o.setSubCode(subCode); + o.setSubMsg(subMsg); + }); + when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher) request -> true), + eq(Method.GET.name()))).thenReturn(response); + String outTradeNo = randomString(); + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger()); + + // 调用 + PayOrderRespDTO resp = client.unifiedOrder(reqDTO); + // 断言 + assertEquals(CLOSED.getStatus(), resp.getStatus()); + assertEquals(outTradeNo, resp.getOutTradeNo()); + assertNull(resp.getChannelOrderNo()); + assertNull(resp.getChannelUserId()); + assertNull(resp.getSuccessTime()); + assertNull(resp.getDisplayMode()); + assertNull(resp.getDisplayContent()); + assertSame(response, resp.getRawData()); + assertEquals(subCode, resp.getChannelErrorCode()); + assertEquals(subMsg, resp.getChannelErrorMsg()); + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java index 19248d1a1..6ed3af8e8 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java @@ -369,7 +369,7 @@ public class AfterSaleServiceImpl implements AfterSaleService { public void afterCommit() { // 创建退款单 PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties); - Long payRefundId = payRefundApi.createRefund(createReqDTO); + Long payRefundId = payRefundApi.createRefund(createReqDTO).getCheckedData(); // 更新售后单的退款单号 tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId)); } 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 c441a563a..c80e0783e 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 @@ -242,7 +242,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { // 创建支付单,用于后续的支付 PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert( order, orderItems, tradeOrderProperties); - Long payOrderId = payOrderApi.createOrder(payOrderCreateReqDTO); + Long payOrderId = payOrderApi.createOrder(payOrderCreateReqDTO).getCheckedData(); // 更新到交易单上 tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setPayOrderId(payOrderId)); @@ -302,7 +302,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { } // 校验支付单是否存在 - PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId); + PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId).getCheckedData(); if (payOrder == null) { log.error("[validateOrderPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId); throw exception(ORDER_NOT_FOUND); diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java index f2fbe0a75..389af59b8 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.pay.api.order; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.enums.ApiConstants; @@ -15,8 +16,6 @@ import org.springframework.web.bind.annotation.RequestParam; import javax.validation.Valid; -// TODO 芋艿:CommonResult; - @FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = @Tag(name = "RPC 服务 - 支付单") public interface PayOrderApi { @@ -25,12 +24,12 @@ public interface PayOrderApi { @PostMapping(PREFIX + "/create") @Operation(summary = "创建支付单") - Long createOrder(@Valid @RequestBody PayOrderCreateReqDTO reqDTO); + CommonResult createOrder(@Valid @RequestBody PayOrderCreateReqDTO reqDTO); @PostMapping(PREFIX + "/get") @Operation(summary = "获得支付单") @Parameter(name = "id", description = "支付单编号", example = "1", required = true) - PayOrderRespDTO getOrder(Long id); + CommonResult getOrder(Long id); @PutMapping(PREFIX + "/update-price") @Operation(summary = "更新支付订单价格") @@ -38,7 +37,7 @@ public interface PayOrderApi { @Parameter(name = "id", description = "支付单编号", example = "1", required = true), @Parameter(name = "payPrice", description = "支付单价格", example = "100", required = true) }) - void updatePayOrderPrice(@RequestParam("id") Long id, - @RequestParam("payPrice") Integer payPrice); + CommonResult updatePayOrderPrice(@RequestParam("id") Long id, + @RequestParam("payPrice") Integer payPrice); } diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java index 524895bab..d19e3ca47 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.pay.api.refund; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO; import cn.iocoder.yudao.module.pay.enums.ApiConstants; @@ -20,11 +21,11 @@ public interface PayRefundApi { @PostMapping(PREFIX + "/create") @Operation(summary = "创建退款单") - Long createRefund(@Valid @RequestBody PayRefundCreateReqDTO reqDTO); + CommonResult createRefund(@Valid @RequestBody PayRefundCreateReqDTO reqDTO); @PostMapping(PREFIX + "/get") @Operation(summary = "获得退款单") @Parameter(name = "id", description = "退款单编号", example = "1", required = true) - PayRefundRespDTO getRefund(Long id); + CommonResult getRefund(Long id); } diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java new file mode 100644 index 000000000..3661fdc39 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.pay.api.transfer; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; +import cn.iocoder.yudao.module.pay.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +import javax.validation.Valid; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +@Tag(name = "RPC 服务 - 转账单") +public interface PayTransferApi { + + String PREFIX = ApiConstants.PREFIX + "/transfer"; + + @PostMapping(PREFIX + "/create") + @Operation(summary = "创建转账单") + CommonResult createTransfer(@Valid @RequestBody PayTransferCreateReqDTO reqDTO); + +} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java new file mode 100644 index 000000000..17a611172 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.pay.api.transfer.dto; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Map; + +@Schema(description = "RPC 服务 - 转账单创建 Request DTO") +@Data +public class PayTransferCreateReqDTO { + + @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "应用编号不能为空") + private Long appId; + + @Schema(description = "类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "转账类型不能为空") + @InEnum(PayTransferTypeEnum.class) + private Integer type; + + @Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "M101") // 例如说,内部系统 A 的订单号。需要保证每个 PayMerchantDO 唯一 + @NotEmpty(message = "商户订单编号不能为空") + private String merchantOrderId; + + @Schema(description = "转账金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "80") + @Min(value = 1, message = "转账金额必须大于零") + @NotNull(message = "转账金额不能为空") + private Integer price; + + @Schema(description = "转账标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是标题") + @NotEmpty(message = "转账标题不能为空") + private String title; + + @Schema(description = "收款方信息", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "收款方信息不能为空") + private Map payeeInfo; + +} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java index f0c89fdae..476d7417b 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java @@ -21,17 +21,17 @@ public interface ErrorCodeConstants { ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1_007_001_004, "已存在相同的渠道"); // ========== ORDER 模块 1-007-002-000 ========== - ErrorCode ORDER_NOT_FOUND = new ErrorCode(1_007_002_000, "支付订单不存在"); - ErrorCode ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_002_001, "支付订单不处于待支付"); - ErrorCode ORDER_STATUS_IS_SUCCESS = new ErrorCode(1_007_002_002, "订单已支付,请刷新页面"); - ErrorCode ORDER_IS_EXPIRED = new ErrorCode(1_007_002_003, "支付订单已经过期"); - ErrorCode ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_002_004, "发起支付报错,错误码:{},错误提示:{}"); - ErrorCode ORDER_REFUND_FAIL_STATUS_ERROR = new ErrorCode(1_007_002_005, "支付订单退款失败,原因:状态不是已支付或已退款"); + ErrorCode PAY_ORDER_NOT_FOUND = new ErrorCode(1_007_002_000, "支付订单不存在"); + ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_002_001, "支付订单不处于待支付"); + ErrorCode PAY_ORDER_STATUS_IS_SUCCESS = new ErrorCode(1_007_002_002, "订单已支付,请刷新页面"); + ErrorCode PAY_ORDER_IS_EXPIRED = new ErrorCode(1_007_002_003, "支付订单已经过期"); + ErrorCode PAY_ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_002_004, "发起支付报错,错误码:{},错误提示:{}"); + ErrorCode PAY_ORDER_REFUND_FAIL_STATUS_ERROR = new ErrorCode(1_007_002_005, "支付订单退款失败,原因:状态不是已支付或已退款"); // ========== ORDER 模块(拓展单) 1-007-003-000 ========== - ErrorCode ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1_007_003_000, "支付交易拓展单不存在"); - ErrorCode ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_003_001, "支付交易拓展单不处于待支付"); - ErrorCode ORDER_EXTENSION_IS_PAID = new ErrorCode(1_007_003_002, "订单已支付,请等待支付结果"); + ErrorCode PAY_ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1_007_003_000, "支付交易拓展单不存在"); + ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_003_001, "支付交易拓展单不处于待支付"); + ErrorCode PAY_ORDER_EXTENSION_IS_PAID = new ErrorCode(1_007_003_002, "订单已支付,请等待支付结果"); // ========== 支付模块(退款) 1-007-006-000 ========== ErrorCode REFUND_PRICE_EXCEED = new ErrorCode(1_007_006_000, "退款金额超过订单可退款金额"); @@ -40,6 +40,42 @@ public interface ErrorCodeConstants { ErrorCode REFUND_NOT_FOUND = new ErrorCode(1_007_006_004, "支付退款单不存在"); ErrorCode REFUND_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_006_005, "支付退款单不处于待退款"); + // ========== 钱包模块 1-007-007-000 ========== + ErrorCode WALLET_NOT_FOUND = new ErrorCode(1_007_007_000, "用户钱包不存在"); + ErrorCode WALLET_BALANCE_NOT_ENOUGH = new ErrorCode(1_007_007_001, "钱包余额不足"); + ErrorCode WALLET_TRANSACTION_NOT_FOUND = new ErrorCode(1_007_007_002, "未找到对应的钱包交易"); + ErrorCode WALLET_REFUND_EXIST = new ErrorCode(1_007_007_003, "已经存在钱包退款"); + ErrorCode WALLET_FREEZE_PRICE_NOT_ENOUGH = new ErrorCode(1_007_007_004, "钱包冻结余额不足"); + + // ========== 钱包充值模块 1-007-008-000 ========== + ErrorCode WALLET_RECHARGE_NOT_FOUND = new ErrorCode(1_007_008_000, "钱包充值记录不存在"); + ErrorCode WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1_007_008_001, "钱包充值更新支付状态失败,钱包充值记录不是【未支付】状态"); + ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR = new ErrorCode(1_007_008_002, "钱包充值更新支付状态失败,支付单编号不匹配"); + ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1_007_008_003, "钱包充值更新支付状态失败,支付单状态不是【支付成功】状态"); + ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH = new ErrorCode(1_007_008_004, "钱包充值更新支付状态失败,支付单金额不匹配"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_NOT_PAID = new ErrorCode(1_007_008_005, "钱包发起退款失败,钱包充值订单未支付"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUNDED = new ErrorCode(1_007_008_006, "钱包发起退款失败,钱包充值订单已退款"); + ErrorCode WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH = new ErrorCode(1_007_008_007, "钱包发起退款失败,钱包余额不足"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1_007_008_008, "钱包退款更新失败,钱包退款单编号不匹配"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_007_008_009, "钱包退款更新失败,退款订单不存在"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1_007_008_010, "钱包退款更新失败,退款单金额不匹配"); + ErrorCode WALLET_RECHARGE_PACKAGE_AND_PRICE_IS_EMPTY = new ErrorCode(1_007_008_011, "充值金额和充钱套餐不能同时为空"); + ErrorCode WALLET_RECHARGE_PACKAGE_NOT_FOUND = new ErrorCode(1_007_008_012, "钱包充值套餐不存在"); + ErrorCode WALLET_RECHARGE_PACKAGE_IS_DISABLE = new ErrorCode(1_007_008_013, "钱包充值套餐已禁用"); + ErrorCode WALLET_RECHARGE_PACKAGE_NAME_EXISTS = new ErrorCode(1_007_008_014, "钱包充值套餐名称已存在"); + + // ========== 转账模块 1-007-009-000 ========== + ErrorCode PAY_TRANSFER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_009_000, "发起转账报错,错误码:{},错误提示:{}"); + ErrorCode PAY_TRANSFER_ALIPAY_LOGIN_ID_IS_EMPTY = new ErrorCode(1_007_009_001, "支付宝登录 ID 不能为空"); + ErrorCode PAY_TRANSFER_ALIPAY_ACCOUNT_NAME_IS_EMPTY = new ErrorCode(1_007_009_002, "支付宝账号名称不能为空"); + ErrorCode PAY_TRANSFER_NOT_FOUND = new ErrorCode(1_007_009_003, "转账交易单不存在"); + ErrorCode PAY_TRANSFER_STATUS_IS_SUCCESS = new ErrorCode(1_007_009_004, "转账单已成功转账"); + ErrorCode PAY_TRANSFER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_005, "转账单不处于待转账"); + ErrorCode PAY_TRANSFER_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_006, "转账单不处于待转账或转账中"); + ErrorCode PAY_TRANSFER_EXTENSION_NOT_FOUND = new ErrorCode(1_007_009_007, "转账交易拓展单不存在"); + ErrorCode PAY_TRANSFER_TYPE_AND_CHANNEL_NOT_MATCH = new ErrorCode(1_007_009_008, "转账类型和转账渠道不匹配"); + ErrorCode PAY_TRANSFER_EXTENSION_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_009, "转账拓展单不处于待转账或转账中"); + // ========== 示例订单 1-007-900-000 ========== ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1_007_900_000, "示例订单不存在"); ErrorCode DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1_007_900_001, "示例订单更新支付状态失败,订单不是【未支付】状态"); diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletOperateTypeEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletOperateTypeEnum.java deleted file mode 100644 index 896e90ef9..000000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletOperateTypeEnum.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.iocoder.yudao.module.pay.enums.member; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 钱包操作类型枚举 - * - * @author jason - */ -@AllArgsConstructor -@Getter -public enum WalletOperateTypeEnum { - TOP_UP_INC(1, "充值增加"), - ORDER_DEC(2, "订单消费扣除"); - // TODO 其它类型 - - private final Integer type; - - private final String desc; -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletTransactionGategoryEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletTransactionGategoryEnum.java deleted file mode 100644 index fe950991d..000000000 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletTransactionGategoryEnum.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.module.pay.enums.member; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 钱包交易大类枚举 - * - * @author jason - */ -@AllArgsConstructor -@Getter -public enum WalletTransactionGategoryEnum { - TOP_UP(1, "充值"), - SPENDING(2, "支出"); - - /** - * 分类 - */ - private final Integer category; - - /** - * 说明 - */ - private final String desc; -} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java new file mode 100644 index 000000000..f3eff1495 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.pay.enums.transfer; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * @author jason + */ +@Getter +@AllArgsConstructor +public enum PayTransferStatusEnum { + + WAITING(0, "待转账"), + /** + * TODO 转账到银行卡. 会有T+0 T+1 到账的请情况。 还未实现 + */ + IN_PROGRESS(10, "转账进行中"), + + SUCCESS(20, "转账成功"), + /** + * 转账关闭 (失败,或者其它情况) + */ + CLOSED(30, "转账关闭"); + + /** + * 状态 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + public static boolean isSuccess(Integer status) { + return Objects.equals(status, SUCCESS.getStatus()); + } + + public static boolean isClosed(Integer status) { + return Objects.equals(status, CLOSED.getStatus()); + } + public static boolean isWaiting(Integer status) { + return Objects.equals(status, WAITING.getStatus()); + } + + /** + * 是否处于待转账或者转账中的状态 + * @param status 状态 + */ + public static boolean isPendingStatus(Integer status) { + return Objects.equals(status, WAITING.getStatus()) || Objects.equals(status, IN_PROGRESS.status); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferTypeEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferTypeEnum.java new file mode 100644 index 000000000..b86b37384 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferTypeEnum.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.pay.enums.transfer; + +import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 转账类型枚举 + * + * @author jason + */ +@AllArgsConstructor +@Getter +public enum PayTransferTypeEnum implements IntArrayValuable { + + ALIPAY_BALANCE(1, "支付宝余额"), + WX_BALANCE(2, "微信余额"), + BANK_CARD(3, "银行卡"), + WALLET_BALANCE(4, "钱包余额"); + + public static final String ALIPAY_LOGON_ID = "ALIPAY_LOGON_ID"; + public static final String ALIPAY_ACCOUNT_NAME = "ALIPAY_ACCOUNT_NAME"; + + private final Integer type; + private final String name; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PayTransferTypeEnum::getType).toArray(); + + @Override + public int[] array() { + return ARRAYS; + } + + public static PayTransferTypeEnum typeOf(Integer type) { + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/pom.xml b/yudao-module-pay/yudao-module-pay-biz/pom.xml index 1e0850252..aca7735de 100644 --- a/yudao-module-pay/yudao-module-pay-biz/pom.xml +++ b/yudao-module-pay/yudao-module-pay-biz/pom.xml @@ -30,6 +30,11 @@ yudao-module-pay-api ${revision} + + cn.iocoder.cloud + yudao-module-member-api + ${revision} + diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java index 424edcf06..a0c0042e3 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApiImpl.java @@ -1,17 +1,18 @@ package cn.iocoder.yudao.module.pay.api.order; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; -import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; -@Service +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + @RestController // 提供 RESTful API 接口,给 Feign 调用 @Validated public class PayOrderApiImpl implements PayOrderApi { @@ -20,19 +21,20 @@ public class PayOrderApiImpl implements PayOrderApi { private PayOrderService payOrderService; @Override - public Long createOrder(PayOrderCreateReqDTO reqDTO) { - return payOrderService.createOrder(reqDTO); + public CommonResult createOrder(PayOrderCreateReqDTO reqDTO) { + return success(payOrderService.createOrder(reqDTO)); } @Override - public PayOrderRespDTO getOrder(Long id) { + public CommonResult getOrder(Long id) { PayOrderDO order = payOrderService.getOrder(id); - return PayOrderConvert.INSTANCE.convert2(order); + return success(PayOrderConvert.INSTANCE.convert2(order)); } @Override - public void updatePayOrderPrice(Long id, Integer payPrice) { + public CommonResult updatePayOrderPrice(Long id, Integer payPrice) { payOrderService.updatePayOrderPrice(id, payPrice); + return success(true); } } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java index d7ef62a9c..d88fb8dd1 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java @@ -1,16 +1,17 @@ package cn.iocoder.yudao.module.pay.api.refund; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO; import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert; import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; -import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; -@Service +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + @RestController // 提供 RESTful API 接口,给 Feign 调用 @Validated public class PayRefundApiImpl implements PayRefundApi { @@ -19,13 +20,13 @@ public class PayRefundApiImpl implements PayRefundApi { private PayRefundService payRefundService; @Override - public Long createRefund(PayRefundCreateReqDTO reqDTO) { - return payRefundService.createPayRefund(reqDTO); + public CommonResult createRefund(PayRefundCreateReqDTO reqDTO) { + return success(payRefundService.createPayRefund(reqDTO)); } @Override - public PayRefundRespDTO getRefund(Long id) { - return PayRefundConvert.INSTANCE.convert02(payRefundService.getRefund(id)); + public CommonResult getRefund(Long id) { + return success(PayRefundConvert.INSTANCE.convert02(payRefundService.getRefund(id))); } } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java new file mode 100644 index 000000000..592a213b3 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.pay.api.transfer; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +/** + * 转账单 API 实现类 + * + * @author jason + */ +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class PayTransferApiImpl implements PayTransferApi { + + @Resource + private PayTransferService payTransferService; + + @Override + public CommonResult createTransfer(PayTransferCreateReqDTO reqDTO) { + return success(payTransferService.createTransfer(reqDTO)); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoTransferController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoTransferController.java new file mode 100644 index 000000000..e3411d40c --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoTransferController.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.pay.controller.admin.demo; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO; +import cn.iocoder.yudao.module.pay.service.demo.PayDemoTransferService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - 示例转账单") +@RestController +@RequestMapping("/pay/demo-transfer") +@Validated +public class PayDemoTransferController { + @Resource + private PayDemoTransferService demoTransferService; + + @PostMapping("/create") + @Operation(summary = "创建示例转账订单") + public CommonResult createDemoOrder(@Valid @RequestBody PayDemoTransferCreateReqVO createReqVO) { + return success(demoTransferService.createDemoTransfer(getLoginUserId(), createReqVO)); + } +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/transfer/PayDemoTransferCreateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/transfer/PayDemoTransferCreateReqVO.java new file mode 100644 index 000000000..41bafe676 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/transfer/PayDemoTransferCreateReqVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Map; + +/** + * @author jason + */ +@Schema(description = "管理后台 - 示例转账单创建 Request VO") +@Data +public class PayDemoTransferCreateReqVO { + + @Schema(description = "转账类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "转账类型不能为空") + @InEnum(PayTransferTypeEnum.class) + private Integer type; + + @NotNull(message = "转账金额不能为空") + @Min(value = 1, message = "转账金额必须大于零") + private Integer price; + + // TODO @jason:感觉这个动态字段,晚点改;可能要讨论下怎么搞好; + @Schema(description = "收款方信息", requiredMode = Schema.RequiredMode.REQUIRED, example = "{'ALIPAY_LOGON_ID':'xxxx'}") + @NotEmpty(message = "收款方信息不能为空") + private Map payeeInfo; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java index 9a2afbe66..edd524466 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java @@ -5,13 +5,16 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*; import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert; import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; +import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient; import cn.iocoder.yudao.module.pay.service.app.PayAppService; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import com.google.common.collect.Maps; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -26,11 +29,14 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType; @Tag(name = "管理后台 - 支付订单") @RestController @@ -70,6 +76,16 @@ public class PayOrderController { @PostMapping("/submit") @Operation(summary = "提交支付订单") public CommonResult submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) { + // 1. 钱包支付事,需要额外传 user_id 和 user_type + if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) { + Map channelExtras = reqVO.getChannelExtras() == null ? + Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras(); + channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId())); + channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType())); + reqVO.setChannelExtras(channelExtras); + } + + // 2. 提交支付 PayOrderSubmitRespVO respVO = orderService.submitOrder(reqVO, getClientIP()); return success(respVO); } @@ -93,7 +109,7 @@ public class PayOrderController { @PreAuthorize("@ss.hasPermission('pay:order:export')") @OperateLog(type = EXPORT) public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO, - HttpServletResponse response) throws IOException { + HttpServletResponse response) throws IOException { List list = orderService.getOrderList(exportReqVO); if (CollectionUtil.isEmpty(list)) { ExcelUtils.write(response, "支付订单.xls", "数据", diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java index 17644fd60..a4fe4632f 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java @@ -13,16 +13,16 @@ import java.time.LocalDateTime; @ToString(callSuper = true) public class PayOrderDetailsRespVO extends PayOrderBaseVO { - @Schema(description = "支付订单编号", required = true, example = "1024") + @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") private Long id; @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") private String appName; - @Schema(description = "创建时间", required = true) + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; - @Schema(description = "更新时间", required = true) + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime updateTime; /** @@ -34,7 +34,7 @@ public class PayOrderDetailsRespVO extends PayOrderBaseVO { @Schema(description = "支付订单扩展") public static class PayOrderExtension { - @Schema(description = "支付订单号", required = true, example = "1024") + @Schema(description = "支付订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") private String no; @Schema(description = "支付异步通知的内容") diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java new file mode 100644 index 000000000..6d8592058 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.pay.controller.admin.transfer; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferSubmitReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferSubmitRespVO; +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; + +@Tag(name = "管理后台 - 转账单") +@RestController +@RequestMapping("/pay/transfer") +@Validated +public class PayTransferController { + + @Resource + private PayTransferService payTransferService; + + @PostMapping("/submit") + @Operation(summary = "提交转账订单") + // TODO @jason:权限的设置, 管理后台页面加的时候加一下 + public CommonResult submitPayTransfer(@Valid @RequestBody PayTransferSubmitReqVO reqVO) { + PayTransferSubmitRespVO respVO = payTransferService.submitTransfer(reqVO, getClientIP()); + return success(respVO); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferSubmitReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferSubmitReqVO.java new file mode 100644 index 000000000..36e67ce01 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferSubmitReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Map; + +@Schema(description = "管理后台 - 转账单提交 Request VO") +@Data +public class PayTransferSubmitReqVO { + + @Schema(description = "转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "转账单编号不能为空") + private Long id; + + @Schema(description = "转账渠道", requiredMode = Schema.RequiredMode.REQUIRED, example = "alipay_transfer") + @NotEmpty(message = "转账渠道不能为空") + private String channelCode; + + @Schema(description = "转账渠道的额外参数") + private Map channelExtras; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferSubmitRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferSubmitRespVO.java new file mode 100644 index 000000000..fef296f09 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferSubmitRespVO.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 转账单提交 Response VO") +@Data +public class PayTransferSubmitRespVO { + + @Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 参见 PayTransferStatusEnum 枚举 + private Integer status; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java new file mode 100644 index 000000000..8fcb668ac --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.member.api.user.MemberUserApi; +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletRespVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletUserReqVO; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +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.RestController; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.enums.UserTypeEnum.MEMBER; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; + +@Tag(name = "管理后台 - 用户钱包") +@RestController +@RequestMapping("/pay/wallet") +@Validated +@Slf4j +public class PayWalletController { + + @Resource + private PayWalletService payWalletService; + @Resource + private MemberUserApi memberUserApi; + + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('pay:wallet:query')") + @Operation(summary = "获得用户钱包明细") + public CommonResult getWallet(PayWalletUserReqVO reqVO) { + PayWalletDO wallet = payWalletService.getOrCreateWallet(reqVO.getUserId(), MEMBER.getValue()); + // TODO jason:如果为空,返回给前端只要 null 就可以了 + MemberUserRespDTO memberUser = memberUserApi.getUser(reqVO.getUserId()).getCheckedData(); + String nickname = memberUser == null ? "" : memberUser.getNickname(); + String avatar = memberUser == null ? "" : memberUser.getAvatar(); + return success(PayWalletConvert.INSTANCE.convert02(nickname, avatar, wallet)); + } + + @GetMapping("/page") + @Operation(summary = "获得会员钱包分页") + @PreAuthorize("@ss.hasPermission('pay:wallet:query')") + public CommonResult> getWalletPage(@Valid PayWalletPageReqVO pageVO) { + if (StrUtil.isNotEmpty(pageVO.getNickname())) { + List users = memberUserApi.getUserListByNickname(pageVO.getNickname()).getCheckedData(); + pageVO.setUserIds(convertSet(users, MemberUserRespDTO::getId)); + } + // TODO @jason:管理员也可以先查询下。。 + // 暂时支持查询 userType 会员类型。管理员类型还不知道使用场景 + PageResult pageResult = payWalletService.getWalletPage(MEMBER.getValue(),pageVO); + if (CollectionUtil.isEmpty(pageResult.getList())) { + return success(new PageResult<>(pageResult.getTotal())); + } + List users = memberUserApi.getUserList(convertList(pageResult.getList(), PayWalletDO::getUserId)).getCheckedData(); + Map userMap = convertMap(users, MemberUserRespDTO::getId); + return success(PayWalletConvert.INSTANCE.convertPage(pageResult, userMap)); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java new file mode 100644 index 000000000..21ba7262b --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; +import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargeService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.annotation.security.PermitAll; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; + +@Tag(name = "管理后台 - 钱包充值") +@RestController +@RequestMapping("/pay/wallet-recharge") +@Validated +@Slf4j +public class PayWalletRechargeController { + + @Resource + private PayWalletRechargeService walletRechargeService; + + @PostMapping("/update-paid") + @Operation(summary = "更新钱包充值为已充值") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob + @PermitAll // 无需登录, 内部校验实现 + @OperateLog(enable = false) // 禁用操作日志,因为没有操作人 + public CommonResult updateWalletRechargerPaid(@Valid @RequestBody PayOrderNotifyReqDTO notifyReqDTO) { + walletRechargeService.updateWalletRechargerPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()), + notifyReqDTO.getPayOrderId()); + return success(true); + } + + // TODO @jason:发起退款,要 post 操作哈; + @GetMapping("/refund") + @Operation(summary = "发起钱包充值退款") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + public CommonResult refundWalletRecharge(@RequestParam("id") Long id) { + walletRechargeService.refundWalletRecharge(id, getClientIP()); + return success(true); + } + + @PostMapping("/update-refunded") + @Operation(summary = "更新钱包充值为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob + @PermitAll // 无需登录, 内部校验实现 + @OperateLog(enable = false) // 禁用操作日志,因为没有操作人 + public CommonResult updateWalletRechargeRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) { + walletRechargeService.updateWalletRechargeRefunded( + Long.valueOf(notifyReqDTO.getMerchantOrderId()), notifyReqDTO.getPayRefundId()); + return success(true); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java new file mode 100644 index 000000000..c60127fe7 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargePackageController.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackagePageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageRespVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageUpdateReqVO; +import cn.iocoder.yudao.module.pay.convert.wallet.WalletRechargePackageConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargePackageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + + +@Tag(name = "管理后台 - 钱包充值套餐") +@RestController +@RequestMapping("/pay/wallet-recharge-package") +@Validated +public class PayWalletRechargePackageController { + + @Resource + private PayWalletRechargePackageService walletRechargePackageService; + + @PostMapping("/create") + @Operation(summary = "创建钱包充值套餐") + @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:create')") + public CommonResult createWalletRechargePackage(@Valid @RequestBody WalletRechargePackageCreateReqVO createReqVO) { + return success(walletRechargePackageService.createWalletRechargePackage(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新钱包充值套餐") + @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:update')") + public CommonResult updateWalletRechargePackage(@Valid @RequestBody WalletRechargePackageUpdateReqVO updateReqVO) { + walletRechargePackageService.updateWalletRechargePackage(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除钱包充值套餐") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:delete')") + public CommonResult deleteWalletRechargePackage(@RequestParam("id") Long id) { + walletRechargePackageService.deleteWalletRechargePackage(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得钱包充值套餐") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:query')") + public CommonResult getWalletRechargePackage(@RequestParam("id") Long id) { + PayWalletRechargePackageDO walletRechargePackage = walletRechargePackageService.getWalletRechargePackage(id); + return success(WalletRechargePackageConvert.INSTANCE.convert(walletRechargePackage)); + } + + @GetMapping("/page") + @Operation(summary = "获得钱包充值套餐分页") + @PreAuthorize("@ss.hasPermission('pay:wallet-recharge-package:query')") + public CommonResult> getWalletRechargePackagePage(@Valid WalletRechargePackagePageReqVO pageVO) { + PageResult pageResult = walletRechargePackageService.getWalletRechargePackagePage(pageVO); + return success(WalletRechargePackageConvert.INSTANCE.convertPage(pageResult)); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java new file mode 100644 index 000000000..37dd51642 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletTransactionController.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +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.admin.wallet.vo.transaction.PayWalletTransactionRespVO; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletTransactionConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +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.RestController; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 钱包余额明细") +@RestController +@RequestMapping("/pay/wallet-transaction") +@Validated +@Slf4j +public class PayWalletTransactionController { + + @Resource + private PayWalletTransactionService payWalletTransactionService; + + @GetMapping("/page") + @Operation(summary = "获得钱包流水分页") + @PreAuthorize("@ss.hasPermission('pay:wallet:query')") + public CommonResult> getWalletTransactionPage( + @Valid PayWalletTransactionPageReqVO pageReqVO) { + PageResult result = payWalletTransactionService.getWalletTransactionPage(pageReqVO); + return success(PayWalletTransactionConvert.INSTANCE.convertPage2(result)); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java new file mode 100644 index 000000000..c3f58e1da --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageBaseVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * 充值套餐 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class WalletRechargePackageBaseVO { + + @Schema(description = "套餐名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @NotNull(message = "套餐名不能为空") + private String name; + + @Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "16454") + @NotNull(message = "支付金额不能为空") + private Integer payPrice; + + @Schema(description = "赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20887") + @NotNull(message = "赠送金额不能为空") + private Integer bonusPrice; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "状态不能为空") + private Byte status; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java new file mode 100644 index 000000000..4232a9983 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageCreateReqVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 充值套餐创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class WalletRechargePackageCreateReqVO extends WalletRechargePackageBaseVO { + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java new file mode 100644 index 000000000..346e85902 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackagePageReqVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 充值套餐分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class WalletRechargePackagePageReqVO extends PageParam { + + @Schema(description = "套餐名", example = "李四") + private String name; + + @Schema(description = "状态", example = "2") + private Integer status; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java new file mode 100644 index 000000000..e709cfdd3 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageRespVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 充值套餐 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class WalletRechargePackageRespVO extends WalletRechargePackageBaseVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9032") + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java new file mode 100644 index 000000000..37170cb8b --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/rechargepackage/WalletRechargePackageUpdateReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 充值套餐更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class WalletRechargePackageUpdateReqVO extends WalletRechargePackageBaseVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9032") + @NotNull(message = "编号不能为空") + private Long id; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java new file mode 100644 index 000000000..678649ce0 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionPageReqVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 钱包流水分页 Request VO") +@Data +public class PayWalletTransactionPageReqVO extends PageParam { + + @Schema(description = "钱包编号", example = "1") + private Long walletId; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java new file mode 100644 index 000000000..6203b78d5 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/transaction/PayWalletTransactionRespVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "用户 APP - 钱包流水分页 Response VO") +@Data +public class PayWalletTransactionRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "钱包编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + private Long walletId; + + @Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer bizType; + + @Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Long price; + + @Schema(description = "流水标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆土豆") + private String title; + + @Schema(description = "交易后的余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Long balance; + + @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + // TODO @jason:merchantOrderId 字段,需要在 PayWalletTransaction 存储下;然后,前端也返回下这个字段,界面也展示下商户名 + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java new file mode 100644 index 000000000..a45ea8b90 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletBaseVO.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * 用户钱包 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class PayWalletBaseVO { + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20020") + @NotNull(message = "用户编号不能为空") + private Long userId; + + @Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "用户类型不能为空") + private Integer userType; + + @Schema(description = "余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "余额,单位分不能为空") + private Integer balance; + + @Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "累计支出,单位分不能为空") + private Integer totalExpense; + + @Schema(description = "累计充值,单位分", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "累计充值,单位分不能为空") + private Integer totalRecharge; + + @Schema(description = "冻结金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "20737") + @NotNull(message = "冻结金额,单位分不能为空") + private Integer freezePrice; + +} \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java new file mode 100644 index 000000000..d74bd44b1 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletPageReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; +import java.util.Collection; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 会员钱包分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayWalletPageReqVO extends PageParam { + + @Schema(description = "用户昵称", example = "李四") + private String nickname; + + @Schema(description = "用户编号", example = "[1,2]") + private Collection userIds; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java new file mode 100644 index 000000000..a9eedbdfd --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletRespVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 用户钱包 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayWalletRespVO extends PayWalletBaseVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29528") + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王**") + private String nickname; + @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.jpg") + private String avatar; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java new file mode 100644 index 000000000..dfa335166 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUserReqVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 用户钱包明细 Request VO") +@Data +public class PayWalletUserReqVO { + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "用户编号不能为空") + private Long userId; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java index 9100d3498..8033b26b0 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java @@ -1,12 +1,15 @@ package cn.iocoder.yudao.module.pay.controller.app.order; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderRespVO; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO; import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO; import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO; import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert; +import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import com.google.common.collect.Maps; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -16,8 +19,13 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import java.util.Map; +import java.util.Objects; + import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType; @Tag(name = "用户 APP - 支付订单") @RestController @@ -40,6 +48,16 @@ public class AppPayOrderController { @PostMapping("/submit") @Operation(summary = "提交支付订单") public CommonResult submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) { + // 1. 钱包支付事,需要额外传 user_id 和 user_type + if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) { + Map channelExtras = reqVO.getChannelExtras() == null ? + Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras(); + channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId())); + channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType())); + reqVO.setChannelExtras(channelExtras); + } + + // 2. 提交支付 PayOrderSubmitRespVO respVO = payOrderService.submitOrder(reqVO, getClientIP()); return success(PayOrderConvert.INSTANCE.convert3(respVO)); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java new file mode 100644 index 000000000..95c085cbc --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +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.RestController; + +import javax.annotation.Resource; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +/** + * @author jason + */ +@Tag(name = "用户 APP - 钱包") +@RestController +@RequestMapping("/pay/wallet") +@Validated +@Slf4j +public class AppPayWalletController { + + @Resource + private PayWalletService payWalletService; + + @GetMapping("/get") + @Operation(summary = "获取钱包") + @PreAuthenticated + public CommonResult getPayWallet() { + PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue()); + return success(PayWalletConvert.INSTANCE.convert(wallet)); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java new file mode 100644 index 000000000..5f0a64286 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateRespVO; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargeConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargeService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType; + +@Tag(name = "用户 APP - 钱包充值") +@RestController +@RequestMapping("/pay/wallet-recharge") +@Validated +@Slf4j +public class AppPayWalletRechargeController { + + @Resource + private PayWalletRechargeService walletRechargeService; + + @PostMapping("/create") + @Operation(summary = "创建钱包充值记录(发起充值)") + public CommonResult createWalletRecharge( + @Valid @RequestBody AppPayWalletRechargeCreateReqVO reqVO) { + PayWalletRechargeDO walletRecharge = walletRechargeService.createWalletRecharge( + getLoginUserId(), getLoginUserType(), getClientIP(), reqVO); + return success(PayWalletRechargeConvert.INSTANCE.convert(walletRecharge)); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java new file mode 100644 index 000000000..9b2aac188 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargePackageController.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletPackageRespVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +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.RestController; + +import java.util.ArrayList; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "用户 APP - 钱包充值套餐") +@RestController +@RequestMapping("/pay/wallet-recharge-package") +@Validated +@Slf4j +public class AppPayWalletRechargePackageController { + + @GetMapping("/list") + @Operation(summary = "获得钱包充值套餐列表") + public CommonResult> getWalletRechargePackageList() { + // 只查询开启;需要按照 payPrice 排序; + List list = new ArrayList<>(); + list.add(new AppPayWalletPackageRespVO().setId(1L).setName("土豆").setPayPrice(10).setBonusPrice(2)); + list.add(new AppPayWalletPackageRespVO().setId(2L).setName("番茄").setPayPrice(20).setBonusPrice(5)); + return success(list); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java new file mode 100644 index 000000000..db0c7844d --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletTransactionConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +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.RestController; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "用户 APP - 钱包余额明细") +@RestController +@RequestMapping("/pay/wallet-transaction") +@Validated +@Slf4j +public class AppPayWalletTransactionController { + + @Resource + private PayWalletTransactionService payWalletTransactionService; + + @GetMapping("/page") + @Operation(summary = "获得钱包流水分页") + public CommonResult> getWalletTransactionPage( + @Valid AppPayWalletTransactionPageReqVO pageReqVO) { + if (true) { + PageResult result = new PageResult<>(10L); + result.getList().add(new AppPayWalletTransactionRespVO().setPrice(1L) + .setTitle("测试").setCreateTime(LocalDateTime.now())); + result.getList().add(new AppPayWalletTransactionRespVO().setPrice(-1L) + .setTitle("测试2").setCreateTime(LocalDateTime.now())); + return success(result); + } + PageResult result = payWalletTransactionService.getWalletTransactionPage(getLoginUserId(), + UserTypeEnum.MEMBER.getValue(), pageReqVO); + return success(PayWalletTransactionConvert.INSTANCE.convertPage(result)); + } +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java new file mode 100644 index 000000000..c12db9889 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletPackageRespVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "用户 APP - 用户充值套餐 Response VO") +@Data +public class AppPayWalletPackageRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + @Schema(description = "套餐名", requiredMode = Schema.RequiredMode.REQUIRED, example = "小套餐") + private String name; + + @Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer payPrice; + @Schema(description = "赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + private Integer bonusPrice; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java new file mode 100644 index 000000000..1421fb0b5 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.AssertTrue; +import javax.validation.constraints.Min; +import java.util.Objects; + +@Schema(description = "用户 APP - 创建钱包充值 Request VO") +@Data +public class AppPayWalletRechargeCreateReqVO { + + @Schema(description = "支付金额", example = "1000") + @Min(value = 1, message = "支付金额必须大于零") + private Integer payPrice; + + @Schema(description = "充值套餐编号", example = "1024") + private Long packageId; + + @AssertTrue(message = "充值金额和充钱套餐不能同时为空") + public boolean validatePayPriceAndPackageId() { + return Objects.nonNull(payPrice) || Objects.nonNull(packageId); + } +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java new file mode 100644 index 000000000..2c4a96f62 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "用户 APP - 创建钱包充值 Resp VO") +@Data +public class AppPayWalletRechargeCreateRespVO { + + @Schema(description = "钱包充值编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Long payOrderId; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java new file mode 100644 index 000000000..942ab5b6d --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "用户 APP - 钱包流水分页 Request VO") +@Data +public class AppPayWalletTransactionPageReqVO extends PageParam { + + /** + * 类型 - 收入 + */ + public static final Integer TYPE_INCOME = 1; + /** + * 类型 - 支出 + */ + public static final Integer TYPE_EXPENSE = 2; + + @Schema(description = "类型", example = "1") + private Integer type; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java new file mode 100644 index 000000000..5c20188eb --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "用户 APP - 钱包流水分页 Response VO") +@Data +public class AppPayWalletTransactionRespVO { + + @Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer bizType; + + @Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Long price; + + @Schema(description = "流水标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆土豆") + private String title; + + @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java new file mode 100644 index 000000000..f0c78e405 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "用户 APP - 用户钱包 Response VO") +@Data +public class AppPayWalletRespVO { + + @Schema(description = "钱包余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Integer balance; + + @Schema(description = "累计支出, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000") + private Integer totalExpense; + + @Schema(description = "累计充值, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000") + private Integer totalRecharge; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/transfer/PayTransferConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/transfer/PayTransferConvert.java new file mode 100644 index 000000000..440f0103b --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/transfer/PayTransferConvert.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.pay.convert.transfer; + +import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface PayTransferConvert { + + PayTransferConvert INSTANCE = Mappers.getMapper(PayTransferConvert.class); + + @Mapping(source = "title", target = "subject") // TODO @jason:是不是都改成 subject 完事呀? + PayTransferDO convert(PayTransferCreateReqDTO dto); + + PayTransferCreateReqDTO convert(PayDemoTransferCreateReqVO vo); + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java new file mode 100644 index 000000000..e162b88bc --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.pay.convert.wallet; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletRespVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.Map; + +@Mapper +public interface PayWalletConvert { + + PayWalletConvert INSTANCE = Mappers.getMapper(PayWalletConvert.class); + + AppPayWalletRespVO convert(PayWalletDO bean); + + PayWalletRespVO convert02(String nickname,String avatar, PayWalletDO bean); + + PageResult convertPage(PageResult page); + + default PageResult convertPage(PageResult page, Map userMap){ + PageResult pageResult = convertPage(page); + pageResult.getList().forEach( wallet -> MapUtils.findAndThen(userMap, wallet.getUserId(), + user -> { + // TODO @jason:可以链式调用哈; + wallet.setNickname(user.getNickname()); + wallet.setAvatar(user.getAvatar()); + })); + return pageResult; + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java new file mode 100644 index 000000000..eda8bcf95 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletRechargeConvert.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.pay.convert.wallet; + +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface PayWalletRechargeConvert { + + PayWalletRechargeConvert INSTANCE = Mappers.getMapper(PayWalletRechargeConvert.class); + + @Mapping(target = "totalPrice", expression = "java( payPrice + bonusPrice)") + PayWalletRechargeDO convert(Long walletId, Integer payPrice, Integer bonusPrice, Long packageId); + + AppPayWalletRechargeCreateRespVO convert(PayWalletRechargeDO bean); + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java new file mode 100644 index 000000000..f956f8d56 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.pay.convert.wallet; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.transaction.PayWalletTransactionRespVO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface PayWalletTransactionConvert { + + PayWalletTransactionConvert INSTANCE = Mappers.getMapper(PayWalletTransactionConvert.class); + + PageResult convertPage(PageResult page); + + PageResult convertPage2(PageResult page); + + PayWalletTransactionDO convert(WalletTransactionCreateReqBO bean); + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/WalletRechargePackageConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/WalletRechargePackageConvert.java new file mode 100644 index 000000000..8784adda7 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/WalletRechargePackageConvert.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.pay.convert.wallet; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageRespVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageUpdateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface WalletRechargePackageConvert { + + WalletRechargePackageConvert INSTANCE = Mappers.getMapper(WalletRechargePackageConvert.class); + + PayWalletRechargePackageDO convert(WalletRechargePackageCreateReqVO bean); + + PayWalletRechargePackageDO convert(WalletRechargePackageUpdateReqVO bean); + + WalletRechargePackageRespVO convert(PayWalletRechargePackageDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoTransferDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoTransferDO.java new file mode 100644 index 000000000..b036c30a2 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoTransferDO.java @@ -0,0 +1,72 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.demo; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 示例转账订单 + * + * 演示业务系统的转账业务 + */ +@TableName(value ="pay_demo_transfer", autoResultMap = true) +@KeySequence("pay_demo_transfer_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayDemoTransferDO extends BaseDO { + + /** + * 订单编号 + */ + @TableId + private Long id; + + /** + * 用户编号 + */ + private Long userId; + + /** + * 转账金额,单位:分 + */ + private Integer price; + + /** + * 转账类型 + */ + private Integer type; + + // TODO @jason:要不字段还是弄成正确的平铺开? + /** + * 收款人信息,不同类型和渠道不同 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map payeeInfo; + + /** + * 转账状态 + */ + private Integer transferStatus; + + /** + * 转账订单编号 + */ + private Long payTransferId; + + /** + * 转账支付成功渠道 + */ + private String payChannelCode; + + /** + * 转账支付时间 + */ + private LocalDateTime transferTime; + +} \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/member/MemberWalletDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/member/MemberWalletDO.java deleted file mode 100644 index e6e92bf82..000000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/member/MemberWalletDO.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.member; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -// TODO @jason:修改 MemberWalletDO 为 PayWalletDO -/** - * 支付 - 会员钱包 DO - * - * @author jason - */ -@TableName(value ="pay_member_wallet") -@KeySequence("pay_member_wallet_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -public class MemberWalletDO extends BaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - - // TODO @jaosn:增加 userType 字段; - /** - * 用户 id - * - * 关联 MemberUserDO 的 id 编号 - * 关联 AdminUserDO 的 id 编号 - */ - private Long userId; - - /** - * 余额, 单位分 - */ - private Integer balance; - - /** - * 累计支出, 单位分 - */ - private Integer totalSpending; - - /** - * 累计充值, 单位分 - */ - private Integer totalTopUp; -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/member/MemberWalletTransactionDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/member/MemberWalletTransactionDO.java deleted file mode 100644 index 82ede2915..000000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/member/MemberWalletTransactionDO.java +++ /dev/null @@ -1,86 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.dataobject.member; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.pay.enums.member.WalletOperateTypeEnum; -import cn.iocoder.yudao.module.pay.enums.member.WalletTransactionGategoryEnum; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * 支付-会员钱包明细 DO - * - * @author jason - */ -@TableName(value ="pay_member_wallet_transaction") -@KeySequence("pay_member_wallet_transaction_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -public class MemberWalletTransactionDO extends BaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - - /** - * 会员钱包 id - * - * 关联 {@link MemberWalletDO#getId()} - */ - private Long walletId; - - /** - * 用户 id - * - * 关联 MemberUserDO 的 id 编号 - */ - private Long userId; - - /** - * 交易单号 @芋艿 这里是关联交易单号, 还是订单号 , 退款单号! ?? - */ - private String tradeNo; - - /** - * 交易分类 - * - * 枚举 {@link WalletTransactionGategoryEnum#getCategory()} - */ - private Integer category; - - /** - * 操作分类 - * - * 枚举 {@link WalletOperateTypeEnum#getType()} - */ - private Integer operateType; - - /** - * 操作详情 - */ - private String operateDesc; - - /** - * 交易金额, 单位分 - */ - private Integer price; - - /** - * 余额, 单位分 - */ - private Integer balance; - - /** - * 备注 - */ - private String mark; - - /** - * 交易时间 - */ - private LocalDateTime transactionTime; -} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java new file mode 100644 index 000000000..507157a7a --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java @@ -0,0 +1,106 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.transfer; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum; +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum; +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 转账单 DO + * + * @author jason + */ +@TableName(value ="pay_transfer", autoResultMap = true) +@KeySequence("pay_transfer_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayTransferDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 应用编号 + * + * 关联 {@link PayAppDO#getId()} + */ + private Long appId; + /** + * 转账渠道编号 + * + * 关联 {@link PayChannelDO#getId()} + */ + private Long channelId; + /** + * 转账渠道编码 + * + * 枚举 {@link PayChannelEnum} + */ + private String channelCode; + /** + * 类型 + * + * 枚举 {@link PayTransferTypeEnum} + */ + private Integer type; + + // ========== 商户相关字段 ========== + + /** + * 商户订单编号 + * + * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 + */ + private String merchantOrderId; + + /** + * 转账标题 + */ + private String subject; + + // ========== 转账相关字段 ========== + /** + * 转账金额,单位:分 + */ + private Integer price; + /** + * 转账状态 + * + * 枚举 {@link PayTransferStatusRespEnum} + */ + private Integer status; + /** + * 订单转账成功时间 + */ + private LocalDateTime successTime; + /** + * 转账成功的转账拓展单编号 + * + * 关联 {@link PayTransferExtensionDO#getId()} + */ + private Long extensionId; + /** + * 转账成功的转账拓展单号 + * + * 关联 {@link PayTransferExtensionDO#getNo()} + */ + private String no; + /** + * 收款人信息,不同类型和渠道不同 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map payeeInfo; + +} \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferExtensionDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferExtensionDO.java new file mode 100644 index 000000000..c3d4253e8 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferExtensionDO.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.transfer; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; + +import java.util.Map; + +// TODO @jason:转账是不是类似 refund,不用拓展单呀?支付做拓展单的原因,是因为它存在不确定性,可以切换多种;转账和退款,都是明确方式的; +// @芋艿 转账是不是也存在多种方式。 例如转账到银行卡。 可以使用微信,也可以使用支付宝。 支付宝账号余额不够,可以切换到微信; +// TODO @jason:发起了,就不允许调整了,类似退款哈; +/** + * 转账拓展单 DO + * + * @author jason + */ +@TableName(value ="pay_transfer_extension",autoResultMap = true) +@KeySequence("pay_transfer_extension_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayTransferExtensionDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 转账单号 + */ + private String no; + + /** + * 转账单编号 + */ + private Long transferId; + + /** + * 转账渠道编号 + */ + private Long channelId; + + /** + * 转账渠道编码 + */ + private String channelCode; + + /** + * 支付渠道的额外参数 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map channelExtras; + + /** + * 转账状态 + */ + private Integer status; + + /** + * 支付渠道异步通知的内容 + */ + private String channelNotifyData; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java new file mode 100644 index 000000000..a3c54c969 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.wallet; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 会员钱包 DO + * + * @author jason + */ +@TableName(value ="pay_wallet") +@KeySequence("pay_wallet_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayWalletDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 用户 id + * + * 关联 MemberUserDO 的 id 编号 + * 关联 AdminUserDO 的 id 编号 + */ + private Long userId; + /** + * 用户类型, 预留 多商户转帐可能需要用到 + * + * 关联 {@link UserTypeEnum} + */ + private Integer userType; + + /** + * 余额,单位分 + */ + private Integer balance; + + /** + * 冻结金额,单位分 + */ + private Integer freezePrice; + + /** + * 累计支出,单位分 + */ + private Integer totalExpense; + /** + * 累计充值,单位分 + */ + private Integer totalRecharge; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java new file mode 100644 index 000000000..a842c95e6 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java @@ -0,0 +1,116 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.wallet; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 会员钱包充值 + */ +@TableName(value ="pay_wallet_recharge") +@KeySequence("pay_wallet_recharge_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayWalletRechargeDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 钱包编号 + * + * 关联 {@link PayWalletDO#getId()} + */ + private Long walletId; + + /** + * 用户实际到账余额 + * + * 例如充 100 送 20,则该值是 120 + */ + private Integer totalPrice; + /** + * 实际支付金额 + */ + private Integer payPrice; + /** + * 钱包赠送金额 + */ + private Integer bonusPrice; + + /** + * 充值套餐编号 + * + * 关联 {@link PayWalletRechargeDO#getPackageId()} 字段 + */ + private Long packageId; + + /** + * 是否已支付 + * + * true - 已支付 + * false - 未支付 + */ + private Boolean payStatus; + + /** + * 支付订单编号 + * + * 关联 {@link PayOrderDO#getId()} + */ + private Long payOrderId; + + /** + * 支付成功的支付渠道 + * + * 冗余 {@link PayOrderDO#getChannelCode()} + */ + private String payChannelCode; + /** + * 订单支付时间 + */ + private LocalDateTime payTime; + + /** + * 支付退款单编号 + * + * 关联 {@link PayRefundDO#getId()} + */ + private Long payRefundId; + + /** + * 退款金额,包含赠送金额 + */ + private Integer refundTotalPrice; + /** + * 退款支付金额 + */ + private Integer refundPayPrice; + + /** + * 退款钱包赠送金额 + */ + private Integer refundBonusPrice; + + /** + * 退款时间 + */ + private LocalDateTime refundTime; + + /** + * 退款状态 + * + * 枚举 {@link PayRefundStatusEnum} + */ + private Integer refundStatus; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java new file mode 100644 index 000000000..72fc43d96 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargePackageDO.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.wallet; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 会员钱包充值套餐 DO + * + * 通过充值套餐时,可以赠送一定金额; + * + * @author 芋道源码 + */ +@TableName(value ="pay_wallet_recharge_package") +@KeySequence("pay_wallet_recharge_package_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayWalletRechargePackageDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 套餐名 + */ + private String name; + + /** + * 支付金额 + */ + private Integer payPrice; + /** + * 赠送金额 + */ + private Integer bonusPrice; + + /** + * 状态 + * + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} + */ + private Integer status; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java new file mode 100644 index 000000000..654e51157 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java @@ -0,0 +1,66 @@ +package cn.iocoder.yudao.module.pay.dal.dataobject.wallet; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 会员钱包流水 DO + * + * @author jason + */ +@TableName(value ="pay_wallet_transaction") +@KeySequence("pay_wallet_transaction_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayWalletTransactionDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 流水号 + */ + private String no; + + /** + * 钱包编号 + * + * 关联 {@link PayWalletDO#getId()} + */ + private Long walletId; + + /** + * 关联业务分类 + * + * 枚举 {@link PayWalletBizTypeEnum#getType()} + */ + private Integer bizType; + + /** + * 关联业务编号 + */ + private String bizId; + + /** + * 流水说明 + */ + private String title; + + /** + * 交易金额,单位分 + * + * 正值表示余额增加,负值表示余额减少 + */ + private Integer price; + + /** + * 交易后余额,单位分 + */ + private Integer balance; +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoTransferMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoTransferMapper.java new file mode 100644 index 000000000..a2a600660 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoTransferMapper.java @@ -0,0 +1,10 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.demo; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoTransferDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PayDemoTransferMapper extends BaseMapperX { + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/member/MemberWalletMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/member/MemberWalletMapper.java deleted file mode 100644 index 46e80df8e..000000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/member/MemberWalletMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.member; - - -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.module.pay.dal.dataobject.member.MemberWalletDO; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface MemberWalletMapper extends BaseMapperX { - -} - - - - diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/member/MemberWalletTransactionMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/member/MemberWalletTransactionMapper.java deleted file mode 100644 index a6cb541d1..000000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/member/MemberWalletTransactionMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.iocoder.yudao.module.pay.dal.mysql.member; - - -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.module.pay.dal.dataobject.member.MemberWalletTransactionDO; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface MemberWalletTransactionMapper extends BaseMapperX { - -} - - - - diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferExtensionMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferExtensionMapper.java new file mode 100644 index 000000000..8e808f24d --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferExtensionMapper.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.transfer; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferExtensionDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface PayTransferExtensionMapper extends BaseMapperX { + + default PayTransferExtensionDO selectByNo(String no){ + return selectOne(PayTransferExtensionDO::getNo, no); + } + + default int updateByIdAndStatus(Long id, List whereStatuses, PayTransferExtensionDO updateObj) { + return update(updateObj, new LambdaQueryWrapper() + .eq(PayTransferExtensionDO::getId, id).in(PayTransferExtensionDO::getStatus, whereStatuses)); + } + +} + + + + diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java new file mode 100644 index 000000000..3bd25d4b5 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.transfer; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface PayTransferMapper extends BaseMapperX { + + default int updateByIdAndStatus(Long id, List status, PayTransferDO updateObj) { + return update(updateObj, new LambdaQueryWrapper() + .eq(PayTransferDO::getId, id).in(PayTransferDO::getStatus, status)); + } + +} + + + + diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java new file mode 100644 index 000000000..ce672255a --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java @@ -0,0 +1,122 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.wallet; + + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PayWalletMapper extends BaseMapperX { + + default PayWalletDO selectByUserIdAndType(Long userId, Integer userType) { + return selectOne(PayWalletDO::getUserId, userId, + PayWalletDO::getUserType, userType); + } + + default PageResult selectPage(Integer userType, PayWalletPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .inIfPresent(PayWalletDO::getUserId, reqVO.getUserIds()) + .eqIfPresent(PayWalletDO::getUserType, userType) + .betweenIfPresent(PayWalletDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayWalletDO::getId)); + } + + /** + * 当消费退款时候, 更新钱包 + * + * @param id 钱包 id + * @param price 消费金额 + */ + default int updateWhenConsumptionRefund(Long id, Integer price){ + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" balance = balance + " + price + + ", total_expense = total_expense - " + price) + .eq(PayWalletDO::getId, id); + return update(null, lambdaUpdateWrapper); + } + + /** + * 当消费时候, 更新钱包 + * + * @param price 消费金额 + * @param id 钱包 id + */ + default int updateWhenConsumption(Long id, Integer price){ + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" balance = balance - " + price + + ", total_expense = total_expense + " + price) + .eq(PayWalletDO::getId, id) + .ge(PayWalletDO::getBalance, price); // cas 逻辑 + return update(null, lambdaUpdateWrapper); + } + + /** + * 当充值的时候,更新钱包 + * + * @param id 钱包 id + * @param price 钱包金额 + */ + default int updateWhenRecharge(Long id, Integer price){ + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" balance = balance + " + price + + ", total_recharge = total_recharge + " + price) + .eq(PayWalletDO::getId, id); + return update(null, lambdaUpdateWrapper); + } + + /** + * 冻结钱包部分余额 + * + * @param id 钱包 id + * @param price 冻结金额 + */ + default int freezePrice(Long id, Integer price){ + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" balance = balance - " + price + + ", freeze_price = freeze_price + " + price) + .eq(PayWalletDO::getId, id) + .ge(PayWalletDO::getBalance, price); // cas 逻辑 + return update(null, lambdaUpdateWrapper); + } + + /** + * 解冻钱包余额 + * + * @param id 钱包 id + * @param price 解冻金额 + */ + default int unFreezePrice(Long id, Integer price){ + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" balance = balance + " + price + + ", freeze_price = freeze_price - " + price) + .eq(PayWalletDO::getId, id) + .ge(PayWalletDO::getFreezePrice, price); // cas 逻辑 + return update(null, lambdaUpdateWrapper); + } + + /** + * 当充值退款时, 更新钱包 + * + * @param id 钱包 id + * @param price 退款金额 + */ + default int updateWhenRechargeRefund(Long id, Integer price){ + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" freeze_price = freeze_price - " + price + + ", total_recharge = total_recharge - " + price) + .eq(PayWalletDO::getId, id) + .ge(PayWalletDO::getFreezePrice, price) + .ge(PayWalletDO::getTotalRecharge, price);// cas 逻辑 + return update(null, lambdaUpdateWrapper); + } + + +} + + + + diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java new file mode 100644 index 000000000..4cb77f020 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.wallet; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PayWalletRechargeMapper extends BaseMapperX { + + default int updateByIdAndPaid(Long id, boolean wherePayStatus, PayWalletRechargeDO updateObj) { + return update(updateObj, new LambdaQueryWrapperX() + .eq(PayWalletRechargeDO::getId, id).eq(PayWalletRechargeDO::getPayStatus, wherePayStatus)); + } + + default int updateByIdAndRefunded(Long id, Integer whereRefundStatus, PayWalletRechargeDO updateObj) { + return update(updateObj, new LambdaQueryWrapperX() + .eq(PayWalletRechargeDO::getId, id).eq(PayWalletRechargeDO::getRefundStatus, whereRefundStatus)); + } + +} \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java new file mode 100644 index 000000000..b68b4c893 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargePackageMapper.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.wallet; + + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackagePageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PayWalletRechargePackageMapper extends BaseMapperX { + + default PageResult selectPage(WalletRechargePackagePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(PayWalletRechargePackageDO::getName, reqVO.getName()) + .eqIfPresent(PayWalletRechargePackageDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(PayWalletRechargePackageDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayWalletRechargePackageDO::getPayPrice)); + } + + // TODO @jason:这里要有空格哈;String name) { + default PayWalletRechargePackageDO selectByName(String name){ + return selectOne(PayWalletRechargePackageDO::getName, name); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java new file mode 100644 index 000000000..41d7dbeb4 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.pay.dal.mysql.wallet; + + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Objects; + +@Mapper +public interface PayWalletTransactionMapper extends BaseMapperX { + + default PageResult selectPage(Long walletId, Integer type, + PageParam pageParam) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX() + .eqIfPresent(PayWalletTransactionDO::getWalletId, walletId); + if (Objects.equals(type, AppPayWalletTransactionPageReqVO.TYPE_INCOME)) { + query.gt(PayWalletTransactionDO::getPrice, 0); + } else if (Objects.equals(type, AppPayWalletTransactionPageReqVO.TYPE_EXPENSE)) { + query.lt(PayWalletTransactionDO::getPrice, 0); + } + query.orderByDesc(PayWalletTransactionDO::getId); + return selectPage(pageParam, query); + } + + default PayWalletTransactionDO selectByNo(String no) { + return selectOne(PayWalletTransactionDO::getNo, no); + } + + default PayWalletTransactionDO selectByBiz(String bizId, Integer bizType) { + return selectOne(PayWalletTransactionDO::getBizId, bizId, + PayWalletTransactionDO::getBizType, bizType); + } + +} + + + + diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java index fc4c94933..30081c6e8 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java @@ -22,6 +22,6 @@ public interface RedisKeyConstants { * KEY 格式:pay_no:{prefix} * VALUE 数据格式:编号自增 */ - String PAY_NO = "pay_no"; + String PAY_NO = "pay_no:"; } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java index 7ce6caadc..f6be6f92a 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java @@ -1,9 +1,13 @@ package cn.iocoder.yudao.module.pay.dal.redis.no; -import cn.hutool.core.date.DatePattern;import cn.hutool.core.date.DateUtil;import org.springframework.data.redis.core.StringRedisTemplate; +import cn.hutool.core.date.DatePattern;import cn.hutool.core.date.DateUtil; +import cn.iocoder.yudao.module.pay.dal.redis.RedisKeyConstants; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Repository; -import javax.annotation.Resource;import java.time.LocalDateTime; +import javax.annotation.Resource; +import java.time.Duration; +import java.time.LocalDateTime; /** * 支付序号的 Redis DAO @@ -23,8 +27,12 @@ public class PayNoRedisDAO { * @return 序号 */ public String generate(String prefix) { + // 递增序号 String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN); - Long no = stringRedisTemplate.opsForValue().increment(noPrefix); + String key = RedisKeyConstants.PAY_NO + noPrefix; + Long no = stringRedisTemplate.opsForValue().increment(key); + // 设置过期时间 + stringRedisTemplate.expire(key, Duration.ofMinutes(1L)); return noPrefix + no; } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java new file mode 100644 index 000000000..212551f4d --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java @@ -0,0 +1,184 @@ +package cn.iocoder.yudao.module.pay.framework.pay.core; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; +import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; +import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; +import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_ORDER_EXTENSION_NOT_FOUND; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.REFUND_NOT_FOUND; + +/** + * 钱包支付的 PayClient 实现类 + * + * @author jason + */ +@Slf4j +public class WalletPayClient extends AbstractPayClient { + + public static final String USER_ID_KEY = "user_id"; + public static final String USER_TYPE_KEY = "user_type"; + + private PayWalletService wallService; + private PayWalletTransactionService walletTransactionService; + private PayOrderService orderService; + private PayRefundService refundService; + + public WalletPayClient(Long channelId, NonePayClientConfig config) { + super(channelId, PayChannelEnum.WALLET.getCode(), config); + } + + @Override + protected void doInit() { + if (wallService == null) { + wallService = SpringUtil.getBean(PayWalletService.class); + } + if (walletTransactionService == null) { + walletTransactionService = SpringUtil.getBean(PayWalletTransactionService.class); + } + } + + @Override + protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { + try { + Long userId = MapUtil.getLong(reqDTO.getChannelExtras(), USER_ID_KEY); + Integer userType = MapUtil.getInt(reqDTO.getChannelExtras(), USER_TYPE_KEY); + Assert.notNull(userId, "用户 id 不能为空"); + Assert.notNull(userType, "用户类型不能为空"); + PayWalletTransactionDO transaction = wallService.orderPay(userId, userType, reqDTO.getOutTradeNo(), + reqDTO.getPrice()); + return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(), + transaction.getCreateTime(), + reqDTO.getOutTradeNo(), transaction); + } catch (Throwable ex) { + log.error("[doUnifiedOrder] 失败", ex); + Integer errorCode = INTERNAL_SERVER_ERROR.getCode(); + String errorMsg = INTERNAL_SERVER_ERROR.getMsg(); + if (ex instanceof ServiceException) { + ServiceException serviceException = (ServiceException) ex; + errorCode = serviceException.getCode(); + errorMsg = serviceException.getMessage(); + } + return PayOrderRespDTO.closedOf(String.valueOf(errorCode), errorMsg, + reqDTO.getOutTradeNo(), ""); + } + } + + @Override + protected PayOrderRespDTO doParseOrderNotify(Map params, String body) { + throw new UnsupportedOperationException("钱包支付无支付回调"); + } + + @Override + protected PayOrderRespDTO doGetOrder(String outTradeNo) { + if (orderService == null) { + orderService = SpringUtil.getBean(PayOrderService.class); + } + PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo); + // 支付交易拓展单不存在, 返回关闭状态 + if (orderExtension == null) { + return PayOrderRespDTO.closedOf(String.valueOf(PAY_ORDER_EXTENSION_NOT_FOUND.getCode()), + PAY_ORDER_EXTENSION_NOT_FOUND.getMsg(), outTradeNo, ""); + } + // 关闭状态 + if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { + return PayOrderRespDTO.closedOf(orderExtension.getChannelErrorCode(), + orderExtension.getChannelErrorMsg(), outTradeNo, ""); + } + // 成功状态 + if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { + PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction( + String.valueOf(orderExtension.getOrderId()), PayWalletBizTypeEnum.PAYMENT); + Assert.notNull(walletTransaction, "支付单 {} 钱包流水不能为空", outTradeNo); + return PayOrderRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreator(), + walletTransaction.getCreateTime(), outTradeNo, walletTransaction); + } + // 其它状态为无效状态 + log.error("[doGetOrder] 支付单 {} 的状态不正确", outTradeNo); + throw new IllegalStateException(String.format("支付单[%s] 状态不正确", outTradeNo)); + } + + @Override + protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { + try { + PayWalletTransactionDO payWalletTransaction = wallService.orderRefund(reqDTO.getOutRefundNo(), + reqDTO.getRefundPrice(), reqDTO.getReason()); + return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getCreateTime(), + reqDTO.getOutRefundNo(), payWalletTransaction); + } catch (Throwable ex) { + log.error("[doUnifiedRefund] 失败", ex); + Integer errorCode = INTERNAL_SERVER_ERROR.getCode(); + String errorMsg = INTERNAL_SERVER_ERROR.getMsg(); + if (ex instanceof ServiceException) { + ServiceException serviceException = (ServiceException) ex; + errorCode = serviceException.getCode(); + errorMsg = serviceException.getMessage(); + } + return PayRefundRespDTO.failureOf(String.valueOf(errorCode), errorMsg, + reqDTO.getOutRefundNo(), ""); + } + } + + @Override + protected PayRefundRespDTO doParseRefundNotify(Map params, String body) { + throw new UnsupportedOperationException("钱包支付无退款回调"); + } + + @Override + protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) { + if (refundService == null) { + refundService = SpringUtil.getBean(PayRefundService.class); + } + PayRefundDO payRefund = refundService.getRefundByNo(outRefundNo); + // 支付退款单不存在, 返回退款失败状态 + if (payRefund == null) { + return PayRefundRespDTO.failureOf(String.valueOf(REFUND_NOT_FOUND), REFUND_NOT_FOUND.getMsg(), + outRefundNo, ""); + } + // 退款失败 + if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) { + return PayRefundRespDTO.failureOf(payRefund.getChannelErrorCode(), payRefund.getChannelErrorMsg(), + outRefundNo, ""); + } + // 退款成功 + if (PayRefundStatusRespEnum.isSuccess(payRefund.getStatus())) { + PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction( + String.valueOf(payRefund.getId()), PayWalletBizTypeEnum.PAYMENT_REFUND); + Assert.notNull(walletTransaction, "支付退款单 {} 钱包流水不能为空", outRefundNo); + return PayRefundRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreateTime(), + outRefundNo, walletTransaction); + } + // 其它状态为无效状态 + log.error("[doGetRefund] 支付退款单 {} 的状态不正确", outRefundNo); + throw new IllegalStateException(String.format("支付退款单[%s] 状态不正确", outRefundNo)); + } + + @Override + public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { + throw new UnsupportedOperationException("待实现"); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/package-info.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/package-info.java deleted file mode 100644 index 07f837ff5..000000000 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 占位,无实际作用 - */ -package cn.iocoder.yudao.module.pay.framework.pay.core; \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/rpc/config/RpcConfiguration.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/rpc/config/RpcConfiguration.java new file mode 100644 index 000000000..92317f04b --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/rpc/config/RpcConfiguration.java @@ -0,0 +1,10 @@ +package cn.iocoder.yudao.module.pay.framework.rpc.config; + +import cn.iocoder.yudao.module.member.api.user.MemberUserApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableFeignClients(clients = MemberUserApi.class) +public class RpcConfiguration { +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/rpc/package-info.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/rpc/package-info.java new file mode 100644 index 000000000..98523d98b --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/rpc/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package cn.iocoder.yudao.module.pay.framework.rpc; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/security/config/SecurityConfiguration.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/security/config/SecurityConfiguration.java index f44e1f5e0..70a3a239f 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/security/config/SecurityConfiguration.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/security/config/SecurityConfiguration.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.pay.framework.security.config; import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer; -import cn.iocoder.yudao.module.system.enums.ApiConstants; +import cn.iocoder.yudao.module.pay.enums.ApiConstants; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java index 293d6c590..2fe495a5f 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java @@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateR import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert; import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper; +import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import lombok.Getter; @@ -20,6 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; +import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.validation.Validator; import java.time.Duration; @@ -68,6 +70,14 @@ public class PayChannelServiceImpl implements PayChannelService { @Resource private Validator validator; + /** + * 初始化,为了注册钱包 + */ + @PostConstruct + public void init() { + payClientFactory.registerPayClientClass(PayChannelEnum.WALLET, WalletPayClient.class); + } + @Override public Long createChannel(PayChannelCreateReqVO reqVO) { // 断言是否有重复的 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 f5ebf7961..c19b9dfd8 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 @@ -25,7 +25,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -import static cn.hutool.core.util.ObjectUtil.*; +import static cn.hutool.core.util.ObjectUtil.notEqual; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; @@ -91,7 +91,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService { .setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用 .setMerchantOrderId(demoOrder.getId().toString()) // 业务的订单编号 .setSubject(spuName).setBody("").setPrice(price) // 价格信息 - .setExpireTime(addTime(Duration.ofHours(2L)))); // 支付的过期时间 + .setExpireTime(addTime(Duration.ofHours(2L)))).getCheckedData(); // 支付的过期时间 // 2.2 更新支付单到 demo 订单 payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(demoOrder.getId()) .setPayOrderId(payOrderId)); @@ -153,10 +153,10 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService { } // 2.1 校验支付单是否存在 - PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId); + PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId).getCheckedData(); if (payOrder == null) { log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId); - throw exception(ORDER_NOT_FOUND); + throw exception(PAY_ORDER_NOT_FOUND); } // 2.2 校验支付单已支付 if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) { @@ -193,7 +193,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService { .setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用 .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号 .setMerchantRefundId(refundId) - .setReason("想退钱").setPrice(order.getPrice()));// 价格信息 + .setReason("想退钱").setPrice(order.getPrice())).getCheckedData();// 价格信息 // 2.3 更新退款单到 demo 订单 payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id) .setPayRefundId(payRefundId).setRefundPrice(order.getPrice())); @@ -239,7 +239,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService { } // 2.1 校验退款订单 - PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId); + PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId).getCheckedData(); if (payRefund == null) { throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferService.java new file mode 100644 index 000000000..f775a838f --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferService.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.pay.service.demo; + +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO; + +import javax.validation.Valid; + +/** + * 示例转账业务 Service 接口 + * + * @author jason + */ +public interface PayDemoTransferService { + + /** + * 创建转账单 + * + * @param userId 用户编号 + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createDemoTransfer(Long userId, @Valid PayDemoTransferCreateReqVO createReqVO); +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java new file mode 100644 index 000000000..d4da016ac --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java @@ -0,0 +1,89 @@ +package cn.iocoder.yudao.module.pay.service.demo; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO; +import cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoTransferDO; +import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoTransferMapper; +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum.*; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_TRANSFER_ALIPAY_ACCOUNT_NAME_IS_EMPTY; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_TRANSFER_ALIPAY_LOGIN_ID_IS_EMPTY; +import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum.WAITING; + +/** + * 示例转账业务 Service 实现类 + * + * @author jason + */ +@Service +@Validated +public class PayDemoTransferServiceImpl implements PayDemoTransferService { + + /** + * 接入的实力应用编号 + + * 从 [支付管理 -> 应用信息] 里添加 + */ + private static final Long TRANSFER_APP_ID = 8L; + @Resource + private PayDemoTransferMapper demoTransferMapper; + @Resource + private PayTransferService transferService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createDemoTransfer(Long userId, @Valid PayDemoTransferCreateReqVO vo) { + // 1 校验收款账号 + validatePayeeInfo(vo.getType(), vo.getPayeeInfo()); + + // 2 保存示例转账业务表 + PayDemoTransferDO demoTransfer = new PayDemoTransferDO().setUserId(userId).setType(vo.getType()) + .setPrice(vo.getPrice()).setPayeeInfo(vo.getPayeeInfo()) + .setTransferStatus(WAITING.getStatus()); + demoTransferMapper.insert(demoTransfer); + + // 3.1 创建转账单 + Long transferId = transferService.createTransfer(PayTransferConvert.INSTANCE.convert(vo) + .setAppId(TRANSFER_APP_ID).setTitle("示例转账") + .setMerchantOrderId(String.valueOf(demoTransfer.getId()))); + // 3.2 更新转账单编号 + demoTransferMapper.updateById(new PayDemoTransferDO().setId(demoTransfer.getId()) + .setPayTransferId(transferId)); + return demoTransfer.getId(); + } + + // TODO @jason:可以参考 AppBrokerageWithdrawCreateReqVO 搞下字段哈,进行校验 + // @jason payeeinfo 字段确定改一下 + private void validatePayeeInfo(Integer transferType, Map payeeInfo) { + PayTransferTypeEnum transferTypeEnum = typeOf(transferType); + switch (transferTypeEnum) { + case ALIPAY_BALANCE: { + if (StrUtil.isEmpty(MapUtil.getStr(payeeInfo, ALIPAY_LOGON_ID))) { + throw exception(PAY_TRANSFER_ALIPAY_LOGIN_ID_IS_EMPTY); + } + if (StrUtil.isEmpty(MapUtil.getStr(payeeInfo, ALIPAY_ACCOUNT_NAME))) { + throw exception(PAY_TRANSFER_ALIPAY_ACCOUNT_NAME_IS_EMPTY); + } + break; + } + case WX_BALANCE: + case BANK_CARD: + case WALLET_BALANCE: { + throw new UnsupportedOperationException("待实现"); + } + } + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java index bc5355af9..978a7950c 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java @@ -106,14 +106,6 @@ public interface PayOrderService { */ void updatePayOrderPrice(Long id, Integer payPrice); - /** - * 更新支付订单价格 - * - * @param payOrderId 支付单编号 - * @param payPrice 支付单价格 - */ - void updatePayOrderPriceById(Long payOrderId, Integer payPrice); - /** * 获得支付订单 * 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 6dc9ea81e..16977c630 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 @@ -156,7 +156,7 @@ public class PayOrderServiceImpl implements PayOrderService { getSelf().notifyOrder(channel, unifiedOrderResp); // 如有渠道错误码,则抛出业务异常,提示用户 if (StrUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) { - throw exception(ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(), + throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(), unifiedOrderResp.getChannelErrorMsg()); } // 此处需要读取最新的状态 @@ -168,16 +168,16 @@ public class PayOrderServiceImpl implements PayOrderService { private PayOrderDO validateOrderCanSubmit(Long id) { PayOrderDO order = orderMapper.selectById(id); if (order == null) { // 是否存在 - throw exception(ORDER_NOT_FOUND); + throw exception(PAY_ORDER_NOT_FOUND); } if (PayOrderStatusEnum.isSuccess(order.getStatus())) { // 校验状态,发现已支付 - throw exception(ORDER_STATUS_IS_SUCCESS); + throw exception(PAY_ORDER_STATUS_IS_SUCCESS); } if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付 - throw exception(ORDER_STATUS_IS_NOT_WAITING); + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); } if (LocalDateTimeUtils.beforeNow(order.getExpireTime())) { // 校验是否过期 - throw exception(ORDER_IS_EXPIRED); + throw exception(PAY_ORDER_IS_EXPIRED); } // 【重要】校验是否支付拓展单已支付,只是没有回调、或者数据不正常 @@ -198,7 +198,7 @@ public class PayOrderServiceImpl implements PayOrderService { if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { log.warn("[validateOrderCanSubmit][order({}) 的 extension({}) 已支付,可能是数据不一致]", id, orderExtension.getId()); - throw exception(ORDER_EXTENSION_IS_PAID); + throw exception(PAY_ORDER_EXTENSION_IS_PAID); } // 情况二:调用三方接口,查询支付单状态,是不是已支付 PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); @@ -210,7 +210,7 @@ public class PayOrderServiceImpl implements PayOrderService { if (respDTO != null && PayOrderStatusRespEnum.isSuccess(respDTO.getStatus())) { log.warn("[validateOrderCanSubmit][order({}) 的 PayOrderRespDTO({}) 已支付,可能是回调延迟]", id, toJsonString(respDTO)); - throw exception(ORDER_EXTENSION_IS_PAID); + throw exception(PAY_ORDER_EXTENSION_IS_PAID); } }); } @@ -250,9 +250,10 @@ public class PayOrderServiceImpl implements PayOrderService { * 通知并更新订单的支付结果 * * @param channel 支付渠道 - * @param notify 通知 + * @param notify 通知 */ - @Transactional(rollbackFor = Exception.class) // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyPayOrder(channel, notify) 调用,否则事务不生效 + @Transactional(rollbackFor = Exception.class) + // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyPayOrder(channel, notify) 调用,否则事务不生效 public void notifyOrder(PayChannelDO channel, PayOrderRespDTO notify) { // 情况一:支付成功的回调 if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) { @@ -291,21 +292,21 @@ public class PayOrderServiceImpl implements PayOrderService { // 1. 查询 PayOrderExtensionDO PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo()); if (orderExtension == null) { - throw exception(ORDER_EXTENSION_NOT_FOUND); + throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); } if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 log.info("[updateOrderExtensionSuccess][orderExtension({}) 已经是已支付,无需更新]", orderExtension.getId()); return orderExtension; } if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付 - throw exception(ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); } // 2. 更新 PayOrderExtensionDO int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(), PayOrderExtensionDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(toJsonString(notify)).build()); if (updateCounts == 0) { // 校验状态,必须是待支付 - throw exception(ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); } log.info("[updateOrderExtensionSuccess][orderExtension({}) 更新为已支付]", orderExtension.getId()); return orderExtension; @@ -314,9 +315,9 @@ public class PayOrderServiceImpl implements PayOrderService { /** * 更新 PayOrderDO 支付成功 * - * @param channel 支付渠道 + * @param channel 支付渠道 * @param orderExtension 支付拓展单 - * @param notify 通知回调 + * @param notify 通知回调 * @return 是否之前已经成功回调 */ private Boolean updateOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension, @@ -324,7 +325,7 @@ public class PayOrderServiceImpl implements PayOrderService { // 1. 判断 PayOrderDO 是否处于待支付 PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId()); if (order == null) { - throw exception(ORDER_NOT_FOUND); + throw exception(PAY_ORDER_NOT_FOUND); } if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功,直接返回,不用重复更新 && Objects.equals(order.getExtensionId(), orderExtension.getId())) { @@ -332,7 +333,7 @@ public class PayOrderServiceImpl implements PayOrderService { return true; } if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付 - throw exception(ORDER_STATUS_IS_NOT_WAITING); + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); } // 2. 更新 PayOrderDO @@ -345,7 +346,7 @@ public class PayOrderServiceImpl implements PayOrderService { .channelFeePrice(MoneyUtils.calculateRatePrice(order.getPrice(), channel.getFeeRate())) .build()); if (updateCounts == 0) { // 校验状态,必须是待支付 - throw exception(ORDER_STATUS_IS_NOT_WAITING); + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); } log.info("[updateOrderExtensionSuccess][order({}) 更新为已支付]", order.getId()); return false; @@ -359,7 +360,7 @@ public class PayOrderServiceImpl implements PayOrderService { // 1. 查询 PayOrderExtensionDO PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo()); if (orderExtension == null) { - throw exception(ORDER_EXTENSION_NOT_FOUND); + throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); } if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { // 如果已经是关闭,直接返回,不用重复更新 log.info("[updateOrderExtensionClosed][orderExtension({}) 已经是支付关闭,无需更新]", orderExtension.getId()); @@ -371,7 +372,7 @@ public class PayOrderServiceImpl implements PayOrderService { return; } if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付 - throw exception(ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); } // 2. 更新 PayOrderExtensionDO @@ -379,7 +380,7 @@ public class PayOrderServiceImpl implements PayOrderService { PayOrderExtensionDO.builder().status(PayOrderStatusEnum.CLOSED.getStatus()).channelNotifyData(toJsonString(notify)) .channelErrorCode(notify.getChannelErrorCode()).channelErrorMsg(notify.getChannelErrorMsg()).build()); if (updateCounts == 0) { // 校验状态,必须是待支付 - throw exception(ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); } log.info("[updateOrderExtensionClosed][orderExtension({}) 更新为支付关闭]", orderExtension.getId()); } @@ -388,10 +389,10 @@ public class PayOrderServiceImpl implements PayOrderService { public void updateOrderRefundPrice(Long id, Integer incrRefundPrice) { PayOrderDO order = orderMapper.selectById(id); if (order == null) { - throw exception(ORDER_NOT_FOUND); + throw exception(PAY_ORDER_NOT_FOUND); } if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) { - throw exception(ORDER_REFUND_FAIL_STATUS_ERROR); + throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR); } if (order.getRefundPrice() + incrRefundPrice > order.getPrice()) { throw exception(REFUND_PRICE_EXCEED); @@ -403,7 +404,7 @@ public class PayOrderServiceImpl implements PayOrderService { .setStatus(PayOrderStatusEnum.REFUND.getStatus()); int updateCount = orderMapper.updateByIdAndStatus(id, order.getStatus(), updateObj); if (updateCount == 0) { - throw exception(ORDER_REFUND_FAIL_STATUS_ERROR); + throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR); } } @@ -411,10 +412,10 @@ public class PayOrderServiceImpl implements PayOrderService { public void updatePayOrderPrice(Long id, Integer payPrice) { PayOrderDO order = orderMapper.selectById(id); if (order == null) { - throw exception(ORDER_NOT_FOUND); + throw exception(PAY_ORDER_NOT_FOUND); } if (ObjectUtil.notEqual(PayOrderStatusEnum.WAITING.getStatus(), order.getStatus())) { - throw exception(ORDER_STATUS_IS_NOT_WAITING); + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); } if (ObjectUtil.equal(order.getPrice(), payPrice)) { return; @@ -425,18 +426,6 @@ public class PayOrderServiceImpl implements PayOrderService { orderMapper.updateById(order); } - @Override - public void updatePayOrderPriceById(Long payOrderId, Integer payPrice) { - // TODO @puhui999:不能直接这样修改哈;应该只有未支付状态的订单才可以改;另外,如果价格如果没变,可以直接 return 哈; - PayOrderDO order = orderMapper.selectById(payOrderId); - if (order == null) { - throw exception(ORDER_NOT_FOUND); - } - - order.setPrice(payPrice); - orderMapper.updateById(order); - } - @Override public PayOrderExtensionDO getOrderExtension(Long id) { return orderExtensionMapper.selectById(id); 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 8a3baebb5..20855826d 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 @@ -156,11 +156,11 @@ public class PayRefundServiceImpl implements PayRefundService { private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO) { PayOrderDO order = orderService.getOrder(reqDTO.getAppId(), reqDTO.getMerchantOrderId()); if (order == null) { - throw exception(ORDER_NOT_FOUND); + throw exception(PAY_ORDER_NOT_FOUND); } // 校验状态,必须是已支付、或者已退款 if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) { - throw exception(ORDER_REFUND_FAIL_STATUS_ERROR); + throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR); } // 校验金额,退款金额不能大于原定的金额 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java new file mode 100644 index 000000000..0da38c640 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.pay.service.transfer; + +import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferSubmitReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferSubmitRespVO; + +import javax.validation.Valid; + +/** + * 转账 Service 接口 + * + * @author jason + */ +public interface PayTransferService { + + /** + * 提交转账单 + * + * 此时,会发起支付渠道的调用 + * + * @param reqVO 请求 + * @param userIp 用户 ip + * @return 渠道的返回结果 + */ + PayTransferSubmitRespVO submitTransfer(@Valid PayTransferSubmitReqVO reqVO, String userIp); + + /** + * 创建转账单 + * + * @param reqDTO 创建请求 + * @return 转账单编号 + */ + Long createTransfer(@Valid PayTransferCreateReqDTO reqDTO); + +} 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 new file mode 100644 index 000000000..cbe5b739c --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java @@ -0,0 +1,271 @@ +package cn.iocoder.yudao.module.pay.service.transfer; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum; +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum; +import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferSubmitReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferSubmitRespVO; +import cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferExtensionDO; +import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferExtensionMapper; +import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper; +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; +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 lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Objects; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum.*; + +// TODO @jason:等彻底实现完,单测写写; +/** + * 转账 Service 实现类 + * + * @author jason + */ +@Service +@Slf4j +public class PayTransferServiceImpl implements PayTransferService { + + private static final String TRANSFER_NO_PREFIX = "T"; + + @Resource + private PayTransferMapper transferMapper; + @Resource + private PayTransferExtensionMapper transferExtensionMapper; + + @Resource + private PayAppService appService; + @Resource + private PayChannelService channelService; + + @Resource + private PayNoRedisDAO noRedisDAO; + + @Override + public PayTransferSubmitRespVO submitTransfer(PayTransferSubmitReqVO reqVO, String userIp) { + // 1.1 校验转账单是否可以提交 + PayTransferDO transfer = validateTransferCanSubmit(reqVO.getId()); + // 1.2 校验转账类型和渠道是否匹配 + validateChannelCodeAndTypeMatch(reqVO.getChannelCode(), transfer.getType()); + // 1.3 校验支付渠道是否有效 + PayChannelDO channel = validateChannelCanSubmit(transfer.getAppId(), reqVO.getChannelCode()); + PayClient client = channelService.getPayClient(channel.getId()); + + // 2. 新增转账拓展单 + String no = noRedisDAO.generate(TRANSFER_NO_PREFIX); + PayTransferExtensionDO transferExtension = new PayTransferExtensionDO().setNo(no) + .setTransferId(transfer.getId()).setChannelId(channel.getId()) + .setChannelCode(channel.getCode()).setStatus(WAITING.getStatus()); + transferExtensionMapper.insert(transferExtension); + + // 3. 调用三方渠道发起转账 + PayTransferUnifiedReqDTO transferUnifiedReq = new PayTransferUnifiedReqDTO() + .setOutTransferNo(transferExtension.getNo()).setPrice(transfer.getPrice()) + .setType(transfer.getType()).setTitle(transfer.getSubject()) + .setPayeeInfo(transfer.getPayeeInfo()).setUserIp(userIp) + .setChannelExtras(reqVO.getChannelExtras()); + PayTransferRespDTO unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq); + + // 4. 通知转账结果 + getSelf().notifyTransfer(channel, unifiedTransferResp); + // 如有渠道错误码,则抛出业务异常,提示用户 + if (StrUtil.isNotEmpty(unifiedTransferResp.getChannelErrorCode())) { + throw exception(PAY_TRANSFER_SUBMIT_CHANNEL_ERROR, unifiedTransferResp.getChannelErrorCode(), + unifiedTransferResp.getChannelErrorMsg()); + } + return new PayTransferSubmitRespVO().setStatus(unifiedTransferResp.getStatus()); + } + + @Override + public Long createTransfer(PayTransferCreateReqDTO reqDTO) { + // 校验 App + appService.validPayApp(reqDTO.getAppId()); + // 创建转账单 + PayTransferDO transfer = PayTransferConvert.INSTANCE.convert(reqDTO) + .setStatus(WAITING.getStatus()); + transferMapper.insert(transfer); + return transfer.getId(); + } + + @Transactional(rollbackFor = Exception.class) + // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyTransfer(channel, notify) 调用,否则事务不生效 + public void notifyTransfer(PayChannelDO channel, PayTransferRespDTO notify) { + // 转账成功的回调 + if (PayTransferStatusRespEnum.isSuccess(notify.getStatus())) { + notifyTransferSuccess(channel, notify); + } + // 转账关闭的回调 + if (PayTransferStatusRespEnum.isClosed(notify.getStatus())) { + notifyTransferClosed(channel, notify); + } + // WAITING 状态无需处理 + // TODO IN_PROGRESS 待处理 + } + + private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) { + // 1. 更新 PayTransferExtensionDO 转账成功 + PayTransferExtensionDO transferExtension = updateTransferExtensionSuccess(notify); + + // 2. 更新 PayTransferDO 转账成功 + Boolean transferred = updateTransferSuccess(channel,transferExtension, notify); + if (transferred) { + return; + } + // 3. TODO 插入转账通知记录 + } + + private Boolean updateTransferSuccess(PayChannelDO channel, PayTransferExtensionDO transferExtension, + PayTransferRespDTO notify) { + // 1.校验 + PayTransferDO transfer = transferMapper.selectById(transferExtension.getTransferId()); + if (transfer == null) { + throw exception(PAY_TRANSFER_NOT_FOUND); + } + if (isSuccess(transfer.getStatus()) && Objects.equals(transfer.getExtensionId(), transferExtension.getId())) { + log.info("[updateTransferSuccess][transfer({}) 已经是已转账,无需更新]", transfer.getId()); + return true; + } + if (!isPendingStatus(transfer.getStatus())) { + throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); + } + // 2.更新 + int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(), + CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()), + new PayTransferDO().setStatus(SUCCESS.getStatus()).setSuccessTime(notify.getSuccessTime()) + .setChannelId(channel.getId()).setChannelCode(channel.getCode()) + .setExtensionId(transferExtension.getId()).setNo(transferExtension.getNo())); + if (updateCounts == 0) { + throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); + } + log.info("[updateTransferSuccess][transfer({}) 更新为已转账]", transfer.getId()); + return false; + } + + private PayTransferExtensionDO updateTransferExtensionSuccess(PayTransferRespDTO notify) { + // 1 校验 + PayTransferExtensionDO transferExtension = transferExtensionMapper.selectByNo(notify.getOutTransferNo()); + if (transferExtension == null) { + throw exception(PAY_TRANSFER_EXTENSION_NOT_FOUND); + } + if (isSuccess(transferExtension.getStatus())) { // 如果已成功,直接返回,不用重复更新 + log.info("[updateTransferExtensionSuccess][transferExtension({}) 已经是成功状态,无需更新]", transferExtension.getId()); + return transferExtension; + } + if (!isPendingStatus(transferExtension.getStatus())) { + throw exception(PAY_TRANSFER_EXTENSION_STATUS_IS_NOT_PENDING); + } + // 2. 更新 PayTransferExtensionDO + int updateCount = transferExtensionMapper.updateByIdAndStatus(transferExtension.getId(), + CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()), + new PayTransferExtensionDO().setStatus(SUCCESS.getStatus()) + .setChannelNotifyData(JsonUtils.toJsonString(notify))); + if (updateCount == 0) { + throw exception(PAY_TRANSFER_EXTENSION_STATUS_IS_NOT_PENDING); + } + log.info("[updateTransferExtensionSuccess][transferExtension({}) 更新为已转账]", transferExtension.getId()); + return transferExtension; + } + + private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) { + // 更新 PayTransferExtensionDO 转账关闭 + updateTransferExtensionClosed(notify); + } + + private void updateTransferExtensionClosed(PayTransferRespDTO notify) { + // 1 校验 + PayTransferExtensionDO transferExtension = transferExtensionMapper.selectByNo(notify.getOutTransferNo()); + if (transferExtension == null) { + throw exception(PAY_TRANSFER_EXTENSION_NOT_FOUND); + } + if (isClosed(transferExtension.getStatus())) { // 如果已是关闭状态,直接返回,不用重复更新 + log.info("[updateTransferExtensionSuccess][transferExtension({}) 已经是关闭状态,无需更新]", transferExtension.getId()); + return; + } + if (!isPendingStatus(transferExtension.getStatus())) { + throw exception(PAY_TRANSFER_EXTENSION_STATUS_IS_NOT_PENDING); + } + // 2. 更新 PayTransferExtensionDO + int updateCount = transferExtensionMapper.updateByIdAndStatus(transferExtension.getId(), + CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()), + new PayTransferExtensionDO().setStatus(CLOSED.getStatus()) + .setChannelNotifyData(JsonUtils.toJsonString(notify))); + if (updateCount == 0) { + throw exception(PAY_TRANSFER_EXTENSION_STATUS_IS_NOT_PENDING); + } + log.info("[updateTransferExtensionSuccess][transferExtension({}) 更新为关闭状态]", transferExtension.getId()); + } + + private void validateChannelCodeAndTypeMatch(String channelCode, Integer type) { + PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(type); + PayChannelEnum payChannel = PayChannelEnum.getByCode(channelCode); + switch (transferType) { + case ALIPAY_BALANCE: { + // TODO @jason:可以抽到 PayChannelEnum 里,isAlipay? 类似这种哈 + if (!payChannel.getCode().startsWith("alipay")) { + throw exception(PAY_TRANSFER_TYPE_AND_CHANNEL_NOT_MATCH); + } + break; + } + case WX_BALANCE: + case BANK_CARD: + case WALLET_BALANCE: { + throw new UnsupportedOperationException("待实现"); + } + } + } + + private PayChannelDO validateChannelCanSubmit(Long appId, String channelCode) { + // 校验 App + appService.validPayApp(appId); + // 校验支付渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(appId, channelCode); + PayClient client = channelService.getPayClient(channel.getId()); + if (client == null) { + log.error("[validateChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); + throw exception(CHANNEL_NOT_FOUND); + } + return channel; + } + + private PayTransferDO validateTransferCanSubmit(Long id) { + PayTransferDO transfer = transferMapper.selectById(id); + if (transfer == null) { // 是否存在 + throw exception(PAY_TRANSFER_NOT_FOUND); + } + if (PayTransferStatusEnum.isSuccess(transfer.getStatus())) { + throw exception(PAY_TRANSFER_STATUS_IS_SUCCESS); + } + if (!PayTransferStatusEnum.isWaiting(transfer.getStatus())) { + throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING); + } + // TODO 查询拓展单是否未已转账和转账中 + return transfer; + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private PayTransferServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java new file mode 100644 index 000000000..235d2a908 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageService.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackagePageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageUpdateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; + +import javax.validation.Valid; + +/** + * 钱包充值套餐 Service 接口 + * + * @author jason + */ +public interface PayWalletRechargePackageService { + + /** + * 获取钱包充值套餐 + * @param packageId 充值套餐编号 + */ + PayWalletRechargePackageDO getWalletRechargePackage(Long packageId); + + /** + * 校验钱包充值套餐的有效性, 无效的话抛出 ServiceException 异常 + * + * @param packageId 充值套餐编号 + */ + PayWalletRechargePackageDO validWalletRechargePackage(Long packageId); + + /** + * 创建充值套餐 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createWalletRechargePackage(@Valid WalletRechargePackageCreateReqVO createReqVO); + + /** + * 更新充值套餐 + * + * @param updateReqVO 更新信息 + */ + void updateWalletRechargePackage(@Valid WalletRechargePackageUpdateReqVO updateReqVO); + + /** + * 删除充值套餐 + * + * @param id 编号 + */ + void deleteWalletRechargePackage(Long id); + + /** + * 获得充值套餐分页 + * + * @param pageReqVO 分页查询 + * @return 充值套餐分页 + */ + PageResult getWalletRechargePackagePage(WalletRechargePackagePageReqVO pageReqVO); + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java new file mode 100644 index 000000000..12224ed4c --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargePackageServiceImpl.java @@ -0,0 +1,106 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackagePageReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.rechargepackage.WalletRechargePackageUpdateReqVO; +import cn.iocoder.yudao.module.pay.convert.wallet.WalletRechargePackageConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; +import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletRechargePackageMapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; + +/** + * 钱包充值套餐 Service 实现类 + * + * @author jason + */ +@Service +public class PayWalletRechargePackageServiceImpl implements PayWalletRechargePackageService { + + @Resource + private PayWalletRechargePackageMapper walletRechargePackageMapper; + + @Override + public PayWalletRechargePackageDO getWalletRechargePackage(Long packageId) { + return walletRechargePackageMapper.selectById(packageId); + } + + @Override + public PayWalletRechargePackageDO validWalletRechargePackage(Long packageId) { + PayWalletRechargePackageDO rechargePackageDO = walletRechargePackageMapper.selectById(packageId); + if (rechargePackageDO == null) { + throw exception(WALLET_RECHARGE_PACKAGE_NOT_FOUND); + } + if (CommonStatusEnum.DISABLE.getStatus().equals(rechargePackageDO.getStatus())) { + throw exception(WALLET_RECHARGE_PACKAGE_IS_DISABLE); + } + return rechargePackageDO; + } + + @Override + public Long createWalletRechargePackage(WalletRechargePackageCreateReqVO createReqVO) { + // 校验套餐名是否唯一 + validateRechargePackageNameUnique(null, createReqVO.getName()); + + // 插入 + PayWalletRechargePackageDO walletRechargePackage = WalletRechargePackageConvert.INSTANCE.convert(createReqVO); + walletRechargePackageMapper.insert(walletRechargePackage); + // 返回 + return walletRechargePackage.getId(); + } + + @Override + public void updateWalletRechargePackage(WalletRechargePackageUpdateReqVO updateReqVO) { + // 校验存在 + validateWalletRechargePackageExists(updateReqVO.getId()); + // 校验套餐名是否唯一 + validateRechargePackageNameUnique(updateReqVO.getId(), updateReqVO.getName()); + + // 更新 + PayWalletRechargePackageDO updateObj = WalletRechargePackageConvert.INSTANCE.convert(updateReqVO); + walletRechargePackageMapper.updateById(updateObj); + } + + private void validateRechargePackageNameUnique(Long id, String name) { + if (StrUtil.isBlank(name)) { + return; + } + PayWalletRechargePackageDO rechargePackage = walletRechargePackageMapper.selectByName(name); + if (rechargePackage == null) { + return ; + } + if (id == null) { + throw exception(WALLET_RECHARGE_PACKAGE_NAME_EXISTS); + } + if (!id.equals(rechargePackage.getId())) { + throw exception(WALLET_RECHARGE_PACKAGE_NAME_EXISTS); + } + } + + @Override + public void deleteWalletRechargePackage(Long id) { + // 校验存在 + validateWalletRechargePackageExists(id); + // 删除 + walletRechargePackageMapper.deleteById(id); + } + + private void validateWalletRechargePackageExists(Long id) { + if (walletRechargePackageMapper.selectById(id) == null) { + throw exception(WALLET_RECHARGE_PACKAGE_NOT_FOUND); + } + } + + @Override + public PageResult getWalletRechargePackagePage(WalletRechargePackagePageReqVO pageReqVO) { + return walletRechargePackageMapper.selectPage(pageReqVO); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java new file mode 100644 index 000000000..752ce89af --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; + +/** + * 钱包充值 Service 接口 + * + * @author jason + */ +public interface PayWalletRechargeService { + + /** + * 创建钱包充值记录(发起充值) + * + * @param userId 用户 id + * @param userType 用户类型 + * @param createReqVO 钱包充值请求 VO + * @param userIp 用户Ip + * @return 钱包充值记录 + */ + PayWalletRechargeDO createWalletRecharge(Long userId, Integer userType, String userIp, + AppPayWalletRechargeCreateReqVO createReqVO); + + /** + * 更新钱包充值成功 + * + * @param id 钱包充值记录 id + * @param payOrderId 支付订单 id + */ + void updateWalletRechargerPaid(Long id, Long payOrderId); + + /** + * 发起钱包充值退款 + * + * @param id 钱包充值编号 + * @param userIp 用户 ip 地址 + */ + void refundWalletRecharge(Long id, String userIp); + + /** + * 更新钱包充值记录为已退款 + * + * @param id 钱包充值 id + * @param payRefundId 退款单id + */ + void updateWalletRechargeRefunded(Long id, Long payRefundId); + +} 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 new file mode 100644 index 000000000..1c6e10611 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java @@ -0,0 +1,281 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; +import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; +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.service.order.PayOrderService; +import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Objects; + +import static cn.hutool.core.util.ObjectUtil.notEqual; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; +import static cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargeConvert.INSTANCE; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum.*; + +/** + * 钱包充值 Service 实现类 + * + * @author jason + */ +@Service +@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 + private PayWalletRechargeMapper walletRechargeMapper; + @Resource + private PayWalletService payWalletService; + @Resource + private PayOrderService payOrderService; + @Resource + private PayRefundService payRefundService; + @Resource + private PayWalletRechargePackageService payWalletRechargePackageService; + + @Override + @Transactional(rollbackFor = Exception.class) + public PayWalletRechargeDO createWalletRecharge(Long userId, Integer userType, String userIp, + AppPayWalletRechargeCreateReqVO reqVO) { + + if (Objects.isNull(reqVO.getPayPrice()) && Objects.isNull(reqVO.getPackageId())) { + // TODO @jason @AssertTrue 貌似没有效果。需要查下原因 + throw exception(WALLET_RECHARGE_PACKAGE_AND_PRICE_IS_EMPTY); + } + // 1.1 计算充值金额 + int payPrice; + int bonusPrice = 0; + if (Objects.nonNull(reqVO.getPackageId())) { + PayWalletRechargePackageDO rechargePackage = payWalletRechargePackageService.validWalletRechargePackage(reqVO.getPackageId()); + payPrice = rechargePackage.getPayPrice(); + bonusPrice = rechargePackage.getBonusPrice(); + } else { + payPrice = reqVO.getPayPrice(); + } + // 1.2 插入充值记录 + PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType); + PayWalletRechargeDO recharge = INSTANCE.convert(wallet.getId(), payPrice, bonusPrice, reqVO.getPackageId()); + walletRechargeMapper.insert(recharge); + + // 2.1 创建支付单 + Long payOrderId = payOrderService.createOrder(new PayOrderCreateReqDTO() + .setAppId(WALLET_PAY_APP_ID).setUserIp(userIp) + .setMerchantOrderId(recharge.getId().toString()) // 业务的订单编号 + .setSubject(WALLET_RECHARGE_ORDER_SUBJECT).setBody("") + .setPrice(recharge.getPayPrice()) + .setExpireTime(addTime(Duration.ofHours(2L)))); // TODO @芋艿:支付超时时间 + // 2.2 更新钱包充值记录中支付订单 + walletRechargeMapper.updateById(new PayWalletRechargeDO().setId(recharge.getId()).setPayOrderId(payOrderId)); + recharge.setPayOrderId(payOrderId); + return recharge; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateWalletRechargerPaid(Long id, Long payOrderId) { + // 1.1 获取钱包充值记录 + PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id); + if (walletRecharge == null) { + log.error("[updateWalletRechargerPaid][钱包充值记录不存在,钱包充值记录 id({})]", id); + throw exception(WALLET_RECHARGE_NOT_FOUND); + } + // 1.2 校验钱包充值是否可以支付 + PayOrderDO payOrderDO = validateWalletRechargerCanPaid(walletRecharge, payOrderId); + + // 2. 更新钱包充值的支付状态 + int updateCount = walletRechargeMapper.updateByIdAndPaid(id, false, + new PayWalletRechargeDO().setId(id).setPayStatus(true).setPayTime(LocalDateTime.now()) + .setPayChannelCode(payOrderDO.getChannelCode())); + if (updateCount == 0) { + throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID); + } + + // 3. 更新钱包余额 + // TODO @jason:这样的话,未来提现会不会把充值的,也提现走哈。类似先充 100,送 110;然后提现 110; + // TODO 需要钱包中加个可提现余额 + payWalletService.addWalletBalance(walletRecharge.getWalletId(), String.valueOf(id), + PayWalletBizTypeEnum.RECHARGE, walletRecharge.getTotalPrice()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void refundWalletRecharge(Long id, String userIp) { + // 1.1 获取钱包充值记录 + PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id); + if (walletRecharge == null) { + log.error("[refundWalletRecharge][钱包充值记录不存在,钱包充值记录 id({})]", id); + throw exception(WALLET_RECHARGE_NOT_FOUND); + } + // 1.2 校验钱包充值是否可以发起退款 + PayWalletDO wallet = validateWalletRechargeCanRefund(walletRecharge); + + // 2. 冻结退款的余额,暂时只处理赠送的余额也全部退回 + payWalletService.freezePrice(wallet.getId(), walletRecharge.getTotalPrice()); + + // 3. 创建退款单 + String walletRechargeId = String.valueOf(id); + String refundId = walletRechargeId + "-refund"; + Long payRefundId = payRefundService.createPayRefund(new PayRefundCreateReqDTO() + .setAppId(WALLET_PAY_APP_ID).setUserIp(userIp) + .setMerchantOrderId(walletRechargeId) + .setMerchantRefundId(refundId) + .setReason("想退钱").setPrice(walletRecharge.getPayPrice())); + + // 4. 更新充值记录退款单号 + // TODO @jaosn:一般新建这种 update 对象,建议是,第一个 set id 属性,容易知道以它为更新 + walletRechargeMapper.updateById(new PayWalletRechargeDO().setPayRefundId(payRefundId) + .setRefundStatus(WAITING.getStatus()).setId(walletRecharge.getId())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateWalletRechargeRefunded(Long id, Long payRefundId) { + // 1.1 获取钱包充值记录 + PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id); + if (walletRecharge == null) { + log.error("[updateWalletRechargerPaid][钱包充值记录不存在,钱包充值记录 id({})]", id); + throw exception(WALLET_RECHARGE_NOT_FOUND); + } + // 1.2 校验钱包充值是否可以更新已退款 + PayRefundDO payRefund = validateWalletRechargeCanRefunded(walletRecharge, payRefundId); + + PayWalletRechargeDO updateObj = new PayWalletRechargeDO().setId(id); + // 退款成功 + if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) { + // 2.1 更新钱包余额 + payWalletService.reduceWalletBalance(walletRecharge.getWalletId(), id, + PayWalletBizTypeEnum.RECHARGE_REFUND, walletRecharge.getTotalPrice()); + + updateObj.setRefundStatus(SUCCESS.getStatus()).setRefundTime(payRefund.getSuccessTime()) + .setRefundTotalPrice(walletRecharge.getTotalPrice()).setRefundPayPrice(walletRecharge.getPayPrice()) + .setRefundBonusPrice(walletRecharge.getBonusPrice()); + } + // 退款失败 + if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) { + // 2.2 解冻余额 + payWalletService.unfreezePrice(walletRecharge.getWalletId(), walletRecharge.getTotalPrice()); + + updateObj.setRefundStatus(FAILURE.getStatus()); + } + // 3. 更新钱包充值的退款字段 + walletRechargeMapper.updateByIdAndRefunded(id, WAITING.getStatus(), updateObj); + } + + private PayRefundDO validateWalletRechargeCanRefunded(PayWalletRechargeDO walletRecharge, Long payRefundId) { + // 1. 校验退款订单匹配 + if (notEqual(walletRecharge.getPayRefundId(), payRefundId)) { + log.error("[validateWalletRechargeCanRefunded][钱包充值({}) 退款单不匹配({}),请进行处理!钱包充值的数据是:{}]", + walletRecharge.getId(), payRefundId, toJsonString(walletRecharge)); + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR); + } + + // 2.1 校验退款订单 + PayRefundDO payRefund = payRefundService.getRefund(payRefundId); + if (payRefund == null) { + log.error("[validateWalletRechargeCanRefunded][payRefund({})不存在]", payRefundId); + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND); + } + // 2.2 校验退款金额一致 + if (notEqual(payRefund.getRefundPrice(), walletRecharge.getPayPrice())) { + log.error("[validateWalletRechargeCanRefunded][钱包({}) payRefund({}) 退款金额不匹配,请进行处理!钱包数据是:{},payRefund 数据是:{}]", + walletRecharge.getId(), payRefundId, toJsonString(walletRecharge), toJsonString(payRefund)); + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH); + } + // 2.3 校验退款订单商户订单是否匹配 + if (notEqual(payRefund.getMerchantOrderId(), walletRecharge.getId().toString())) { + log.error("[validateWalletRechargeCanRefunded][钱包({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]", + walletRecharge.getId(), payRefundId, toJsonString(payRefund)); + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR); + } + return payRefund; + } + + private PayWalletDO validateWalletRechargeCanRefund(PayWalletRechargeDO walletRecharge) { + // 校验充值订单是否支付 + if (!walletRecharge.getPayStatus()) { + throw exception(WALLET_RECHARGE_REFUND_FAIL_NOT_PAID); + } + // 校验充值订单是否已退款 + if (walletRecharge.getPayRefundId() != null) { + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUNDED); + } + // 校验钱包余额是否足够 + PayWalletDO wallet = payWalletService.getWallet(walletRecharge.getWalletId()); + Assert.notNull(wallet, "用户钱包({}) 不存在", wallet.getId()); + if (wallet.getBalance() < walletRecharge.getTotalPrice()) { + throw exception(WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH); + } + // TODO @芋艿:需要考虑下,赠送的金额,会不会导致提现超过; + return wallet; + } + + private PayOrderDO validateWalletRechargerCanPaid(PayWalletRechargeDO walletRecharge, Long payOrderId) { + // 1.1 校验充值记录的支付状态 + if (walletRecharge.getPayStatus()) { + log.error("[validateWalletRechargerCanPaid][钱包({}) 不处于未支付状态! 钱包数据是:{}]", + walletRecharge.getId(), toJsonString(walletRecharge)); + throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID); + } + // 1.2 校验支付订单匹配 + if (notEqual(walletRecharge.getPayOrderId(), payOrderId)) { // 支付单号 + log.error("[validateWalletRechargerCanPaid][钱包({}) 支付单不匹配({}),请进行处理! 钱包数据是:{}]", + walletRecharge.getId(), payOrderId, toJsonString(walletRecharge)); + throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR); + } + + // 2.1 校验支付单是否存在 + PayOrderDO payOrder = payOrderService.getOrder(payOrderId); + if (payOrder == null) { + log.error("[validateWalletRechargerCanPaid][钱包({}) payOrder({}) 不存在,请进行处理!]", + walletRecharge.getId(), payOrderId); + throw exception(PAY_ORDER_NOT_FOUND); + } + // 2.2 校验支付单已支付 + if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) { + log.error("[validateWalletRechargerCanPaid][钱包({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]", + walletRecharge.getId(), payOrderId, toJsonString(payOrder)); + throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS); + } + // 2.3 校验支付金额一致 + if (notEqual(payOrder.getPrice(), walletRecharge.getPayPrice())) { + log.error("[validateDemoOrderCanPaid][钱包({}) payOrder({}) 支付金额不匹配,请进行处理!钱包 数据是:{},payOrder 数据是:{}]", + walletRecharge.getId(), payOrderId, toJsonString(walletRecharge), toJsonString(payOrder)); + throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH); + } + // 2.4 校验支付订单的商户订单匹配 + if (notEqual(payOrder.getMerchantOrderId(), walletRecharge.getId().toString())) { + log.error("[validateDemoOrderCanPaid][钱包({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]", + walletRecharge.getId(), payOrderId, toJsonString(payOrder)); + throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR); + } + return payOrder; + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java new file mode 100644 index 000000000..d9abe958d --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java @@ -0,0 +1,101 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; + +/** + * 钱包 Service 接口 + * + * @author jason + */ +public interface PayWalletService { + + /** + * 获取钱包信息 + *

+ * 如果不存在,则创建钱包。由于用户注册时候不会创建钱包 + * + * @param userId 用户编号 + * @param userType 用户类型 + */ + PayWalletDO getOrCreateWallet(Long userId, Integer userType); + + /** + * 获取钱包信息 + * + * @param walletId 钱包 id + */ + PayWalletDO getWallet(Long walletId); + + + /** + * 获得会员钱包分页 + * + * @param pageReqVO 分页查询 + * @return 会员钱包分页 + */ + PageResult getWalletPage(Integer userType, PayWalletPageReqVO pageReqVO); + + /** + * 钱包订单支付 + * + * @param userId 用户 id + * @param userType 用户类型 + * @param outTradeNo 外部订单号 + * @param price 金额 + */ + PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price); + + /** + * 钱包订单支付退款 + * + * @param outRefundNo 外部退款号 + * @param refundPrice 退款金额 + * @param reason 退款原因 + */ + PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason); + + /** + * 扣减钱包余额 + * + * @param walletId 钱包 id + * @param bizId 业务关联 id + * @param bizType 业务关联分类 + * @param price 扣减金额 + * @return 钱包流水 + */ + PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId, + PayWalletBizTypeEnum bizType, Integer price); + + /** + * 增加钱包余额 + * + * @param walletId 钱包 id + * @param bizId 业务关联 id + * @param bizType 业务关联分类 + * @param price 增加金额 + * @return 钱包流水 + */ + PayWalletTransactionDO addWalletBalance(Long walletId, String bizId, + PayWalletBizTypeEnum bizType, Integer price); + + /** + * 冻结钱包部分余额 + * + * @param id 钱包编号 + * @param price 冻结金额 + */ + void freezePrice(Long id, Integer price); + + /** + * 解冻钱包余额 + * + * @param id 钱包编号 + * @param price 解冻金额 + */ + void unfreezePrice(Long id, Integer price); + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java new file mode 100644 index 000000000..db4ad647a --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java @@ -0,0 +1,206 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; +import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum.PAYMENT; +import static cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum.PAYMENT_REFUND; + +/** + * 钱包 Service 实现类 + * + * @author jason + */ +@Service +@Slf4j +public class PayWalletServiceImpl implements PayWalletService { + + @Resource + private PayWalletMapper walletMapper; + @Resource + private PayWalletTransactionService walletTransactionService; + @Resource + @Lazy + private PayOrderService orderService; + @Resource + @Lazy + private PayRefundService refundService; + + @Override + public PayWalletDO getOrCreateWallet(Long userId, Integer userType) { + PayWalletDO wallet = walletMapper.selectByUserIdAndType(userId, userType); + if (wallet == null) { + wallet = new PayWalletDO().setUserId(userId).setUserType(userType) + .setBalance(0).setTotalExpense(0).setTotalRecharge(0); + wallet.setCreateTime(LocalDateTime.now()); + walletMapper.insert(wallet); + } + return wallet; + } + + @Override + public PayWalletDO getWallet(Long walletId) { + return walletMapper.selectById(walletId); + } + + @Override + public PageResult getWalletPage(Integer userType,PayWalletPageReqVO pageReqVO) { + return walletMapper.selectPage(userType, pageReqVO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price) { + // 1. 判断支付交易拓展单是否存 + PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo); + if (orderExtension == null) { + throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); + } + PayWalletDO wallet = getOrCreateWallet(userId, userType); + // 2. 扣减余额 + return reduceWalletBalance(wallet.getId(), orderExtension.getOrderId(), PAYMENT, price); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason) { + // 1.1 判断退款单是否存在 + PayRefundDO payRefund = refundService.getRefundByNo(outRefundNo); + if (payRefund == null) { + throw exception(REFUND_NOT_FOUND); + } + // 1.2 校验是否可以退款 + Long walletId = validateWalletCanRefund(payRefund.getId(), payRefund.getChannelOrderNo()); + PayWalletDO wallet = walletMapper.selectById(walletId); + Assert.notNull(wallet, "钱包 {} 不存在", walletId); + + // 2. 增加余额 + return addWalletBalance(walletId, String.valueOf(payRefund.getId()), PAYMENT_REFUND, refundPrice); + } + + /** + * 校验是否能退款 + * + * @param refundId 支付退款单 id + * @param walletPayNo 钱包支付 no + */ + private Long validateWalletCanRefund(Long refundId, String walletPayNo) { + // 1. 校验钱包支付交易存在 + PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransactionByNo(walletPayNo); + if (walletTransaction == null) { + throw exception(WALLET_TRANSACTION_NOT_FOUND); + } + // 2. 校验退款是否存在 + PayWalletTransactionDO refundTransaction = walletTransactionService.getWalletTransaction( + String.valueOf(refundId), PAYMENT_REFUND); + if (refundTransaction != null) { + throw exception(WALLET_REFUND_EXIST); + } + return walletTransaction.getWalletId(); + } + + @Override + public PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId, + PayWalletBizTypeEnum bizType, Integer price) { + // 1. 获取钱包 + PayWalletDO payWallet = getWallet(walletId); + if (payWallet == null) { + log.error("[reduceWalletBalance],用户钱包({})不存在.", walletId); + throw exception(WALLET_NOT_FOUND); + } + + // 2.1 扣除余额 + int updateCounts; + switch (bizType) { + case PAYMENT: { + updateCounts = walletMapper.updateWhenConsumption(payWallet.getId(), price); + break; + } + case RECHARGE_REFUND: { + updateCounts = walletMapper.updateWhenRechargeRefund(payWallet.getId(), price); + break; + } + default: { + // TODO 其它类型待实现 + throw new UnsupportedOperationException("待实现"); + } + } + if (updateCounts == 0) { + throw exception(WALLET_BALANCE_NOT_ENOUGH); + } + // 2.2 生成钱包流水 + Integer afterBalance = payWallet.getBalance() - price; + WalletTransactionCreateReqBO bo = new WalletTransactionCreateReqBO().setWalletId(payWallet.getId()) + .setPrice(-price).setBalance(afterBalance).setBizId(String.valueOf(bizId)) + .setBizType(bizType.getType()).setTitle(bizType.getDescription()); + return walletTransactionService.createWalletTransaction(bo); + } + + @Override + public PayWalletTransactionDO addWalletBalance(Long walletId, String bizId, + PayWalletBizTypeEnum bizType, Integer price) { + // 1.1 获取钱包 + PayWalletDO payWallet = getWallet(walletId); + if (payWallet == null) { + log.error("[addWalletBalance],用户钱包({})不存在.", walletId); + throw exception(WALLET_NOT_FOUND); + } + // 1.2 更新钱包金额 + switch (bizType) { + case PAYMENT_REFUND: { // 退款更新 + walletMapper.updateWhenConsumptionRefund(payWallet.getId(), price); + break; + } + case RECHARGE: { // 充值更新 + walletMapper.updateWhenRecharge(payWallet.getId(), price); + break; + } + default: { + // TODO 其它类型待实现 + throw new UnsupportedOperationException("待实现"); + } + } + + // 2. 生成钱包流水 + WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO() + .setWalletId(payWallet.getId()).setPrice(price).setBalance(payWallet.getBalance() + price) + .setBizId(bizId).setBizType(bizType.getType()).setTitle(bizType.getDescription()); + return walletTransactionService.createWalletTransaction(transactionCreateReqBO); + } + + @Override + public void freezePrice(Long id, Integer price) { + int updateCounts = walletMapper.freezePrice(id, price); + if (updateCounts == 0) { + throw exception(WALLET_BALANCE_NOT_ENOUGH); + } + } + + @Override + public void unfreezePrice(Long id, Integer price) { + int updateCounts = walletMapper.unFreezePrice(id, price); + if (updateCounts == 0) { + throw exception(WALLET_FREEZE_PRICE_NOT_ENOUGH); + } + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java new file mode 100644 index 000000000..21fc0dc18 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java @@ -0,0 +1,60 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +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; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO; + +import javax.validation.Valid; + +/** + * 钱包余额流水 Service 接口 + * + * @author jason + */ +public interface PayWalletTransactionService { + + /** + * 查询钱包余额流水分页 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param pageVO 分页查询参数 + */ + PageResult getWalletTransactionPage(Long userId, Integer userType, + AppPayWalletTransactionPageReqVO pageVO); + + /** + * 查询钱包余额流水分页 + * + * @param pageVO 分页查询参数 + */ + PageResult getWalletTransactionPage(PayWalletTransactionPageReqVO pageVO); + + /** + * 新增钱包余额流水 + * + * @param bo 创建钱包流水 bo + * @return 新建的钱包 do + */ + PayWalletTransactionDO createWalletTransaction(@Valid WalletTransactionCreateReqBO bo); + + /** + * 根据 no,获取钱包余流水 + * + * @param no 流水号 + */ + PayWalletTransactionDO getWalletTransactionByNo(String no); + + /** + * 获取钱包流水 + * + * @param bizId 业务编号 + * @param type 业务类型 + * @return 钱包流水 + */ + PayWalletTransactionDO getWalletTransaction(String bizId, PayWalletBizTypeEnum type); + +} 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 new file mode 100644 index 000000000..357ac6a4e --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java @@ -0,0 +1,71 @@ +package cn.iocoder.yudao.module.pay.service.wallet; + +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; +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletTransactionConvert; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; +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 lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; + +/** + * 钱包流水 Service 实现类 + * + * @author jason + */ +@Service +@Slf4j +@Validated +public class PayWalletTransactionServiceImpl implements PayWalletTransactionService { + + /** + * 钱包流水的 no 前缀 + */ + private static final String WALLET_NO_PREFIX = "W"; + + @Resource + private PayWalletService payWalletService; + @Resource + private PayWalletTransactionMapper payWalletTransactionMapper; + @Resource + private PayNoRedisDAO noRedisDAO; + + @Override + public PageResult getWalletTransactionPage(Long userId, Integer userType, + AppPayWalletTransactionPageReqVO pageVO) { + PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType); + return payWalletTransactionMapper.selectPage(wallet.getId(), pageVO.getType(), pageVO); + } + + @Override + public PageResult getWalletTransactionPage(PayWalletTransactionPageReqVO pageVO) { + return payWalletTransactionMapper.selectPage(pageVO.getWalletId(), null, pageVO); + } + + @Override + public PayWalletTransactionDO createWalletTransaction(WalletTransactionCreateReqBO bo) { + PayWalletTransactionDO transaction = PayWalletTransactionConvert.INSTANCE.convert(bo) + .setNo(noRedisDAO.generate(WALLET_NO_PREFIX)); + payWalletTransactionMapper.insert(transaction); + return transaction; + } + + @Override + public PayWalletTransactionDO getWalletTransactionByNo(String no) { + return payWalletTransactionMapper.selectByNo(no); + } + + @Override + public PayWalletTransactionDO getWalletTransaction(String bizId, PayWalletBizTypeEnum type) { + return payWalletTransactionMapper.selectByBiz(bizId, type.getType()); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java new file mode 100644 index 000000000..a305ceb0d --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/bo/WalletTransactionCreateReqBO.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.pay.service.wallet.bo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + * 创建钱包流水 BO + * + * @author jason + */ +@Data +public class WalletTransactionCreateReqBO { + + /** + * 钱包编号 + * + */ + @NotNull(message = "钱包编号不能为空") + private Long walletId; + + /** + * 交易金额,单位分 + * + * 正值表示余额增加,负值表示余额减少 + */ + @NotNull(message = "交易金额不能为空") + private Integer price; + + /** + * 交易后余额,单位分 + */ + @NotNull(message = "交易后余额不能为空") + private Integer balance; + + /** + * 关联业务分类 + * + * 枚举 {@link PayWalletBizTypeEnum#getType()} + */ + @NotNull(message = "关联业务分类不能为空") + @InEnum(PayWalletBizTypeEnum.class) + private Integer bizType; + + /** + * 关联业务编号 + */ + @NotEmpty(message = "关联业务编号不能为空") + private String bizId; + + /** + * 流水说明 + */ + @NotEmpty(message = "流水说明不能为空") + private String title; +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/resources/application-local.yaml b/yudao-module-pay/yudao-module-pay-biz/src/main/resources/application-local.yaml index a3ad89db2..3415865ad 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/resources/application-local.yaml +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/resources/application-local.yaml @@ -74,7 +74,7 @@ spring: --- #################### 定时任务相关配置 #################### xxl: job: - enabled: true # 是否开启调度中心,默认为 true 开启 + enabled: false # 是否开启调度中心,默认为 true 开启 admin: addresses: http://127.0.0.1:8000/xxl-job-admin # 调度中心部署跟地址 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 6a5f15ce3..c61672b44 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 @@ -261,7 +261,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { String userIp = randomString(); // 调用, 并断言异常 - assertServiceException(() -> orderService.submitOrder(reqVO, userIp), ORDER_NOT_FOUND); + assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_NOT_FOUND); } @Test @@ -274,7 +274,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { String userIp = randomString(); // 调用, 并断言异常 - assertServiceException(() -> orderService.submitOrder(reqVO, userIp), ORDER_STATUS_IS_NOT_WAITING); + assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_STATUS_IS_NOT_WAITING); } @Test @@ -287,7 +287,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { String userIp = randomString(); // 调用, 并断言异常 - assertServiceException(() -> orderService.submitOrder(reqVO, userIp), ORDER_STATUS_IS_SUCCESS); + assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_STATUS_IS_SUCCESS); } @Test @@ -301,7 +301,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { String userIp = randomString(); // 调用, 并断言异常 - assertServiceException(() -> orderService.submitOrder(reqVO, userIp), ORDER_IS_EXPIRED); + assertServiceException(() -> orderService.submitOrder(reqVO, userIp), PAY_ORDER_IS_EXPIRED); } @Test @@ -366,7 +366,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 调用,并断言异常 assertServiceException(() -> orderService.submitOrder(reqVO, userIp), - ORDER_SUBMIT_CHANNEL_ERROR, "001", "模拟异常"); + PAY_ORDER_SUBMIT_CHANNEL_ERROR, "001", "模拟异常"); // 断言,数据记录(PayOrderExtensionDO) PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null); assertNotNull(orderExtension); @@ -450,7 +450,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 调用,并断言异常 assertServiceException(() -> orderService.validateOrderActuallyPaid(id), - ORDER_EXTENSION_IS_PAID); + PAY_ORDER_EXTENSION_IS_PAID); } @Test @@ -469,7 +469,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 调用,并断言异常 assertServiceException(() -> orderService.validateOrderActuallyPaid(id), - ORDER_EXTENSION_IS_PAID); + PAY_ORDER_EXTENSION_IS_PAID); } @Test @@ -519,7 +519,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 调用,并断言异常 assertServiceException(() -> orderService.notifyOrder(channel, notify), - ORDER_EXTENSION_NOT_FOUND); + PAY_ORDER_EXTENSION_NOT_FOUND); } @Test @@ -537,7 +537,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 调用,并断言异常 assertServiceException(() -> orderService.notifyOrder(channel, notify), - ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); } @Test @@ -555,7 +555,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 调用,并断言异常 assertServiceException(() -> orderService.notifyOrder(channel, notify), - ORDER_NOT_FOUND); + PAY_ORDER_NOT_FOUND); // 断言 PayOrderExtensionDO :数据更新被回滚 assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null)); } @@ -588,7 +588,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 调用,并断言异常 assertServiceException(() -> orderService.notifyOrder(channel, notify), - ORDER_STATUS_IS_NOT_WAITING); + PAY_ORDER_STATUS_IS_NOT_WAITING); // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null)); } @@ -661,7 +661,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { "updateTime", "updater"); // 断言,调用 verify(notifyService).createPayNotifyTask(eq(PayNotifyTypeEnum.ORDER.getType()), - eq(orderExtension.getOrderId())); + eq(orderExtension.getOrderId())); } @Test @@ -673,7 +673,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 调用,并断言异常 assertServiceException(() -> orderService.notifyOrder(channel, notify), - ORDER_EXTENSION_NOT_FOUND); + PAY_ORDER_EXTENSION_NOT_FOUND); } @Test @@ -729,7 +729,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 调用,并断言异常 assertServiceException(() -> orderService.notifyOrder(channel, notify), - ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); } @Test @@ -762,7 +762,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 调用,并断言异常 assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice), - ORDER_NOT_FOUND); + PAY_ORDER_NOT_FOUND); } @Test @@ -786,7 +786,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { // 调用,并断言异常 assertServiceException(() -> orderService.updateOrderRefundPrice(id, incrRefundPrice), - ORDER_REFUND_FAIL_STATUS_ERROR); + PAY_ORDER_REFUND_FAIL_STATUS_ERROR); } @Test 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 b0b31fbc4..58a086c6b 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 @@ -219,7 +219,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { // 调用,并断言异常 assertServiceException(() -> refundService.createPayRefund(reqDTO), - ORDER_NOT_FOUND); + PAY_ORDER_NOT_FOUND); } @Test @@ -245,7 +245,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { // 调用,并断言异常 assertServiceException(() -> refundService.createPayRefund(reqDTO), - ORDER_REFUND_FAIL_STATUS_ERROR); + PAY_ORDER_REFUND_FAIL_STATUS_ERROR); } @Test @@ -418,7 +418,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { assertNotNull(unifiedReqDTO.getOutRefundNo()); assertThat(unifiedReqDTO) .extracting("payPrice", "refundPrice", "outTradeNo", - "notifyUrl", "reason") + "notifyUrl", "reason") .containsExactly(order.getPrice(), reqDTO.getPrice(), order.getNo(), "http://127.0.0.1/10", reqDTO.getReason()); return true; From 3f32c4488e9d8cd94c53e0e5843ec68c128b2433 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 24 Oct 2023 08:12:38 +0800 Subject: [PATCH 8/9] =?UTF-8?q?pay=EF=BC=9A=E4=BF=AE=E5=A4=8D=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95=E7=9A=84=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/order/PayOrderServiceTest.java | 31 +++++++++---------- .../service/refund/PayRefundServiceTest.java | 15 ++++----- 2 files changed, 20 insertions(+), 26 deletions(-) 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 c61672b44..cf72aa819 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 @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.pay.service.order; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.pay.core.client.PayClient; -import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum; @@ -67,8 +66,6 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { @Resource private PayOrderExtensionMapper orderExtensionMapper; - @MockBean - private PayClientFactory payClientFactory; @MockBean private PayProperties properties; @MockBean @@ -351,7 +348,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { .thenReturn(channel); // mock 方法(client) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // mock 方法() PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> o.setChannelErrorCode("001").setChannelErrorMsg("模拟异常")); @@ -405,7 +402,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { .thenReturn(channel); // mock 方法(client) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // mock 方法(支付渠道的调用) PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> o.setChannelErrorCode(null).setChannelErrorMsg(null) .setDisplayMode(PayOrderDisplayModeEnum.URL.getMode()).setDisplayContent("tudou")); @@ -463,7 +460,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { orderExtensionMapper.insert(orderExtension); // mock 方法(PayClient 已支付) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(orderExtension.getChannelId()))).thenReturn(client); + when(channelService.getPayClient(eq(orderExtension.getChannelId()))).thenReturn(client); when(client.getOrder(eq(orderExtension.getNo()))).thenReturn(randomPojo(PayOrderRespDTO.class, o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()))); @@ -482,7 +479,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { orderExtensionMapper.insert(orderExtension); // mock 方法(PayClient 已支付) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(orderExtension.getChannelId()))).thenReturn(client); + when(channelService.getPayClient(eq(orderExtension.getChannelId()))).thenReturn(client); when(client.getOrder(eq(orderExtension.getNo()))).thenReturn(randomPojo(PayOrderRespDTO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus()))); @@ -639,7 +636,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { orderExtensionMapper.insert(orderExtension); // 准备参数 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L) - .setFeeRate(0.1D)); + .setFeeRate(10D)); PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus()) .setOutTradeNo("P110")); @@ -656,12 +653,12 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { .setChannelId(10L).setChannelCode(channel.getCode()) .setSuccessTime(notify.getSuccessTime()).setExtensionId(orderExtension.getId()).setNo(orderExtension.getNo()) .setChannelOrderNo(notify.getChannelOrderNo()).setChannelUserId(notify.getChannelUserId()) - .setChannelFeeRate(0.1D).setChannelFeePrice(1); + .setChannelFeeRate(10D).setChannelFeePrice(1); assertPojoEquals(order, orderMapper.selectOne(null), "updateTime", "updater"); // 断言,调用 verify(notifyService).createPayNotifyTask(eq(PayNotifyTypeEnum.ORDER.getType()), - eq(orderExtension.getOrderId())); + eq(orderExtension.getOrderId())); } @Test @@ -874,7 +871,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { orderExtensionMapper.insert(orderExtension); // mock 方法(PayClient) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // mock 方法(PayClient 异常) when(client.getOrder(any())).thenThrow(new RuntimeException()); @@ -901,7 +898,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { orderExtensionMapper.insert(orderExtension); // mock 方法(PayClient) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // mock 方法(PayClient 成功返回) PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); @@ -935,7 +932,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { orderExtensionMapper.insert(orderExtension); // mock 方法(PayClient) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // mock 方法(PayClient 成功返回) PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())); @@ -966,7 +963,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { orderExtensionMapper.insert(orderExtension); // mock 方法(PayClient) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // 调用 int count = orderService.expireOrder(); @@ -1013,7 +1010,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { orderExtensionMapper.insert(orderExtension); // mock 方法(PayClient) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // mock 方法(PayClient 退款返回) PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, o -> o.setStatus(PayOrderStatusEnum.REFUND.getStatus())); @@ -1047,7 +1044,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { orderExtensionMapper.insert(orderExtension); // mock 方法(PayClient) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // mock 方法(PayClient 成功返回) PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())); @@ -1081,7 +1078,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { orderExtensionMapper.insert(orderExtension); // mock 方法(PayClient) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // mock 方法(PayClient 关闭返回) PayOrderRespDTO respDTO = randomPojo(PayOrderRespDTO.class, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())); 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 58a086c6b..a9296f93f 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 @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.pay.service.refund; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.pay.core.client.PayClient; -import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; @@ -67,8 +66,6 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { @MockBean private PayProperties payProperties; @MockBean - private PayClientFactory payClientFactory; - @MockBean private PayOrderService orderService; @MockBean private PayAppService appService; @@ -335,7 +332,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { when(channelService.validPayChannel(eq(1L))).thenReturn(channel); // mock 方法(client) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // mock 数据(refund 已存在) PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setMerchantRefundId("200")); @@ -367,7 +364,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { when(channelService.validPayChannel(eq(10L))).thenReturn(channel); // mock 方法(client) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // mock 方法(client 调用发生异常) when(client.unifiedRefund(any(PayRefundUnifiedReqDTO.class))).thenThrow(new RuntimeException()); @@ -411,14 +408,14 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { when(channelService.validPayChannel(eq(10L))).thenReturn(channel); // mock 方法(client) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // mock 方法(client 成功) PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class); when(client.unifiedRefund(argThat(unifiedReqDTO -> { assertNotNull(unifiedReqDTO.getOutRefundNo()); assertThat(unifiedReqDTO) .extracting("payPrice", "refundPrice", "outTradeNo", - "notifyUrl", "reason") + "notifyUrl", "reason") .containsExactly(order.getPrice(), reqDTO.getPrice(), order.getNo(), "http://127.0.0.1/10", reqDTO.getReason()); return true; @@ -668,7 +665,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { refundMapper.insert(refund); // mock 方法(client) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // mock 方法(client 返回指定状态) PayRefundRespDTO respDTO = randomPojo(PayRefundRespDTO.class, o -> o.setStatus(status)); when(client.getRefund(eq("P110"), eq("R220"))).thenReturn(respDTO); @@ -690,7 +687,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { refundMapper.insert(refund); // mock 方法(client) PayClient client = mock(PayClient.class); - when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); + when(channelService.getPayClient(eq(10L))).thenReturn(client); // mock 方法(client 抛出异常) when(client.getRefund(eq("P110"), eq("R220"))).thenThrow(new RuntimeException()); From 110b3476a88d6a9119e155cadc34b1117f1e19c0 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 24 Oct 2023 08:40:34 +0800 Subject: [PATCH 9/9] =?UTF-8?q?bpm=EF=BC=9A=E8=B7=9F=E8=BF=9B=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E5=8A=9F=E8=83=BD=EF=BC=88=E9=A9=B3=E5=9B=9E=E3=80=81?= =?UTF-8?q?=E5=8A=A0=E5=87=8F=E7=AD=BE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flowable/core/util/BpmnModelUtils.java | 309 ++++++++ .../flowable/core/util/FlowableUtils.java | 50 -- .../bpm/api/task/BpmProcessInstanceApi.java | 5 +- .../module/bpm/enums/ErrorCodeConstants.java | 15 +- .../bpm/enums/task/BpmCommentTypeEnum.java | 35 + .../task/BpmProcessInstanceResultEnum.java | 37 +- .../enums/task/BpmTaskAddSignTypeEnum.java | 40 + yudao-module-bpm/yudao-module-bpm-biz/pom.xml | 12 - .../api/task/BpmProcessInstanceApiImpl.java | 12 +- .../admin/task/BpmTaskController.java | 48 ++ .../task/vo/task/BpmTaskAddSignReqVO.java | 30 + .../task/vo/task/BpmTaskDelegateReqVO.java | 25 + .../admin/task/vo/task/BpmTaskRespVO.java | 10 + .../task/vo/task/BpmTaskReturnReqVO.java | 24 + .../task/vo/task/BpmTaskSimpleRespVO.java | 16 + .../task/vo/task/BpmTaskSubSignReqVO.java | 20 + .../task/vo/task/BpmTaskSubSignRespVO.java | 15 + .../bpm/convert/task/BpmTaskConvert.java | 70 +- .../bpm/dal/mysql/task/BpmTaskExtMapper.java | 13 + .../web/config/BpmWebConfiguration.java | 24 + .../bpm/framework/web/package-info.java | 4 + .../service/definition/BpmModelService.java | 9 + .../definition/BpmModelServiceImpl.java | 6 +- .../BpmProcessDefinitionServiceImpl.java | 4 +- .../BpmTaskAssignRuleServiceImpl.java | 6 +- .../bpm/service/oa/BpmOALeaveServiceImpl.java | 3 +- .../bpm/service/task/BpmTaskService.java | 67 +- .../bpm/service/task/BpmTaskServiceImpl.java | 732 +++++++++++++++++- .../module/system/api/user/AdminUserApi.java | 4 +- .../system/api/user/AdminUserApiImpl.java | 2 +- 30 files changed, 1507 insertions(+), 140 deletions(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/BpmnModelUtils.java create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskAddSignTypeEnum.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskAddSignReqVO.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskDelegateReqVO.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSimpleRespVO.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSubSignReqVO.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSubSignRespVO.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/web/config/BpmWebConfiguration.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/web/package-info.java diff --git a/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/BpmnModelUtils.java b/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/BpmnModelUtils.java new file mode 100644 index 000000000..7b23a467a --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/BpmnModelUtils.java @@ -0,0 +1,309 @@ +package cn.iocoder.yudao.framework.flowable.core.util; + +import cn.hutool.core.collection.CollUtil; +import org.flowable.bpmn.converter.BpmnXMLConverter; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.*; + +import java.util.*; + +/** + * 流程模型转操作工具类 + */ +public class BpmnModelUtils { + + /** + * 根据节点,获取入口连线 + * + * @param source 起始节点 + * @return 入口连线列表 + */ + public static List getElementIncomingFlows(FlowElement source) { + if (source instanceof FlowNode) { + return ((FlowNode) source).getIncomingFlows(); + } + return new ArrayList<>(); + } + + /** + * 根据节点,获取出口连线 + * + * @param source 起始节点 + * @return 出口连线列表 + */ + public static List getElementOutgoingFlows(FlowElement source) { + if (source instanceof FlowNode) { + return ((FlowNode) source).getOutgoingFlows(); + } + return new ArrayList<>(); + } + + /** + * 获取流程元素信息 + * + * @param model bpmnModel 对象 + * @param flowElementId 元素 ID + * @return 元素信息 + */ + public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) { + Process process = model.getMainProcess(); + return process.getFlowElement(flowElementId); + } + + /** + * 获得 BPMN 流程中,指定的元素们 + * + * @param model + * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等 + * @return 元素们 + */ + public static List getBpmnModelElements(BpmnModel model, Class clazz) { + List result = new ArrayList<>(); + model.getProcesses().forEach(process -> { + process.getFlowElements().forEach(flowElement -> { + if (flowElement.getClass().isAssignableFrom(clazz)) { + result.add((T) flowElement); + } + }); + }); + return result; + } + + /** + * 比较 两个bpmnModel 是否相同 + * @param oldModel 老的bpmn model + * @param newModel 新的bpmn model + */ + public static boolean equals(BpmnModel oldModel, BpmnModel newModel) { + // 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较 + return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel)); + } + + /** + * 把 bpmnModel 转换成 byte[] + * @param model bpmnModel + */ + public static byte[] getBpmnBytes(BpmnModel model) { + if (model == null) { + return new byte[0]; + } + BpmnXMLConverter converter = new BpmnXMLConverter(); + return converter.convertToXML(model); + } + + // ========== 遍历相关的方法 ========== + + /** + * 找到 source 节点之前的所有用户任务节点 + * + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 已找到的用户任务节点 + * @return 用户任务节点 数组 + */ + public static List getPreviousUserTaskList(FlowElement source, Set hasSequenceFlow, List userTaskList) { + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + userTaskList = getPreviousUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + if (sequenceFlows == null) { + return userTaskList; + } + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 类型为用户节点,则新增父级节点 + if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { + userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement()); + } + // 类型为子流程,则添加子流程开始节点出口处相连的节点 + if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { + // 获取子流程用户任务节点 + List childUserTaskList = findChildProcessUserTaskList((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (CollUtil.isNotEmpty(childUserTaskList)) { + userTaskList.addAll(childUserTaskList); + } + } + // 继续迭代 + userTaskList = getPreviousUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList); + } + return userTaskList; + } + + /** + * 迭代获取子流程用户任务节点 + * + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 需要撤回的用户任务列表 + * @return 用户任务节点 + */ + public static List findChildProcessUserTaskList(FlowElement source, Set hasSequenceFlow, List userTaskList) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + if (sequenceFlows == null) { + return userTaskList; + } + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 + if (sequenceFlow.getTargetFlowElement() instanceof UserTask) { + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); + continue; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + List childUserTaskList = findChildProcessUserTaskList((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (CollUtil.isNotEmpty(childUserTaskList)) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = findChildProcessUserTaskList(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList); + } + return userTaskList; + } + + + /** + * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 + * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况 + * + * @param source 起始节点 + * @param target 目标节点 + * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复 + * @return 结果 + */ + public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set visitedElements) { + visitedElements = visitedElements == null ? new HashSet<>() : visitedElements; + // 不能是开始事件和子流程 + if (source instanceof StartEvent && isInEventSubprocess(source)) { + return false; + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + if (CollUtil.isEmpty(sequenceFlows)) { + return true; + } + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (visitedElements.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + visitedElements.add(sequenceFlow.getId()); + // 这条线路存在目标节点,这条线路完成,进入下个线路 + FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement(); + if (target.getId().equals(sourceFlowElement.getId())) { + continue; + } + // 如果目标节点为并行网关,则不继续 + if (sourceFlowElement instanceof ParallelGateway) { + return false; + } + // 否则就继续迭代 + if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) { + return false; + } + } + return true; + } + + /** + * 判断当前节点是否属于不同的子流程 + * + * @param flowElement 被判断的节点 + * @return true 表示属于子流程 + */ + private static boolean isInEventSubprocess(FlowElement flowElement) { + FlowElementsContainer flowElementsContainer = flowElement.getParentContainer(); + while (flowElementsContainer != null) { + if (flowElementsContainer instanceof EventSubProcess) { + return true; + } + + if (flowElementsContainer instanceof FlowElement) { + flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer(); + } else { + flowElementsContainer = null; + } + } + return false; + } + + /** + * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找 + * + * @param source 起始节点 + * @param runTaskKeyList 正在运行的任务 Key,用于校验任务节点是否是正在运行的节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 需要撤回的用户任务列表 + * @return 子级任务节点列表 + */ + public static List iteratorFindChildUserTasks(FlowElement source, List runTaskKeyList, + Set hasSequenceFlow, List userTaskList) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList); + } + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + if (sequenceFlows == null) { + return userTaskList; + } + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 + if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) { + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); + continue; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + List childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (CollUtil.isNotEmpty(childUserTaskList)) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList); + } + return userTaskList; + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/FlowableUtils.java b/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/FlowableUtils.java index 3c6133c8a..2a0fc38ac 100644 --- a/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/FlowableUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/FlowableUtils.java @@ -1,14 +1,7 @@ package cn.iocoder.yudao.framework.flowable.core.util; -import org.flowable.bpmn.converter.BpmnXMLConverter; -import org.flowable.bpmn.model.BpmnModel; -import org.flowable.bpmn.model.FlowElement; import org.flowable.common.engine.impl.identity.Authentication; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - /** * Flowable 相关的工具方法 * @@ -26,49 +19,6 @@ public class FlowableUtils { Authentication.setAuthenticatedUserId(null); } - // ========== BPMN 相关的工具方法 ========== - - /** - * 获得 BPMN 流程中,指定的元素们 - * - * @param model - * @param clazz 指定元素。例如说,{@link org.flowable.bpmn.model.UserTask}、{@link org.flowable.bpmn.model.Gateway} 等等 - * @return 元素们 - */ - public static List getBpmnModelElements(BpmnModel model, Class clazz) { - List result = new ArrayList<>(); - model.getProcesses().forEach(process -> { - process.getFlowElements().forEach(flowElement -> { - if (flowElement.getClass().isAssignableFrom(clazz)) { - result.add((T) flowElement); - } - }); - }); - return result; - } - - /** - * 比较 两个bpmnModel 是否相同 - * @param oldModel 老的bpmn model - * @param newModel 新的bpmn model - */ - public static boolean equals(BpmnModel oldModel, BpmnModel newModel) { - // 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较 - return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel)); - } - - /** - * 把 bpmnModel 转换成 byte[] - * @param model bpmnModel - */ - public static byte[] getBpmnBytes(BpmnModel model) { - if (model == null) { - return new byte[0]; - } - BpmnXMLConverter converter = new BpmnXMLConverter(); - return converter.convertToXML(model); - } - // ========== Execution 相关的工具方法 ========== public static String formatCollectionVariable(String activityId) { diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApi.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApi.java index 152e605dd..e22152ee5 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApi.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApi.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.api.task; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.enums.ApiConstants; import io.swagger.v3.oas.annotations.Operation; @@ -21,7 +22,7 @@ public interface BpmProcessInstanceApi { @PostMapping(PREFIX + "/create") @Operation(summary = "创建流程实例(提供给内部),返回实例编号") @Parameter(name = "userId", description = "用户编号", required = true, example = "1") - String createProcessInstance(@RequestParam("userId") Long userId, - @Valid @RequestBody BpmProcessInstanceCreateReqDTO reqDTO); + CommonResult createProcessInstance(@RequestParam("userId") Long userId, + @Valid @RequestBody BpmProcessInstanceCreateReqDTO reqDTO); } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index 555d3f548..51eebda23 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode; /** * Bpm 错误码枚举类 - * + *

* bpm 系统,使用 1-009-000-000 段 */ public interface ErrorCodeConstants { @@ -43,8 +43,17 @@ public interface ErrorCodeConstants { ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的"); // ========== 流程任务 1-009-005-000 ========== - ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1_009_005_000, "审批任务失败,原因:该任务不处于未审批"); - ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "审批任务失败,原因:该任务的审批人不是你"); + ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你"); + ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在"); + ErrorCode TASK_IS_PENDING = new ErrorCode(1_009_005_003, "当前任务处于挂起状态,不能操作"); + ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_004, " 目标节点不存在"); + ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "回退任务失败,目标节点是在并行网关上或非同一路线上,不可跳转"); + ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人"); + ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在"); + ErrorCode TASK_ADD_SIGN_USER_NOT_EXIST = new ErrorCode(1_009_005_009, "任务加签:选择的用户不存在"); + ErrorCode TASK_ADD_SIGN_TYPE_ERROR = new ErrorCode(1_009_005_010, "任务加签:当前任务已经{},不能{}"); + ErrorCode TASK_ADD_SIGN_USER_REPEAT = new ErrorCode(1_009_005_011, "任务加签失败,加签人与现有审批人[{}]重复"); + ErrorCode TASK_SUB_SIGN_NO_PARENT = new ErrorCode(1_009_005_011, "任务减签失败,被减签的任务必须是通过加签生成的任务"); // ========== 流程任务分配规则 1-009-006-000 ========== ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1_009_006_000, "流程({}) 的任务({}) 已经存在分配规则"); diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java new file mode 100644 index 000000000..89d56b3c0 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.bpm.enums.task; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 流程任务 -- comment类型枚举 + */ +@Getter +@AllArgsConstructor +public enum BpmCommentTypeEnum { + + APPROVE(1, "通过", ""), + REJECT(2, "不通过", ""), + CANCEL(3, "已取消", ""), + BACK(4, "退回", ""), + DELEGATE(5, "委派", ""), + ADD_SIGN(6, "加签", "[{}]{}给了[{}],理由为:{}"), + SUB_SIGN(7, "减签", "[{}]操作了【减签】,审批人[{}]的任务被取消"), + ; + + /** + * 操作类型 + */ + private final Integer type; + /** + * 操作名字 + */ + private final String name; + /** + * 操作描述 + */ + private final String comment; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceResultEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceResultEnum.java index 3b57554cf..615416c73 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceResultEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceResultEnum.java @@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; +import java.util.List; + /** * 流程实例的结果 * @@ -20,11 +23,35 @@ public enum BpmProcessInstanceResultEnum { // ========== 流程任务独有的状态 ========== - BACK(5, "退回/驳回"); + BACK(5, "驳回"), // 退回 + DELEGATE(6, "委派"), + /** + * 【加签】源任务已经审批完成,但是它使用了后加签,后加签的任务未完成,源任务就会是这个状态 + * 相当于是 通过 APPROVE 的特殊状态 + * 例如:A审批, A 后加签了 B,并且审批通过了任务,但是 B 还未审批,则当前任务状态为“待后加签任务完成” + */ + SIGN_AFTER(7, "待后加签任务完成"), + /** + * 【加签】源任务未审批,但是向前加签了,所以源任务状态变为“待前加签任务完成” + * 相当于是 处理中 PROCESS 的特殊状态 + * 例如:A 审批, A 前加签了 B,B 还未审核 + */ + SIGN_BEFORE(8, "待前加签任务完成"), + /** + * 【加签】后加签任务被创建时的初始状态 + * 相当于是 处理中 PROCESS 的特殊状态 + * 因为需要源任务先完成,才能到后加签的人来审批,所以加了一个状态区分 + */ + WAIT_BEFORE_TASK(9, "待前置任务完成"); + + /** + * 能被减签的状态 + */ + public static final List CAN_SUB_SIGN_STATUS_LIST = Arrays.asList(PROCESS.result, WAIT_BEFORE_TASK.result); /** * 结果 - * + *

* 如果新增时,注意 {@link #isEndResult(Integer)} 是否需要变更 */ private final Integer result; @@ -35,14 +62,16 @@ public enum BpmProcessInstanceResultEnum { /** * 判断该结果是否已经处于 End 最终结果 - * + *

* 主要用于一些结果更新的逻辑,如果已经是最终结果,就不再进行更新 * * @param result 结果 * @return 是否 */ public static boolean isEndResult(Integer result) { - return ObjectUtils.equalsAny(result, APPROVE.getResult(), REJECT.getResult(), CANCEL.getResult(), BACK.getResult()); + return ObjectUtils.equalsAny(result, APPROVE.getResult(), REJECT.getResult(), + CANCEL.getResult(), BACK.getResult(), + SIGN_AFTER.getResult()); } } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskAddSignTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskAddSignTypeEnum.java new file mode 100644 index 000000000..42c212e28 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskAddSignTypeEnum.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.bpm.enums.task; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 流程任务 -- 加签类型枚举类型 + */ +@Getter +@AllArgsConstructor +public enum BpmTaskAddSignTypeEnum { + + /** + * 向前加签,需要前置任务审批完成,才回到原审批人 + */ + BEFORE("before", "向前加签"), + /** + * 向后加签,需要后置任务全部审批完,才会通过原审批人节点 + */ + AFTER("after", "向后加签"), + /** + * 创建后置加签时的过度状态,用于控制向后加签生成的任务状态 + */ + AFTER_CHILDREN_TASK("afterChildrenTask", "向后加签生成的子任务"); + + private final String type; + + private final String desc; + + public static String formatDesc(String type) { + for (BpmTaskAddSignTypeEnum value : values()) { + if (value.type.equals(type)) { + return value.desc; + } + } + return null; + } + +} + \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/pom.xml b/yudao-module-bpm/yudao-module-bpm-biz/pom.xml index ab26ed27a..8b04d5969 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/pom.xml +++ b/yudao-module-bpm/yudao-module-bpm-biz/pom.xml @@ -86,18 +86,6 @@ spring-cloud-starter-alibaba-nacos-config - - - cn.iocoder.cloud - yudao-spring-boot-starter-job - - - - - cn.iocoder.cloud - yudao-spring-boot-starter-mq - - diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java index 596c9981a..01bf397fd 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java @@ -1,20 +1,23 @@ package cn.iocoder.yudao.module.bpm.api.task; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; -import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.validation.Valid; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + /** * Flowable 流程实例 Api 实现类 * * @author 芋道源码 * @author jason */ -@Service +@RestController @Validated public class BpmProcessInstanceApiImpl implements BpmProcessInstanceApi { @@ -22,7 +25,8 @@ public class BpmProcessInstanceApiImpl implements BpmProcessInstanceApi { private BpmProcessInstanceService processInstanceService; @Override - public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO) { - return processInstanceService.createProcessInstance(userId, reqDTO); + public CommonResult createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO) { + return success(processInstanceService.createProcessInstance(userId, reqDTO)); } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java index 57d601f6d..dcfab78ef 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java @@ -74,4 +74,52 @@ public class BpmTaskController { return success(true); } + @GetMapping("/return-list") + @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮") + @Parameter(name = "taskId", description = "当前任务ID", required = true) + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult> getReturnList(@RequestParam("taskId") String taskId) { + return success(taskService.getReturnTaskList(taskId)); + } + + @PutMapping("/return") + @Operation(summary = "回退任务", description = "用于【流程详情】的【回退】按钮") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) { + taskService.returnTask(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/delegate") + @Operation(summary = "委派任务", description = "用于【流程详情】的【委派】按钮。和向前【加签】有点像,唯一区别是【委托】没有单独创立任务") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult delegateTask(@Valid @RequestBody BpmTaskDelegateReqVO reqVO) { + taskService.delegateTask(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/create-sign") + @Operation(summary = "加签", description = "before 前加签,after 后加签") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult createSignTask(@Valid @RequestBody BpmTaskAddSignReqVO reqVO) { + taskService.createSignTask(getLoginUserId(), reqVO); + return success(true); + } + + @DeleteMapping("/delete-sign") + @Operation(summary = "减签") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult deleteSignTask(@Valid @RequestBody BpmTaskSubSignReqVO reqVO) { + taskService.deleteSignTask(getLoginUserId(), reqVO); + return success(true); + } + + @GetMapping("children-list") + @Operation(summary = "获取能被减签的任务") + @Parameter(name = "parentId", description = "父级任务 ID", required = true) + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult> getChildrenTaskList(@RequestParam("parentId") String parentId) { + return success(taskService.getChildrenTaskList(parentId)); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskAddSignReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskAddSignReqVO.java new file mode 100644 index 000000000..cabb91be1 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskAddSignReqVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import java.util.Set; + +// TODO @海洋:类名,应该是 create 哈 +@Schema(description = "管理后台 - 加签流程任务的 Request VO") +@Data +public class BpmTaskAddSignReqVO { + + @Schema(description = "需要加签的任务 ID") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "加签的用户 ID") + @NotEmpty(message = "加签用户 ID 不能为空") + private Set userIdList; + + @Schema(description = "加签类型,before 向前加签,after 向后加签") + @NotEmpty(message = "加签类型不能为空") + private String type; + + @Schema(description = "加签原因") + @NotEmpty(message = "加签原因不能为空") + private String reason; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskDelegateReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskDelegateReqVO.java new file mode 100644 index 000000000..96c42deb8 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskDelegateReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 委派流程任务的 Request VO") +@Data +public class BpmTaskDelegateReqVO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "被委派人 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "被委派人 ID 不能为空") + private Long delegateUserId; + + @Schema(description = "委派原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "做不了决定,需要你先帮忙瞅瞅") + @NotEmpty(message = "委派原因不能为空") + private String reason; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java index 3c404678f..1af839e26 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java @@ -5,6 +5,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; +import java.util.List; + @Schema(description = "管理后台 - 流程任务的 Response VO") @Data @EqualsAndHashCode(callSuper = true) @@ -19,6 +21,14 @@ public class BpmTaskRespVO extends BpmTaskDonePageItemRespVO { */ private User assigneeUser; + /** + * 父任务ID + */ + private String parentTaskId; + + @Schema(description = "子任务(由加签生成)", requiredMode = Schema.RequiredMode.REQUIRED, example = "childrenTask") + private List children; + @Schema(description = "用户信息") @Data public static class User { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java new file mode 100644 index 000000000..983f35cd2 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; + +@Schema(description = "管理后台 - 回退流程任务的 Request VO") +@Data +public class BpmTaskReturnReqVO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "回退到的任务 Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotEmpty(message = "回退到的任务 Key 不能为空") + private String targetDefinitionKey; + + @Schema(description = "回退意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "我就是想驳回") + @NotEmpty(message = "回退意见不能为空") + private String reason; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSimpleRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSimpleRespVO.java new file mode 100644 index 000000000..b98a25a22 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSimpleRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 流程任务的精简 Response VO") +@Data +public class BpmTaskSimpleRespVO { + + @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one") + private String definitionKey; + + @Schema(description = "任务名词", requiredMode = Schema.RequiredMode.REQUIRED, example = "经理审批") + private String name; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSubSignReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSubSignReqVO.java new file mode 100644 index 000000000..731e4804a --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSubSignReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; + +// TODO @海洋:类名,应该是 delete 哈 +@Schema(description = "管理后台 - 减签流程任务的 Request VO") +@Data +public class BpmTaskSubSignReqVO { + + @Schema(description = "被减签的任务 ID") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "加签原因") + @NotEmpty(message = "加签原因不能为空") + private String reason; +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSubSignRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSubSignRespVO.java new file mode 100644 index 000000000..efecbf582 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSubSignRespVO.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 减签流程任务的 Response VO") +@Data +public class BpmTaskSubSignRespVO { + @Schema(description = "审核的用户信息", requiredMode = Schema.RequiredMode.REQUIRED, example = "小李") + private BpmTaskRespVO.User assigneeUser; + @Schema(description = "任务 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "12312") + private String id; + @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "经理审批") + private String name; +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java index e9c21ec2c..60ce84021 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java @@ -1,27 +1,34 @@ package cn.iocoder.yudao.module.bpm.convert.task; import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskDonePageItemRespVO; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO; +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.flowable.bpmn.model.FlowElement; import org.flowable.common.engine.impl.db.SuspensionState; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl; import org.mapstruct.*; import org.mapstruct.factory.Mappers; +import java.util.Date; import java.util.List; import java.util.Map; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; + /** * Bpm 任务 Convert * @@ -47,8 +54,6 @@ public interface BpmTaskConvert { } @Mapping(source = "suspended", target = "suspensionState", qualifiedByName = "convertSuspendedToSuspensionState") - // @Mapping(target = "claimTime", expression = "java(bean.getClaimTime()==null?null: LocalDateTime.ofInstant(bean.getClaimTime().toInstant(),ZoneId.systemDefault()))") - // @Mapping(target = "createTime", expression = "java(bean.getCreateTime()==null?null:LocalDateTime.ofInstant(bean.getCreateTime().toInstant(),ZoneId.systemDefault()))") BpmTaskTodoPageItemRespVO convert1(Task bean); @Named("convertSuspendedToSuspensionState") @@ -107,8 +112,6 @@ public interface BpmTaskConvert { } @Mapping(source = "taskDefinitionKey", target = "definitionKey") - // @Mapping(target = "createTime", expression = "java(bean.getCreateTime() == null ? null : LocalDateTime.ofInstant(bean.getCreateTime().toInstant(), ZoneId.systemDefault()))") - // @Mapping(target = "endTime", expression = "java(bean.getEndTime() == null ? null : LocalDateTime.ofInstant(bean.getEndTime().toInstant(), ZoneId.systemDefault()))") BpmTaskRespVO convert3(HistoricTaskInstance bean); BpmTaskRespVO.User convert3(AdminUserRespDTO bean); @@ -142,4 +145,57 @@ public interface BpmTaskConvert { return reqDTO; } + default List convertList(List elementList) { + return CollectionUtils.convertList(elementList, element -> new BpmTaskSimpleRespVO() + .setName(element.getName()) + .setDefinitionKey(element.getId())); + } + + //此处不用 mapstruct 映射,因为 TaskEntityImpl 还有很多其他属性,这里我们只设置我们需要的 + //使用 mapstruct 会将里面嵌套的各个属性值都设置进去,会出现意想不到的问题 + default TaskEntityImpl convert(TaskEntityImpl task,TaskEntityImpl parentTask){ + task.setCategory(parentTask.getCategory()); + task.setDescription(parentTask.getDescription()); + task.setTenantId(parentTask.getTenantId()); + task.setName(parentTask.getName()); + task.setParentTaskId(parentTask.getId()); + task.setProcessDefinitionId(parentTask.getProcessDefinitionId()); + task.setProcessInstanceId(parentTask.getProcessInstanceId()); + task.setTaskDefinitionKey(parentTask.getTaskDefinitionKey()); + task.setTaskDefinitionId(parentTask.getTaskDefinitionId()); + task.setPriority(parentTask.getPriority()); + task.setCreateTime(new Date()); + return task; + } + + default List convertList(List bpmTaskExtDOList, + Map userMap, + Map idTaskMap){ + return CollectionUtils.convertList(bpmTaskExtDOList, task -> { + BpmTaskSubSignRespVO bpmTaskSubSignRespVO = new BpmTaskSubSignRespVO() + .setId(task.getTaskId()).setName(task.getName()); + // 后加签任务不会直接设置 assignee ,所以不存在 assignee 的情况,则去取 owner + Task sourceTask = idTaskMap.get(task.getTaskId()); + String assignee = ObjectUtil.defaultIfBlank(sourceTask.getOwner(),sourceTask.getAssignee()); + MapUtils.findAndThen(userMap,NumberUtils.parseLong(assignee), + assignUser-> bpmTaskSubSignRespVO.setAssigneeUser(convert3(assignUser))); + return bpmTaskSubSignRespVO; + }); + } + + /** + * 转换任务为父子级 + * + * @param sourceList 原始数据 + * @return 转换后的父子级数组 + */ + default List convertChildrenList(List sourceList) { + List childrenTaskList = filterList(sourceList, r -> StrUtil.isNotEmpty(r.getParentTaskId())); + Map> parentChildrenTaskListMap = convertMultiMap(childrenTaskList, BpmTaskRespVO::getParentTaskId); + for (BpmTaskRespVO bpmTaskRespVO : sourceList) { + bpmTaskRespVO.setChildren(parentChildrenTaskListMap.get(bpmTaskRespVO.getId())); + } + return filterList(sourceList, r -> StrUtil.isEmpty(r.getParentTaskId())); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmTaskExtMapper.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmTaskExtMapper.java index 6683b536d..8108e613d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmTaskExtMapper.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmTaskExtMapper.java @@ -1,7 +1,9 @@ package cn.iocoder.yudao.module.bpm.dal.mysql.task; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO; +import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.apache.ibatis.annotations.Mapper; @@ -19,8 +21,19 @@ public interface BpmTaskExtMapper extends BaseMapperX { return selectList(BpmTaskExtDO::getTaskId, taskIds); } + // TODO @海:BpmProcessInstanceResultEnum.CAN_SUB_SIGN_STATUS_LIST) 应该作为条件,mapper 不要有业务 + default List selectProcessListByTaskIds(Collection taskIds) { + return selectList(new LambdaQueryWrapperX() + .in(BpmTaskExtDO::getTaskId, taskIds) + .in(BpmTaskExtDO::getResult, BpmProcessInstanceResultEnum.CAN_SUB_SIGN_STATUS_LIST)); + } + default BpmTaskExtDO selectByTaskId(String taskId) { return selectOne(BpmTaskExtDO::getTaskId, taskId); } + default void updateBatchByTaskIdList(List taskIdList, BpmTaskExtDO updateObj) { + update(updateObj, new LambdaQueryWrapper().in(BpmTaskExtDO::getTaskId, taskIdList)); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/web/config/BpmWebConfiguration.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/web/config/BpmWebConfiguration.java new file mode 100644 index 000000000..036e8a12f --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/web/config/BpmWebConfiguration.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.bpm.framework.web.config; + +import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration; +import org.springdoc.core.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * bpm 模块的 web 组件的 Configuration + * + * @author 芋道源码 + */ +@Configuration(proxyBeanMethods = false) +public class BpmWebConfiguration { + + /** + * bpm 模块的 API 分组 + */ + @Bean + public GroupedOpenApi bpmGroupedOpenApi() { + return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("bpm"); + } + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/web/package-info.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/web/package-info.java new file mode 100644 index 000000000..c01417081 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/web/package-info.java @@ -0,0 +1,4 @@ +/** + * bpm 模块的 web 配置 + */ +package cn.iocoder.yudao.module.bpm.framework.web; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java index a4e4f83d1..03fc99e03 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java @@ -12,6 +12,7 @@ import javax.validation.Valid; * @author yunlongn */ public interface BpmModelService { + /** * 获得流程模型分页 * @@ -74,4 +75,12 @@ public interface BpmModelService { */ BpmnModel getBpmnModel(String id); + /** + * 获得流程定义编号对应的 BPMN Model + * + * @param processDefinitionId 流程定义编号 + * @return BPMN Model + */ + BpmnModel getBpmnModelByDefinitionId(String processDefinitionId); + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index 2e7a07e1e..8f7e3996b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -233,6 +233,11 @@ public class BpmModelServiceImpl implements BpmModelService { return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true); } + @Override + public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) { + return repositoryService.getBpmnModel(processDefinitionId); + } + private void checkKeyNCName(String key) { if (!ValidationUtils.isXmlNCName(key)) { throw exception(MODEL_KEY_VALID); @@ -283,5 +288,4 @@ public class BpmModelServiceImpl implements BpmModelService { processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode()); } - } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java index e1cbde570..72d488cdc 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java @@ -5,7 +5,7 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.PageUtils; -import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils; +import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO; @@ -200,7 +200,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ BpmnModel newModel = buildBpmnModel(createReqDTO.getBpmnBytes()); BpmnModel oldModel = getBpmnModel(oldProcessDefinition.getId()); // 对比字节变化 - if (!FlowableUtils.equals(oldModel, newModel)) { + if (!BpmnModelUtils.equals(oldModel, newModel)) { return false; } // 最终发现都一致,则返回 true diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java index 10f3c2cdc..62f0be44c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java @@ -7,7 +7,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; -import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils; +import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO; @@ -15,8 +15,8 @@ import cn.iocoder.yudao.module.bpm.convert.definition.BpmTaskAssignRuleConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.PostApi; @@ -114,7 +114,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService { return Collections.emptyList(); } // 获得用户任务,只有用户任务才可以设置分配规则 - List userTasks = FlowableUtils.getBpmnModelElements(model, UserTask.class); + List userTasks = BpmnModelUtils.getBpmnModelElements(model, UserTask.class); if (CollUtil.isEmpty(userTasks)) { return Collections.emptyList(); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/BpmOALeaveServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/BpmOALeaveServiceImpl.java index 33d178901..5c3db6f98 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/BpmOALeaveServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/BpmOALeaveServiceImpl.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.bpm.service.oa; -import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.LocalDateTimeUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; @@ -57,7 +56,7 @@ public class BpmOALeaveServiceImpl implements BpmOALeaveService { processInstanceVariables.put("day", day); String processInstanceId = processInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY) - .setVariables(processInstanceVariables).setBusinessKey(String.valueOf(leave.getId()))); + .setVariables(processInstanceVariables).setBusinessKey(String.valueOf(leave.getId()))).getCheckedData(); // 将工作流的编号,更新到 OA 请假单中 leaveMapper.updateById(new BpmOALeaveDO().setId(leave.getId()).setProcessInstanceId(processInstanceId)); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index fc45887b7..42be9260a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -1,9 +1,9 @@ package cn.iocoder.yudao.module.bpm.service.task; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; +import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO; import org.flowable.task.api.Task; import javax.validation.Valid; @@ -23,7 +23,6 @@ public interface BpmTaskService { * * @param userId 用户编号 * @param pageReqVO 分页请求 - * * @return 流程任务分页 */ PageResult getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageReqVO); @@ -33,7 +32,6 @@ public interface BpmTaskService { * * @param userId 用户编号 * @param pageReqVO 分页请求 - * * @return 流程任务分页 */ PageResult getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageReqVO); @@ -42,19 +40,17 @@ public interface BpmTaskService { * 获得流程任务 Map * * @param processInstanceIds 流程实例的编号数组 - * * @return 流程任务 Map */ default Map> getTaskMapByProcessInstanceIds(List processInstanceIds) { return CollectionUtils.convertMultiMap(getTasksByProcessInstanceIds(processInstanceIds), - Task::getProcessInstanceId); + Task::getProcessInstanceId); } /** * 获得流程任务列表 * * @param processInstanceIds 流程实例的编号数组 - * * @return 流程任务列表 */ List getTasksByProcessInstanceIds(List processInstanceIds); @@ -63,11 +59,19 @@ public interface BpmTaskService { * 获得指令流程实例的流程任务列表,包括所有状态的 * * @param processInstanceId 流程实例的编号 - * * @return 流程任务列表 */ List getTaskListByProcessInstanceId(String processInstanceId); + + /** + * 通过任务 ID 集合,获取任务扩展表信息集合 + * + * @param taskIdList 任务 ID 集合 + * @return 任务列表 + */ + List getTaskListByTaskIdList(List taskIdList); + /** * 通过任务 * @@ -128,4 +132,53 @@ public interface BpmTaskService { */ void updateTaskExtAssign(Task task); + /** + * 获取当前任务的可回退的流程集合 + * + * @param taskId 当前的任务 ID + * @return 可以回退的节点列表 + */ + List getReturnTaskList(String taskId); + + /** + * 将任务回退到指定的 targetDefinitionKey 位置 + * + * @param userId 用户编号 + * @param reqVO 回退的任务key和当前所在的任务ID + */ + void returnTask(Long userId, BpmTaskReturnReqVO reqVO); + + + /** + * 将指定任务委派给其他人处理,等接收人处理后再回到原审批人手中审批 + * + * @param userId 用户编号 + * @param reqVO 被委派人和被委派的任务编号理由参数 + */ + void delegateTask(Long userId, BpmTaskDelegateReqVO reqVO); + + /** + * 任务加签 + * + * @param userId 被加签的用户和任务 ID,加签类型 + * @param reqVO 当前用户 ID + */ + void createSignTask(Long userId, BpmTaskAddSignReqVO reqVO); + + /** + * 任务减签名 + * + * @param userId 当前用户ID + * @param reqVO 被减签的任务 ID,理由 + */ + void deleteSignTask(Long userId, BpmTaskSubSignReqVO reqVO); + + /** + * 获取指定任务的子任务和审批人信息 + * + * @param parentId 指定任务ID + * @return 子任务列表 + */ + List getChildrenTaskList(String parentId); + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index b52125d14..a717ba684 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -2,45 +2,63 @@ package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; +import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils; +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper; +import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; +import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskAddSignTypeEnum; +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; +import org.flowable.engine.ManagementService; +import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.DelegationState; import org.flowable.task.api.Task; +import org.flowable.task.api.TaskInfo; import org.flowable.task.api.TaskQuery; import org.flowable.task.api.history.HistoricTaskInstance; import org.flowable.task.api.history.HistoricTaskInstanceQuery; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; +import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.Assert; import javax.annotation.Resource; import javax.validation.Valid; import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_TARGET_NODE_NOT_EXISTS; /** * 流程任务实例 Service 实现类 @@ -56,23 +74,32 @@ public class BpmTaskServiceImpl implements BpmTaskService { private TaskService taskService; @Resource private HistoryService historyService; + @Resource + private RuntimeService runtimeService; @Resource private BpmProcessInstanceService processInstanceService; + @Resource + private BpmModelService bpmModelService; + @Resource + private BpmMessageService messageService; + @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; + @Resource private BpmTaskExtMapper taskExtMapper; + @Resource - private BpmMessageService messageService; + private ManagementService managementService; @Override public PageResult getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) { // 查询待办任务 TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(String.valueOf(userId)) // 分配给自己 - .orderByTaskCreateTime().desc(); // 创建时间倒序 + .orderByTaskCreateTime().desc(); // 创建时间倒序 if (StrUtil.isNotBlank(pageVO.getName())) { taskQuery.taskNameLike("%" + pageVO.getName() + "%"); } @@ -90,21 +117,21 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 获得 ProcessInstance Map Map processInstanceMap = - processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId)); + processInstanceService.getProcessInstanceMap(convertSet(tasks, Task::getProcessInstanceId)); // 获得 User Map Map userMap = adminUserApi.getUserMap( - convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); + convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); // 拼接结果 return new PageResult<>(BpmTaskConvert.INSTANCE.convertList1(tasks, processInstanceMap, userMap), - taskQuery.count()); + taskQuery.count()); } @Override public PageResult getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageVO) { // 查询已办任务 HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery().finished() // 已完成 - .taskAssignee(String.valueOf(userId)) // 分配给自己 - .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序 + .taskAssignee(String.valueOf(userId)) // 分配给自己 + .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序 if (StrUtil.isNotBlank(pageVO.getName())) { taskQuery.taskNameLike("%" + pageVO.getName() + "%"); } @@ -122,19 +149,19 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 获得 TaskExtDO Map List bpmTaskExtDOs = - taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId)); + taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId)); Map bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId); // 获得 ProcessInstance Map Map historicProcessInstanceMap = - processInstanceService.getHistoricProcessInstanceMap( - convertSet(tasks, HistoricTaskInstance::getProcessInstanceId)); + processInstanceService.getHistoricProcessInstanceMap( + convertSet(tasks, HistoricTaskInstance::getProcessInstanceId)); // 获得 User Map Map userMap = adminUserApi.getUserMap( - convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); + convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); // 拼接结果 return new PageResult<>( - BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap), - taskQuery.count()); + BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap), + taskQuery.count()); } @Override @@ -169,33 +196,242 @@ public class BpmTaskServiceImpl implements BpmTaskService { Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); // 拼接数据 - return BpmTaskConvert.INSTANCE.convertList3(tasks, bpmTaskExtDOMap, processInstance, userMap, deptMap); + List result = BpmTaskConvert.INSTANCE.convertList3(tasks, bpmTaskExtDOMap, processInstance, userMap, deptMap); + return BpmTaskConvert.INSTANCE.convertChildrenList(result); + } + + @Override + public List getTaskListByTaskIdList(List taskIdList) { + return taskExtMapper.selectListByTaskIds(taskIdList); } @Override @Transactional(rollbackFor = Exception.class) public void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO) { - // 校验任务存在 - Task task = checkTask(userId, reqVO.getId()); - // 校验流程实例存在 + // 1.1 校验任务存在 + Task task = validateTask(userId, reqVO.getId()); + // 1.2 校验流程实例存在 ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); if (instance == null) { throw exception(PROCESS_INSTANCE_NOT_EXISTS); } + // 情况一:被委派的任务,不调用 complete 去完成任务 + if (DelegationState.PENDING.equals(task.getDelegationState())) { + approveDelegateTask(reqVO, task); + return; + } + + // 情况二:后加签的任务 + if (BpmTaskAddSignTypeEnum.AFTER.getType().equals(task.getScopeType())) { + // 后加签处理 + approveAfterSignTask(task, reqVO); + return; + } + + // 情况三:自己审批的任务,调用 complete 去完成任务 // 完成任务,审批通过 taskService.complete(task.getId(), instance.getProcessVariables()); - // 更新任务拓展表为通过 taskExtMapper.updateByTaskId( - new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()) - .setReason(reqVO.getReason())); + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()) + .setReason(reqVO.getReason())); + // 处理加签任务 + handleParentTask(task); + } + + + /** + * 审批通过存在“后加签”的任务。 + *

+ * 注意:该任务不能马上完成,需要一个中间状态(SIGN_AFTER),并激活剩余所有子任务(PROCESS)为可审批处理 + * + * @param task 当前任务 + * @param reqVO 前端请求参数 + */ + private void approveAfterSignTask(Task task, BpmTaskApproveReqVO reqVO) { + // 1. 有向后加签,则该任务状态临时设置为 ADD_SIGN_AFTER 状态 + taskExtMapper.updateByTaskId( + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.SIGN_AFTER.getResult()) + .setReason(reqVO.getReason()).setEndTime(LocalDateTime.now())); + + // 2. 激活子任务 + List childrenTaskIdList = getChildrenTaskIdList(task.getId()); + for (String childrenTaskId : childrenTaskIdList) { + taskService.resolveTask(childrenTaskId); + } + // 2.1 更新任务扩展表中子任务为进行中 + taskExtMapper.updateBatchByTaskIdList(childrenTaskIdList, + new BpmTaskExtDO().setResult(BpmProcessInstanceResultEnum.PROCESS.getResult())); + } + + /** + * 处理当前任务的父任务,主要处理“加签”的情况 + * + * @param task 当前任务 + */ + private void handleParentTask(Task task) { + String parentTaskId = task.getParentTaskId(); + if (StrUtil.isBlank(parentTaskId)) { + return; + } + // 1. 判断当前任务的父任务是否还有子任务 + Long childrenTaskCount = getChildrenTaskCount(parentTaskId); + if (childrenTaskCount > 0) { + return; + } + // 2. 获取父任务 + Task parentTask = validateTaskExist(parentTaskId); + + // 3. 处理加签情况 + String scopeType = parentTask.getScopeType(); + if(!validateSignType(scopeType)){ + return; + } + // 3.1 情况一:处理向前加签 + if (BpmTaskAddSignTypeEnum.BEFORE.getType().equals(scopeType)) { + // 3.1.1 如果是向前加签的任务,则调用 resolveTask 指派父任务,将 owner 重新赋值给父任务的 assignee,这样它就可以被审批 + taskService.resolveTask(parentTaskId); + // 3.1.2 更新任务拓展表为处理中 + taskExtMapper.updateByTaskId( + new BpmTaskExtDO().setTaskId(parentTask.getId()).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult())); + } else if (BpmTaskAddSignTypeEnum.AFTER.getType().equals(scopeType)) { + // 3.2 情况二:处理向后加签 + handleParentTaskForAfterSign(parentTask); + } + + // 4. 子任务已处理完成,清空 scopeType 字段,修改 parentTask 信息,方便后续可以继续向前后向后加签 + // 再查询一次的原因是避免报错:Task was updated by another transaction concurrently + // 因为前面处理后可能会导致 parentTask rev 字段被修改,需要重新获取最新的 + parentTask = getTask(parentTaskId); + if (parentTask == null) { + // 为空的情况是:已经通过 handleAfterSign 方法将任务完成了,所以 ru_task 表会查不到数据 + return; + } + clearTaskScopeTypeAndSave(parentTask); + } + + + /** + * 处理后加签任务 + * + * @param parentTask 当前审批任务的父任务 + */ + // TODO @海:这个逻辑,怎么感觉可以是 parentTask 的 parent,再去调用 handleParentTask 方法;可以微信聊下; + private void handleParentTaskForAfterSign(Task parentTask) { + String parentTaskId = parentTask.getId(); + // 1. 更新 parentTask 的任务拓展表为通过,并调用 complete 完成自己 + BpmTaskExtDO currentTaskExt = taskExtMapper.selectByTaskId(parentTask.getId()); + BpmTaskExtDO currentTaskExtUpdateObj = new BpmTaskExtDO().setTaskId(parentTask.getId()) + .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); + if (currentTaskExt.getEndTime() == null) { + // 1.1 有这个判断是因为,以前没设置过结束时间,才去设置 + currentTaskExtUpdateObj.setEndTime(LocalDateTime.now()); + } + taskExtMapper.updateByTaskId(currentTaskExtUpdateObj); + // 1.2 完成自己(因为它已经没有子任务,所以也可以完成) + taskService.complete(parentTaskId); + + // 2. 如果有父级,递归查询上级任务是否都已经完成 + if (StrUtil.isEmpty(parentTask.getParentTaskId())) { + return; + } + // 2.1 判断整条链路的任务是否完成 + // 例如从 A 任务加签了一个 B 任务,B 任务又加签了一个 C 任务,C 任务加签了 D 任务 + // 此时,D 任务完成,要一直往上找到祖先任务 A调用 complete 方法完成 A 任务 + boolean allChildrenTaskFinish = true; + while (StrUtil.isNotBlank(parentTask.getParentTaskId())) { + parentTask = validateTaskExist(parentTask.getParentTaskId()); + BpmTaskExtDO parentTaskExt = taskExtMapper.selectByTaskId(parentTask.getId()); + if (parentTaskExt == null) { + break; + } + boolean currentTaskFinish = BpmProcessInstanceResultEnum.isEndResult(parentTaskExt.getResult()); + // 2.2 如果 allChildrenTaskFinish 已经被赋值为 false,则不会再赋值为 true,因为整个链路没有完成 + if (allChildrenTaskFinish) { + allChildrenTaskFinish = currentTaskFinish; + } + // 2.3 任务已完成则不处理 + if (currentTaskFinish) { + continue; + } + + // 3 处理非完成状态的任务 + // 3.1 判断当前任务的父任务是否还有子任务 + Long childrenTaskCount = getChildrenTaskCount(parentTaskExt.getTaskId()); + if (childrenTaskCount > 0) { + continue; + } + // 3.2 没有子任务,判断当前任务状态是否为 ADD_SIGN_BEFORE 待前加签任务完成 + if (BpmProcessInstanceResultEnum.SIGN_BEFORE.getResult().equals(parentTaskExt.getResult())) { + // 3.3 需要修改该任务状态为处理中 + taskService.resolveTask(parentTaskExt.getTaskId()); + parentTaskExt.setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); + taskExtMapper.updateByTaskId(parentTaskExt); + } + // 3.4 清空 scopeType 字段,用于任务没有子任务时使用该方法,方便任务可以再次被不同的方式加签 + parentTask = validateTaskExist(parentTaskExt.getTaskId()); + clearTaskScopeTypeAndSave(parentTask); + } + + // 4. 完成最后的顶级祖先任务 + if (allChildrenTaskFinish) { + taskService.complete(parentTask.getId()); + } + } + + /** + * 清空 scopeType 字段,用于任务没有子任务时使用该方法,方便任务可以再次被不同的方式加签 + * + * @param task 需要被清空的任务 + */ + private void clearTaskScopeTypeAndSave(Task task) { + TaskEntityImpl taskImpl = (TaskEntityImpl) task; + taskImpl.setScopeType(null); + taskService.saveTask(task); + } + + /** + * 获取子任务个数 + * + * @param parentTaskId 父任务 ID + * @return 剩余子任务个数 + */ + private Long getChildrenTaskCount(String parentTaskId) { + String tableName = managementService.getTableName(TaskEntity.class); + String sql = "SELECT COUNT(1) from " + tableName + " WHERE PARENT_TASK_ID_=#{parentTaskId}"; + return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count(); + } + + /** + * 审批被委派的任务 + * + * @param reqVO 前端请求参数,包含当前任务ID,审批意见等 + * @param task 当前被审批的任务 + */ + private void approveDelegateTask(BpmTaskApproveReqVO reqVO, Task task) { + // 1. 添加审批意见 + AdminUserRespDTO currentUser = adminUserApi.getUser(WebFrameworkUtils.getLoginUserId()).getCheckedData(); + AdminUserRespDTO sourceApproveUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner())).getCheckedData(); + Assert.notNull(sourceApproveUser, "委派任务找不到原审批人,需要检查数据"); + String comment = StrUtil.format("[{}]完成委派任务,任务重新回到[{}]手中,审批意见为:{}", currentUser.getNickname(), + sourceApproveUser.getNickname(), reqVO.getReason()); + taskService.addComment(reqVO.getId(), task.getProcessInstanceId(), + BpmCommentTypeEnum.DELEGATE.getType().toString(), comment); + + // 2.1 调用 resolveTask 完成任务。 + // 底层调用 TaskHelper.changeTaskAssignee(task, task.getOwner()):将 owner 设置为 assignee + taskService.resolveTask(task.getId()); + // 2.2 更新任务拓展表为【处理中】 + taskExtMapper.updateByTaskId( + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()) + .setReason(reqVO.getReason())); } @Override @Transactional(rollbackFor = Exception.class) public void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO) { - Task task = checkTask(userId, reqVO.getId()); + Task task = validateTask(userId, reqVO.getId()); // 校验流程实例存在 ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); if (instance == null) { @@ -207,14 +443,14 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 更新任务拓展表为不通过 taskExtMapper.updateByTaskId( - new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult()) - .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason())); + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult()) + .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason())); } @Override public void updateTaskAssignee(Long userId, BpmTaskUpdateAssigneeReqVO reqVO) { // 校验任务存在 - Task task = checkTask(userId, reqVO.getId()); + Task task = validateTask(userId, reqVO.getId()); // 更新负责人 updateTaskAssignee(task.getId(), reqVO.getAssigneeUserId()); } @@ -230,21 +466,22 @@ public class BpmTaskServiceImpl implements BpmTaskService { * @param userId 用户 id * @param taskId task id */ - private Task checkTask(Long userId, String taskId) { - Task task = getTask(taskId); - if (task == null) { - throw exception(TASK_COMPLETE_FAIL_NOT_EXISTS); - } + private Task validateTask(Long userId, String taskId) { + Task task = validateTaskExist(taskId); if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) { - throw exception(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF); + throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF); } return task; } @Override public void createTaskExt(Task task) { - BpmTaskExtDO taskExtDO = - BpmTaskConvert.INSTANCE.convert2TaskExt(task).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); + BpmTaskExtDO taskExtDO = BpmTaskConvert.INSTANCE.convert2TaskExt(task) + .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); + // 向后加签生成的任务,状态不能为进行中,需要等前面父任务完成 + if (BpmTaskAddSignTypeEnum.AFTER_CHILDREN_TASK.getType().equals(task.getScopeType())) { + taskExtDO.setResult(BpmProcessInstanceResultEnum.WAIT_BEFORE_TASK.getResult()); + } taskExtMapper.insert(taskExtDO); } @@ -292,21 +529,31 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override public void updateTaskExtAssign(Task task) { BpmTaskExtDO taskExtDO = - new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId()); + new BpmTaskExtDO().setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())).setTaskId(task.getId()); taskExtMapper.updateByTaskId(taskExtDO); // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { - ProcessInstance processInstance = - processInstanceService.getProcessInstance(task.getProcessInstanceId()); - AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData(); - messageService.sendMessageWhenTaskAssigned( - BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); + if (StrUtil.isNotEmpty(task.getAssignee())) { + ProcessInstance processInstance = + processInstanceService.getProcessInstance(task.getProcessInstanceId()); + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData(); + messageService.sendMessageWhenTaskAssigned( + BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); + } } }); } + private Task validateTaskExist(String id) { + Task task = getTask(id); + if (task == null) { + throw exception(TASK_NOT_EXISTS); + } + return task; + } + private Task getTask(String id) { return taskService.createTaskQuery().taskId(id).singleResult(); } @@ -315,4 +562,409 @@ public class BpmTaskServiceImpl implements BpmTaskService { return historyService.createHistoricTaskInstanceQuery().taskId(id).singleResult(); } + @Override + public List getReturnTaskList(String taskId) { + // 1. 校验当前任务 task 存在 + Task task = validateTaskExist(taskId); + // 根据流程定义获取流程模型信息 + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); + FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + if (source == null) { + throw exception(TASK_NOT_EXISTS); + } + + // 2.1 查询该任务的前置任务节点的 key 集合 + List previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null); + if (CollUtil.isEmpty(previousUserList)) { + return Collections.emptyList(); + } + // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 + previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null)); + return BpmTaskConvert.INSTANCE.convertList(previousUserList); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void returnTask(Long userId, BpmTaskReturnReqVO reqVO) { + // 1.1 当前任务 task + Task task = validateTask(userId, reqVO.getId()); + if (task.isSuspended()) { + throw exception(TASK_IS_PENDING); + } + // 1.2 校验源头和目标节点的关系,并返回目标元素 + FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(), reqVO.getTargetDefinitionKey(), task.getProcessDefinitionId()); + + // 2. 调用 flowable 框架的回退逻辑 + returnTask0(task, targetElement, reqVO); + + // 3. 更新任务扩展表 + taskExtMapper.updateByTaskId(new BpmTaskExtDO().setTaskId(task.getId()) + .setResult(BpmProcessInstanceResultEnum.BACK.getResult()) + .setEndTime(LocalDateTime.now()).setReason(reqVO.getReason())); + } + + /** + * 回退流程节点时,校验目标任务节点是否可回退 + * + * @param sourceKey 当前任务节点 Key + * @param targetKey 目标任务节点 key + * @param processDefinitionId 当前流程定义 ID + * @return 目标任务节点元素 + */ + private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) { + // 1.1 获取流程模型信息 + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); + // 1.3 获取当前任务节点元素 + FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); + // 1.3 获取跳转的节点元素 + FlowElement target = BpmnModelUtils.getFlowElementById(bpmnModel, targetKey); + if (target == null) { + throw exception(TASK_TARGET_NODE_NOT_EXISTS); + } + + // 2.2 只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 + if (!BpmnModelUtils.isSequentialReachable(source, target, null)) { + throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR); + } + return target; + } + + /** + * 执行回退逻辑 + * + * @param currentTask 当前回退的任务 + * @param targetElement 需要回退到的目标任务 + * @param reqVO 前端参数封装 + */ + public void returnTask0(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) { + // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤 + // 1.1 获取所有正常进行的任务节点 Key + List taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list(); + List runTaskKeyList = convertList(taskList, Task::getTaskDefinitionKey); + // 1.2 通过 targetElement 的出口连线,计算在 runTaskKeyList 有哪些 key 需要被撤回 + // 为什么不直接使用 runTaskKeyList 呢?因为可能存在多个审批分支,例如说:A -> B -> C 和 D -> F,而只要 C 撤回到 A,需要排除掉 F + List returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null); + List returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId); + + // 2. 给当前要被回退的 task 数组,设置回退意见 + taskList.forEach(task -> { + // 需要排除掉,不需要设置回退意见的任务 + if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) { + return; + } + taskService.addComment(task.getId(), currentTask.getProcessInstanceId(), + BpmCommentTypeEnum.BACK.getType().toString(), reqVO.getReason()); + }); + + // 3. 执行驳回 + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(currentTask.getProcessInstanceId()) + .moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多) + reqVO.getTargetDefinitionKey()) // targetKey 跳转到的节点(1) + .changeState(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delegateTask(Long userId, BpmTaskDelegateReqVO reqVO) { + // 1.1 校验任务 + Task task = validateTaskCanDelegate(userId, reqVO); + // 1.2 校验目标用户存在 + AdminUserRespDTO delegateUser = adminUserApi.getUser(reqVO.getDelegateUserId()).getCheckedData(); + if (delegateUser == null) { + throw exception(TASK_DELEGATE_FAIL_USER_NOT_EXISTS); + } + + // 2. 添加审批意见 + AdminUserRespDTO currentUser = adminUserApi.getUser(userId).getCheckedData(); + String comment = StrUtil.format("[{}]将任务委派给[{}],委派理由为:{}", currentUser.getNickname(), + delegateUser.getNickname(), reqVO.getReason()); + String taskId = reqVO.getId(); + taskService.addComment(taskId, task.getProcessInstanceId(), + BpmCommentTypeEnum.DELEGATE.getType().toString(), comment); + + // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee) + taskService.setOwner(taskId, task.getAssignee()); + // 3.2 执行委派,将任务委派给 receiveId + taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString()); + // 3.3 更新任务拓展表为【委派】 + taskExtMapper.updateByTaskId( + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.DELEGATE.getResult()) + .setReason(reqVO.getReason())); + } + + /** + * 校验任务委派参数 + * + * @param userId 用户编号 + * @param reqVO 任务编号,接收人ID + * @return 当前任务信息 + */ + private Task validateTaskCanDelegate(Long userId, BpmTaskDelegateReqVO reqVO) { + // 校验任务 + Task task = validateTask(userId, reqVO.getId()); + // 校验当前审批人和被委派人不是同一人 + if (task.getAssignee().equals(reqVO.getDelegateUserId().toString())) { + throw exception(TASK_DELEGATE_FAIL_USER_REPEAT); + } + return task; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void createSignTask(Long userId, BpmTaskAddSignReqVO reqVO) { + // 1. 获取和校验任务 + TaskEntityImpl taskEntity = validateAddSign(userId, reqVO); + List userList = adminUserApi.getUserList(reqVO.getUserIdList()).getCheckedData(); + if (CollUtil.isEmpty(userList)) { + throw exception(TASK_ADD_SIGN_USER_NOT_EXIST); + } + + // 2. 处理当前任务 + // 2.1 开启计数功能,主要用于为了让表 ACT_RU_TASK 中的 SUB_TASK_COUNT_ 字段记录下总共有多少子任务,后续可能有用 + taskEntity.setCountEnabled(true); + if (reqVO.getType().equals(BpmTaskAddSignTypeEnum.BEFORE.getType())) { + // 2.2 向前加签,设置 owner,置空 assign。等子任务都完成后,再调用 resolveTask 重新将 owner 设置为 assign + // 原因是:不能和向前加签的子任务一起审批,需要等前面的子任务都完成才能审批 + taskEntity.setOwner(taskEntity.getAssignee()); + taskEntity.setAssignee(null); + // 2.3 更新扩展表状态 + taskExtMapper.updateByTaskId( + new BpmTaskExtDO().setTaskId(taskEntity.getId()) + .setResult(BpmProcessInstanceResultEnum.SIGN_BEFORE.getResult()) + .setReason(reqVO.getReason())); + } + // 2.4 记录加签方式,完成任务时需要用到判断 + taskEntity.setScopeType(reqVO.getType()); + // 2.5 保存当前任务修改后的值 + taskService.saveTask(taskEntity); + + // 3. 创建加签任务 + createSignTask(convertList(reqVO.getUserIdList(), String::valueOf), taskEntity); + + // 4. 记录加签 comment,拼接结果为: [当前用户]向前加签/向后加签给了[多个用户],理由为:reason + AdminUserRespDTO currentUser = adminUserApi.getUser(userId).getCheckedData(); + String comment = StrUtil.format(BpmCommentTypeEnum.ADD_SIGN.getComment(), currentUser.getNickname(), + BpmTaskAddSignTypeEnum.formatDesc(reqVO.getType()), String.join(",", convertList(userList, AdminUserRespDTO::getNickname)), reqVO.getReason()); + taskService.addComment(reqVO.getId(), taskEntity.getProcessInstanceId(), + BpmCommentTypeEnum.ADD_SIGN.getType().toString(), comment); + } + + + /** + * 校验任务的加签是否一致 + *

+ * 1. 如果存在“向前加签”的任务,则不能“向后加签” + * 2. 如果存在“向后加签”的任务,则不能“向前加签” + * + * @param userId 当前用户 ID + * @param reqVO 请求参数,包含任务 ID 和加签类型 + * @return 当前任务 + */ + private TaskEntityImpl validateAddSign(Long userId, BpmTaskAddSignReqVO reqVO) { + TaskEntityImpl taskEntity = (TaskEntityImpl) validateTask(userId, reqVO.getId()); + // 向前加签和向后加签不能同时存在 + if (StrUtil.isNotBlank(taskEntity.getScopeType()) + && ObjectUtil.notEqual(BpmTaskAddSignTypeEnum.AFTER_CHILDREN_TASK.getType(), taskEntity.getScopeType()) + && ObjectUtil.notEqual(taskEntity.getScopeType(), reqVO.getType())) { + throw exception(TASK_ADD_SIGN_TYPE_ERROR, + BpmTaskAddSignTypeEnum.formatDesc(taskEntity.getScopeType()), BpmTaskAddSignTypeEnum.formatDesc(reqVO.getType())); + } + // 同一个 key 的任务,审批人不重复 + List taskList = taskService.createTaskQuery().processInstanceId(taskEntity.getProcessInstanceId()) + .taskDefinitionKey(taskEntity.getTaskDefinitionKey()).list(); + List currentAssigneeList = convertList(taskList, task -> NumberUtils.parseLong(task.getAssignee())); + // 保留交集在 currentAssigneeList 中 + currentAssigneeList.retainAll(reqVO.getUserIdList()); + if (CollUtil.isNotEmpty(currentAssigneeList)) { + List userList = adminUserApi.getUserList(currentAssigneeList).getCheckedData(); + throw exception(TASK_ADD_SIGN_USER_REPEAT, String.join(",", convertList(userList, AdminUserRespDTO::getNickname))); + } + return taskEntity; + } + + /** + * 创建加签子任务 + * + * @param addSingUserIdList 被加签的用户 ID + * @param taskEntity 被加签的任务 + */ + private void createSignTask(List addSingUserIdList, TaskEntityImpl taskEntity) { + if (CollUtil.isEmpty(addSingUserIdList)) { + return; + } + // 创建加签人的新任务,全部基于 taskEntity 为父任务来创建 + for (String addSignId : addSingUserIdList) { + if (StrUtil.isBlank(addSignId)) { + continue; + } + createSignTask(taskEntity, addSignId); + } + } + + /** + * 创建加签子任务 + * + * @param parentTask 父任务 + * @param assignee 子任务的执行人 + */ + private void createSignTask(TaskEntityImpl parentTask, String assignee) { + // 1. 生成子任务 + TaskEntityImpl task = (TaskEntityImpl) taskService.newTask(IdUtil.fastSimpleUUID()); + task = BpmTaskConvert.INSTANCE.convert(task, parentTask); + if (BpmTaskAddSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) { + // 2.1 前加签,设置审批人 + task.setAssignee(assignee); + } else { + // 2.2.1 设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成 + task.setOwner(assignee); + // 2.2.2 设置向后加签任务的 scopeType 为 afterChildrenTask,用于设置任务扩展表的状态 + task.setScopeType(BpmTaskAddSignTypeEnum.AFTER_CHILDREN_TASK.getType()); + } + // 2. 保存子任务 + taskService.saveTask(task); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteSignTask(Long userId, BpmTaskSubSignReqVO reqVO) { + // 1.1 校验 task 可以被减签 + Task task = validateSubSign(reqVO.getId()); + // 1.2 校验取消人存在 + AdminUserRespDTO cancelUser = null; + if (StrUtil.isNotBlank(task.getAssignee())) { + cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getAssignee())).getCheckedData(); + } + if (cancelUser == null && StrUtil.isNotBlank(task.getOwner())) { + cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner())).getCheckedData(); + } + Assert.notNull(cancelUser, "任务中没有所有者和审批人,数据错误"); + + // 2. 删除任务和对应子任务 + // 2.1 获取所有需要删除的任务 ID ,包含当前任务和所有子任务 + List allTaskIdList = getAllChildTaskIds(task.getId()); + // 2.2 删除任务和所有子任务 + taskService.deleteTasks(allTaskIdList); + // 2.3 修改扩展表状态为取消 + AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData(); + taskExtMapper.updateBatchByTaskIdList(allTaskIdList, new BpmTaskExtDO().setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()) + .setReason(StrUtil.format("由于{}操作[减签],任务被取消", user.getNickname()))); + + // 3. 记录日志到父任务中。先记录日志是因为,通过 handleParentTask 方法之后,任务可能被完成了,并且不存在了,会报异常,所以先记录 + String comment = StrUtil.format(BpmCommentTypeEnum.SUB_SIGN.getComment(), user.getNickname(), cancelUser.getNickname()); + taskService.addComment(task.getParentTaskId(), task.getProcessInstanceId(), + BpmCommentTypeEnum.SUB_SIGN.getType().toString(), comment); + + // 4. 处理当前任务的父任务 + handleParentTask(task); + } + + /** + * 校验任务是否能被减签 + * + * @param id 任务ID + * @return 任务信息 + */ + private Task validateSubSign(String id) { + Task task = validateTaskExist(id); + + // 必须有 scopeType + String scopeType = task.getScopeType(); + if (StrUtil.isEmpty(scopeType)) { + throw exception(TASK_SUB_SIGN_NO_PARENT); + } + // 并且值为 向前和向后加签 + if (!validateSignType(scopeType)) { + throw exception(TASK_SUB_SIGN_NO_PARENT); + } + return task; + } + + /** + * 判断当前类型是否为加签 + * @param scopeType 任务的 scopeType + * @return 当前 scopeType 为加签则返回 true + */ + private boolean validateSignType(String scopeType){ + return StrUtil.equalsAny(scopeType,BpmTaskAddSignTypeEnum.BEFORE.getType(),scopeType, BpmTaskAddSignTypeEnum.AFTER.getType()); + } + + /** + * 获取所有要被取消的删除的任务 ID 集合 + * + * @param parentTaskId 父级任务ID + * @return 所有任务ID + */ + public List getAllChildTaskIds(String parentTaskId) { + List allChildTaskIds = new ArrayList<>(); + // 1. 递归获取子级 + Stack stack = new Stack<>(); + // 1.1 将根任务ID入栈 + stack.push(parentTaskId); + //控制遍历的次数不超过 Byte.MAX_VALUE,避免脏数据造成死循环 + int count = 0; + // TODO @海:< 的前后空格,要注意哈; + while (!stack.isEmpty() && count childrenTaskIdList = getChildrenTaskIdList(taskId); + if (CollUtil.isNotEmpty(childrenTaskIdList)) { + for (String childTaskId : childrenTaskIdList) { + // 1.5 将子任务ID入栈,以便后续处理 + stack.push(childTaskId); + } + } + count++; + } + return allChildTaskIds; + } + + /** + * 获取指定父级任务的所有子任务 ID 集合 + * + * @param parentTaskId 父任务 ID + * @return 所有子任务的 ID 集合 + */ + private List getChildrenTaskIdList(String parentTaskId) { + return convertList(getChildrenTaskList0(parentTaskId), Task::getId); + } + + /** + * 获取指定父级任务的所有子任务 ID 集合 + * + * @param parentTaskId 父任务 ID + * @return 所有子任务的 ID 集合 + */ + private List getChildrenTaskList0(String parentTaskId) { + String tableName = managementService.getTableName(TaskEntity.class); + // taskService.createTaskQuery() 没有 parentId 参数,所以写 sql 查询 + String sql = "select ID_,OWNER_,ASSIGNEE_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}"; + return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list(); + } + + + @Override + public List getChildrenTaskList(String parentId) { + // 1. 只查询进行中的任务 后加签的任务,可能不存在 assignee,所以还需要查询 owner + List taskList = getChildrenTaskList0(parentId); + if (CollUtil.isEmpty(taskList)) { + return Collections.emptyList(); + } + List childrenTaskIdList = convertList(taskList, Task::getId); + + // 2.1 将 owner 和 assignee 统一到一个集合中 + List userIds = convertListByFlatMap(taskList, control -> + Stream.of(NumberUtils.parseLong(control.getAssignee()), NumberUtils.parseLong(control.getOwner())) + .filter(Objects::nonNull)); + // 2.2 组装数据 + Map userMap = adminUserApi.getUserMap(userIds); + List taskExtList = taskExtMapper.selectProcessListByTaskIds(childrenTaskIdList); + Map idTaskMap = convertMap(taskList, TaskInfo::getId); + return BpmTaskConvert.INSTANCE.convertList(taskExtList, userMap, idTaskMap); + } + } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java index e7f34bf52..96e3f110a 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java @@ -30,7 +30,7 @@ public interface AdminUserApi { @GetMapping(PREFIX + "/list") @Operation(summary = "通过用户 ID 查询用户们") @Parameter(name = "ids", description = "部门编号数组", example = "1,2", required = true) - CommonResult> getUsers(@RequestParam("ids") Collection ids); + CommonResult> getUserList(@RequestParam("ids") Collection ids); @GetMapping(PREFIX + "/list-by-dept-id") @Operation(summary = "获得指定部门的用户数组") @@ -49,7 +49,7 @@ public interface AdminUserApi { * @return 用户 Map */ default Map getUserMap(Collection ids) { - List users = getUsers(ids).getCheckedData(); + List users = getUserList(ids).getCheckedData(); return CollectionUtils.convertMap(users, AdminUserRespDTO::getId); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java index 05279a7d8..3e1f45483 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java @@ -29,7 +29,7 @@ public class AdminUserApiImpl implements AdminUserApi { } @Override - public CommonResult> getUsers(Collection ids) { + public CommonResult> getUserList(Collection ids) { List users = userService.getUserList(ids); return success(UserConvert.INSTANCE.convertList4(users)); }