Commit 61b0624a591753da3d855f353f37a2e175134d28

Authored by 芋道源码
Committed by Gitee
2 parents c06077e3 19a703b8

!262 接口实现-交易订单创建(未完全实现)

Merge pull request !262 from LeeYan9/ly_uniapp
Showing 23 changed files with 831 additions and 4 deletions
sql/optional/mall/order.sql 0 → 100644
  1 +/**todo cancelType 设置默认值 0?*/
  2 +CREATE TABLE `trade_order`
  3 +(
  4 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  5 + `sn` varchar(32) NOT NULL COMMENT '订单流水号',
  6 + `type` int NOT NULL DEFAULT '0' COMMENT '订单类型:[0:普通订单 1:秒杀订单 2:拼团订单 3:砍价订单]',
  7 + `terminal` int NOT NULL COMMENT '订单来源终端:[1:小程序 2:H5 3:iOS 4:安卓]',
  8 + `user_id` bigint unsigned NOT NULL COMMENT '用户编号',
  9 + `user_ip` varchar(30) NOT NULL DEFAULT '' COMMENT '用户 IP',
  10 + `user_remark` varchar(200) DEFAULT NULL COMMENT '用户备注',
  11 + `status` int NOT NULL DEFAULT '0' COMMENT '订单状态:[0:待付款 1:待发货 2:待收货 3:已完成 4:已关闭]',
  12 + `product_count` int NOT NULL COMMENT '购买的商品数量',
  13 + `cancel_type` int NOT NULL COMMENT '取消类型:[10:超时未支付 20:退款关闭 30:买家取消 40:已通过货到付款交易]',
  14 + `remark` varchar(200) DEFAULT NULL COMMENT '商家备注',
  15 + `payed` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已支付:[0:未支付 1:已经支付过]',
  16 + `finish_time` datetime DEFAULT NULL COMMENT '订单完成时间',
  17 + `cancel_time` datetime DEFAULT NULL COMMENT '订单取消时间',
  18 + `sku_original_price` int NOT NULL DEFAULT '0' COMMENT '商品原价(总),单位:分',
  19 + `sku_promotion_price` int NOT NULL DEFAULT '0' COMMENT '商品优惠(总),单位:分',
  20 + `order_promotion_price` int NOT NULL DEFAULT '0' COMMENT '订单优惠(总),单位:分',
  21 + `delivery_price` int NOT NULL DEFAULT '0' COMMENT '运费金额,单位:分',
  22 + `pay_price` int NOT NULL DEFAULT '0' COMMENT '应付金额(总),单位:分',
  23 + `pay_order_id` int NOT NULL COMMENT '支付订单编号',
  24 + `pay_channel` int NOT NULL COMMENT '支付成功的支付渠道',
  25 + `delivery_type` int NOT NULL DEFAULT '1' COMMENT '配送方式:[1:快递发货 2:自提]',
  26 + `actual_delivery_type` int NOT NULL DEFAULT '1' COMMENT '实际的配送方式:[1:快递发货 2:自提]',
  27 + `delivery_templateid` int DEFAULT NULL COMMENT '配置模板的编号',
  28 + `express_no` int DEFAULT NULL COMMENT '物流公司单号',
  29 + `delivery_status` bit(1) NOT NULL DEFAULT b'0' COMMENT '发货状态[0:未发货 1:已发货]',
  30 + `delivery_time` datetime DEFAULT NULL COMMENT '发货时间',
  31 + `receive_time` datetime DEFAULT NULL COMMENT '收货时间',
  32 + `receiver_name` varchar(20) NOT NULL COMMENT '收件人名称',
  33 + `receiver_mobile` varchar(20) NOT NULL COMMENT '收件人手机',
  34 + `receiver_area_id` int NOT NULL COMMENT '收件人地区编号',
  35 + `receiver_post_code` int DEFAULT NULL COMMENT '收件人邮编',
  36 + `receiver_detail_address` varchar(255) NOT NULL COMMENT '收件人详细地址',
  37 + `refund_status` int NOT NULL DEFAULT '0' COMMENT '订单状态:[0:未退款 1:部分退款 2:全部退款]',
  38 + `refund_price` int NOT NULL DEFAULT '0' COMMENT '退款金额,单位:分',
  39 + `coupon_id` bigint unsigned NOT NULL COMMENT '优惠劵编号',
  40 + `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  41 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  42 + `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  43 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  44 + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  45 + PRIMARY KEY (`id`) USING BTREE
  46 +) ENGINE = InnoDB COMMENT ='交易订单表';
  47 +
  48 +
  49 +DROP TABLE IF EXISTS `trade_order_item`;
  50 +CREATE TABLE `trade_order_item`
  51 +(
  52 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  53 + `user_id` bigint unsigned NOT NULL COMMENT '用户编号',
  54 + `order_Id` bigint unsigned NOT NULL COMMENT '订单编号',
  55 + `spu_id` bigint unsigned NOT NULL COMMENT '商品 SPU 编号',
  56 + `sku_id` bigint unsigned NOT NULL COMMENT '商品 SKU 编号',
  57 + `properties` json DEFAULT NULL COMMENT '规格值数组,JSON 格式',
  58 + `name` varchar(128) NOT NULL DEFAULT '' COMMENT '商品名称',
  59 + `pic_url` varchar(200) DEFAULT NULL COMMENT '商品图片',
  60 + `count` int NOT NULL COMMENT '购买数量',
  61 + `commented` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否评论:[0:未评论 1:已评论]',
  62 + `original_price` int NOT NULL DEFAULT '0' COMMENT '商品原价(单),单位:分',
  63 + `total_original_price` int NOT NULL DEFAULT '0' COMMENT '商品原价(总),单位:分',
  64 + `total_promotion_price` int NOT NULL DEFAULT '0' COMMENT '商品级优惠(总),单位:分',
  65 + `present_price` int NOT NULL DEFAULT '0' COMMENT '最终购买金额(单),单位:分。',
  66 + `total_present_price` int NOT NULL DEFAULT '0' COMMENT '最终购买金额(总),单位:分。',
  67 + `total_pay_price` int NOT NULL DEFAULT '0' COMMENT '应付金额(总),单位:分',
  68 + `refund_status` int NOT NULL DEFAULT '0' COMMENT '退款状态:[0:未申请退款 1:申请退款 2:等待退款 3:退款成功]',
  69 + `refund_total` int NOT NULL DEFAULT '0' COMMENT '退款总金额,单位:分',
  70 + `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  71 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  72 + `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  73 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  74 + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  75 + PRIMARY KEY (`id`) USING BTREE
  76 +) ENGINE = InnoDB COMMENT ='交易订单明细表';
0 \ No newline at end of file 77 \ No newline at end of file
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java
@@ -15,6 +15,7 @@ import java.util.Arrays; @@ -15,6 +15,7 @@ import java.util.Arrays;
15 @Getter 15 @Getter
16 public enum TerminalEnum implements IntArrayValuable { 16 public enum TerminalEnum implements IntArrayValuable {
17 17
  18 + //TODO terminal 重复,请参考 '订单来源终端:[1:小程序 2:H5 3:iOS 4:安卓]'
18 MINI_PROGRAM(1, "小程序"), 19 MINI_PROGRAM(1, "小程序"),
19 H5(2, "H5"), 20 H5(2, "H5"),
20 IOS(3, "iOS"), 21 IOS(3, "iOS"),
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.product.api.sku;
  2 +
  3 +import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
  4 +import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO;
  5 +
  6 +import java.util.Collection;
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * @author LeeYan9
  11 + * @since 2022-08-26
  12 + */
  13 +public interface ProductSkuApi {
  14 +
  15 +
  16 + /**
  17 + * 根据skuId列表 查询sku信息
  18 + *
  19 + * @param skuIds sku ID列表
  20 + * @return sku信息列表
  21 + */
  22 + List<SkuInfoRespDTO> getSkusByIds(Collection<Long> skuIds);
  23 +
  24 + /**
  25 + * 批量扣减sku库存
  26 + *
  27 + * @param batchReqDTO sku库存信息列表
  28 + */
  29 + void decrementStockBatch(SkuDecrementStockBatchReqDTO batchReqDTO);
  30 +}
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/SkuDecrementStockBatchReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.product.api.sku.dto;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * @author LeeYan9
  11 + * @since 2022-08-26
  12 + */
  13 +@Data
  14 +@NoArgsConstructor
  15 +@AllArgsConstructor
  16 +public class SkuDecrementStockBatchReqDTO {
  17 +
  18 +
  19 + private List<Item> items;
  20 +
  21 + @Data
  22 + public static class Item {
  23 +
  24 + /**
  25 + * 商品 SPU 编号,自增
  26 + */
  27 + private Long productId;
  28 +
  29 + /**
  30 + * 商品 SKU 编号,自增
  31 + */
  32 + private Long skuId;
  33 +
  34 + /**
  35 + * 数量
  36 + */
  37 + private Integer count;
  38 +
  39 + }
  40 +
  41 + public static SkuDecrementStockBatchReqDTO of(List<Item> items) {
  42 + return new SkuDecrementStockBatchReqDTO(items);
  43 + }
  44 +
  45 +}
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/SkuInfoRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.product.api.sku.dto;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import lombok.Data;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * @author LeeYan9
  10 + * @since 2022-08-26
  11 + */
  12 +@Data
  13 +public class SkuInfoRespDTO {
  14 +
  15 + /**
  16 + * 商品 SKU 编号,自增
  17 + */
  18 + private Long id;
  19 + /**
  20 + * 商品 SKU 名字
  21 + */
  22 + private String name;
  23 + /**
  24 + * SPU 编号
  25 + */
  26 + private Long spuId;
  27 +
  28 + /**
  29 + * 规格值数组,JSON 格式
  30 + */
  31 + private List<Property> properties;
  32 + /**
  33 + * 销售价格,单位:分
  34 + */
  35 + private Integer price;
  36 + /**
  37 + * 市场价,单位:分
  38 + */
  39 + private Integer marketPrice;
  40 + /**
  41 + * 成本价,单位:分
  42 + */
  43 + private Integer costPrice;
  44 + /**
  45 + * SKU 的条形码
  46 + */
  47 + private String barCode;
  48 + /**
  49 + * 图片地址
  50 + */
  51 + private String picUrl;
  52 + /**
  53 + * SKU 状态
  54 + * <p>
  55 + * 枚举 {@link CommonStatusEnum}
  56 + */
  57 + private Integer status;
  58 + /**
  59 + * 库存
  60 + */
  61 + private Integer stock;
  62 + /**
  63 + * 预警预存
  64 + */
  65 + private Integer warnStock;
  66 + /**
  67 + * 商品重量,单位:kg 千克
  68 + */
  69 + private Double weight;
  70 + /**
  71 + * 商品体积,单位:m^3 平米
  72 + */
  73 + private Double volume;
  74 +
  75 + /**
  76 + * 商品属性
  77 + */
  78 + @Data
  79 + public static class Property {
  80 +
  81 + /**
  82 + * 属性编号
  83 + */
  84 + private Long propertyId;
  85 + /**
  86 + * 属性值编号
  87 + */
  88 + private Long valueId;
  89 +
  90 + }
  91 +
  92 +
  93 +}
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.product.api.spu;
  2 +
  3 +import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO;
  4 +import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
  5 +
  6 +import java.util.Collection;
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * @author LeeYan9
  11 + * @since 2022-08-26
  12 + */
  13 +public interface ProductSpuApi {
  14 +
  15 +
  16 + /**
  17 + * 根据spuId列表 查询spu信息
  18 + *
  19 + * @param spuIds spu ID列表
  20 + * @return spu信息列表
  21 + */
  22 + List<SpuInfoRespDTO> getSpusByIds(Collection<Long> spuIds);
  23 +}
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/SpuInfoRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.product.api.spu.dto;
  2 +
  3 +import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO;
  4 +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum;
  5 +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * @author LeeYan9
  12 + * @since 2022-08-26
  13 + */
  14 +@Data
  15 +public class SpuInfoRespDTO {
  16 +
  17 + /**
  18 + * 商品 SPU 编号,自增
  19 + */
  20 + private Long id;
  21 +
  22 + // ========== 基本信息 =========
  23 +
  24 + /**
  25 + * 商品名称
  26 + */
  27 + private String name;
  28 + /**
  29 + * 商品编码
  30 + */
  31 + private String code;
  32 + /**
  33 + * 商品卖点
  34 + */
  35 + private String sellPoint;
  36 + /**
  37 + * 商品详情
  38 + */
  39 + private String description;
  40 + /**
  41 + * 商品分类编号
  42 + */
  43 + private Long categoryId;
  44 + /**
  45 + * 商品品牌编号
  46 + */
  47 + private Long brandId;
  48 + /**
  49 + * 商品图片的数组
  50 + * <p>
  51 + * 1. 第一张图片将作为商品主图,支持同时上传多张图;
  52 + * 2. 建议使用尺寸 800x800 像素以上、大小不超过 1M 的正方形图片;
  53 + * 3. 至少 1 张,最多上传 10 张
  54 + */
  55 + private List<String> picUrls;
  56 + /**
  57 + * 商品视频
  58 + */
  59 + private String videoUrl;
  60 +
  61 + /**
  62 + * 排序字段
  63 + */
  64 + private Integer sort;
  65 + /**
  66 + * 商品状态
  67 + * <p>
  68 + * 枚举 {@link ProductSpuStatusEnum}
  69 + */
  70 + private Integer status;
  71 +
  72 + // ========== SKU 相关字段 =========
  73 +
  74 + /**
  75 + * 规格类型
  76 + * <p>
  77 + * 枚举 {@link ProductSpuSpecTypeEnum}
  78 + */
  79 + private Integer specType;
  80 + /**
  81 + * 最小价格,单位使用:分
  82 + * <p>
  83 + * 基于其对应的 {@link SkuInfoRespDTO#getPrice()} 最小值
  84 + */
  85 + private Integer minPrice;
  86 + /**
  87 + * 最大价格,单位使用:分
  88 + * <p>
  89 + * 基于其对应的 {@link SkuInfoRespDTO#getPrice()} 最大值
  90 + */
  91 + private Integer maxPrice;
  92 + /**
  93 + * 市场价,单位使用:分
  94 + * <p>
  95 + * 基于其对应的 {@link SkuInfoRespDTO#getMarketPrice()} 最大值
  96 + */
  97 + private Integer marketPrice;
  98 + /**
  99 + * 总库存
  100 + * <p>
  101 + * 基于其对应的 {@link SkuInfoRespDTO#getStock()} 求和
  102 + */
  103 + private Integer totalStock;
  104 + /**
  105 + * 是否展示库存
  106 + */
  107 + private Boolean showStock;
  108 +
  109 + // ========== 统计相关字段 =========
  110 +
  111 + /**
  112 + * 商品销量
  113 + */
  114 + private Integer salesCount;
  115 + /**
  116 + * 虚拟销量
  117 + */
  118 + private Integer virtualSalesCount;
  119 + /**
  120 + * 商品点击量
  121 + */
  122 + private Integer clickCount;
  123 +
  124 +}
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/enums/ErrorCodeConstants.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.enums.enums;
  2 +
  3 +import cn.iocoder.yudao.framework.common.exception.ErrorCode;
  4 +
  5 +/**
  6 + * Trade-Order 错误码 Core 枚举类
  7 + * <p>
  8 + * Trade-Order 系统,使用 1-011-000-000 段
  9 + *
  10 + * @author LeeYan9
  11 + * @since 2022-08-26
  12 + */
  13 +public interface ErrorCodeConstants {
  14 +
  15 + /**
  16 + * ========== Order 模块 1-011-000-000 ==========
  17 + */
  18 + ErrorCode ORDER_SKU_NOT_FOUND = new ErrorCode(1011000001, "商品不存在");
  19 + ErrorCode ORDER_SPU_NOT_SALE = new ErrorCode(1011000002, "商品不可售卖");
  20 + ErrorCode ORDER_SKU_NOT_SALE = new ErrorCode(1011000003, "商品Sku不可售卖");
  21 + ErrorCode ORDER_SKU_STOCK_NOT_ENOUGH = new ErrorCode(1011000004, "商品库存不足");
  22 +
  23 +}
yudao-module-mall/yudao-module-trade-biz/pom.xml
@@ -23,12 +23,25 @@ @@ -23,12 +23,25 @@
23 <artifactId>yudao-module-trade-api</artifactId> 23 <artifactId>yudao-module-trade-api</artifactId>
24 <version>${revision}</version> 24 <version>${revision}</version>
25 </dependency> 25 </dependency>
  26 +
26 <dependency> 27 <dependency>
27 <groupId>cn.iocoder.boot</groupId> 28 <groupId>cn.iocoder.boot</groupId>
28 <artifactId>yudao-module-product-api</artifactId> 29 <artifactId>yudao-module-product-api</artifactId>
29 <version>${revision}</version> 30 <version>${revision}</version>
30 </dependency> 31 </dependency>
31 32
  33 + <dependency>
  34 + <groupId>cn.iocoder.boot</groupId>
  35 + <artifactId>yudao-module-pay-api</artifactId>
  36 + <version>${revision}</version>
  37 + </dependency>
  38 +
  39 + <dependency>
  40 + <groupId>cn.iocoder.boot</groupId>
  41 + <artifactId>yudao-module-market-api</artifactId>
  42 + <version>${revision}</version>
  43 + </dependency>
  44 +
32 <!-- 业务组件 --> 45 <!-- 业务组件 -->
33 <dependency> 46 <dependency>
34 <groupId>cn.iocoder.boot</groupId> 47 <groupId>cn.iocoder.boot</groupId>
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
1 package cn.iocoder.yudao.module.trade.controller.app.order; 1 package cn.iocoder.yudao.module.trade.controller.app.order;
2 2
  3 +import cn.hutool.extra.servlet.ServletUtil;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; 6 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
  7 +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
6 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO; 8 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
7 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderGetCreateInfoRespVO; 9 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderGetCreateInfoRespVO;
8 import cn.iocoder.yudao.module.trade.controller.app.order.vo.TradeOrderPageReqVO; 10 import cn.iocoder.yudao.module.trade.controller.app.order.vo.TradeOrderPageReqVO;
9 import cn.iocoder.yudao.module.trade.controller.app.order.vo.TradeOrderRespVO; 11 import cn.iocoder.yudao.module.trade.controller.app.order.vo.TradeOrderRespVO;
  12 +import cn.iocoder.yudao.module.trade.service.order.TradeOrderService;
10 import io.swagger.annotations.Api; 13 import io.swagger.annotations.Api;
11 import io.swagger.annotations.ApiImplicitParam; 14 import io.swagger.annotations.ApiImplicitParam;
12 import io.swagger.annotations.ApiOperation; 15 import io.swagger.annotations.ApiOperation;
  16 +import lombok.RequiredArgsConstructor;
13 import lombok.extern.slf4j.Slf4j; 17 import lombok.extern.slf4j.Slf4j;
14 import org.springframework.validation.annotation.Validated; 18 import org.springframework.validation.annotation.Validated;
15 import org.springframework.web.bind.annotation.*; 19 import org.springframework.web.bind.annotation.*;
@@ -19,12 +23,16 @@ import javax.servlet.http.HttpServletRequest; @@ -19,12 +23,16 @@ import javax.servlet.http.HttpServletRequest;
19 @Api(tags = "用户 App - 交易订单") 23 @Api(tags = "用户 App - 交易订单")
20 @RestController 24 @RestController
21 @RequestMapping("/trade/order") 25 @RequestMapping("/trade/order")
  26 +@RequiredArgsConstructor
22 @Validated 27 @Validated
23 @Slf4j 28 @Slf4j
24 public class AppTradeOrderController { 29 public class AppTradeOrderController {
25 30
26 // TODO 在思考下; 31 // TODO 在思考下;
27 32
  33 + private final TradeOrderService tradeOrderService;
  34 +
  35 +
28 @GetMapping("/get-create-info") 36 @GetMapping("/get-create-info")
29 @ApiOperation("基于商品,确认创建订单") 37 @ApiOperation("基于商品,确认创建订单")
30 @PreAuthenticated 38 @PreAuthenticated
@@ -36,11 +44,18 @@ public class AppTradeOrderController { @@ -36,11 +44,18 @@ public class AppTradeOrderController {
36 @PostMapping("/create") 44 @PostMapping("/create")
37 @ApiOperation("创建订单") 45 @ApiOperation("创建订单")
38 @PreAuthenticated 46 @PreAuthenticated
39 - public CommonResult<Integer> createTradeOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO,  
40 - HttpServletRequest servletRequest) { 47 + public CommonResult<Long> createTradeOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO,
  48 + HttpServletRequest servletRequest) {
41 // return success(tradeOrderService.createTradeOrder(UserSecurityContextHolder.getUserId(), 49 // return success(tradeOrderService.createTradeOrder(UserSecurityContextHolder.getUserId(),
42 // HttpUtil.getIp(servletRequest), createReqVO)); 50 // HttpUtil.getIp(servletRequest), createReqVO));
43 - return null; 51 + // 获取登录用户
  52 + Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
  53 + // 获取用户ip地址
  54 + String clientIp = ServletUtil.getClientIP(servletRequest);
  55 + // 创建交易订单,预支付记录
  56 + Long result = tradeOrderService.createTradeOrder(loginUserId, clientIp, createReqVO);
  57 +
  58 + return CommonResult.success(result);
44 } 59 }
45 60
46 @GetMapping("/get") 61 @GetMapping("/get")
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java
@@ -38,7 +38,7 @@ public class AppTradeOrderCreateReqVO { @@ -38,7 +38,7 @@ public class AppTradeOrderCreateReqVO {
38 38
39 @ApiModelProperty(name = "商品 SKU 编号", required = true, example = "111") 39 @ApiModelProperty(name = "商品 SKU 编号", required = true, example = "111")
40 @NotNull(message = "商品 SKU 编号不能为空") 40 @NotNull(message = "商品 SKU 编号不能为空")
41 - private Integer skuId; 41 + private Long skuId;
42 42
43 @ApiModelProperty(name = "商品 SKU 购买数量", required = true, example = "1024") 43 @ApiModelProperty(name = "商品 SKU 购买数量", required = true, example = "1024")
44 @NotNull(message = "商品 SKU 购买数量不能为空") 44 @NotNull(message = "商品 SKU 购买数量不能为空")
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.convert.order;
  2 +
  3 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
  4 +import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
  5 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
  6 +import org.mapstruct.Mapper;
  7 +import org.mapstruct.factory.Mappers;
  8 +
  9 +/**
  10 + * @author LeeYan9
  11 + * @since 2022-08-26
  12 + */
  13 +@Mapper
  14 +public interface TradeOrderConvert {
  15 +
  16 + TradeOrderConvert INSTANCE = Mappers.getMapper(TradeOrderConvert.class);
  17 +
  18 +
  19 + TradeOrderDO convert(AppTradeOrderCreateReqVO createReqVO, PriceCalculateRespDTO.Order order);
  20 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderItemConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.convert.order;
  2 +
  3 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
  4 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
  5 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
  6 +import org.mapstruct.Mapper;
  7 +import org.mapstruct.Mapping;
  8 +import org.mapstruct.Mappings;
  9 +import org.mapstruct.factory.Mappers;
  10 +
  11 +import java.util.List;
  12 +
  13 +/**
  14 + * @author LeeYan9
  15 + * @since 2022-08-26
  16 + */
  17 +@Mapper
  18 +public interface TradeOrderItemConvert {
  19 +
  20 + TradeOrderItemConvert INSTANCE = Mappers.getMapper(TradeOrderItemConvert.class);
  21 +
  22 + /**
  23 + *
  24 + * @param tradeOrder 交易订单
  25 + * @param items sku列表价格
  26 + * @return 订单项
  27 + */
  28 + @Mappings({
  29 + @Mapping(source = "tradeOrder.userId", target = "userId"),
  30 + @Mapping(source = "tradeOrder.orderId", target = "orderId")
  31 + })
  32 + List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrder, List<PriceCalculateRespDTO.Item> items);
  33 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/pay/PayOrderConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.convert.pay;
  2 +
  3 +import cn.iocoder.yudao.module.pay.api.order.PayOrderDataCreateReqDTO;
  4 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
  5 +import org.mapstruct.factory.Mappers;
  6 +
  7 +/**
  8 + * @author LeeYan9
  9 + * @since 2022-08-26
  10 + */
  11 +public interface PayOrderConvert {
  12 +
  13 + PayOrderConvert INSTANCE = Mappers.getMapper(PayOrderConvert.class);
  14 +
  15 +
  16 + PayOrderDataCreateReqDTO convert(TradeOrderDO tradeOrderDO);
  17 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/price/PriceConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.convert.price;
  2 +
  3 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
  4 +import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
  5 +import org.mapstruct.Mapper;
  6 +import org.mapstruct.Mapping;
  7 +import org.mapstruct.Mappings;
  8 +import org.mapstruct.factory.Mappers;
  9 +
  10 +/**
  11 + * @author LeeYan9
  12 + * @since 2022-08-26
  13 + */
  14 +@Mapper
  15 +public interface PriceConvert {
  16 +
  17 + PriceConvert INSTANCE = Mappers.getMapper(PriceConvert.class);
  18 +
  19 + @Mappings(
  20 + @Mapping(source = "userId" , target = "userId")
  21 + )
  22 + PriceCalculateReqDTO convert(AppTradeOrderCreateReqVO createReqVO , Long userId);
  23 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/sku/ProductSkuConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.convert.sku;
  2 +
  3 +import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
  4 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
  5 +import org.mapstruct.Mapper;
  6 +import org.mapstruct.factory.Mappers;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * @author LeeYan9
  12 + * @since 2022-08-26
  13 + */
  14 +@Mapper
  15 +public interface ProductSkuConvert {
  16 +
  17 + ProductSkuConvert INSTANCE = Mappers.getMapper(ProductSkuConvert.class);
  18 +
  19 + List<SkuDecrementStockBatchReqDTO.Item> convert(List<TradeOrderItemDO> tradeOrderItems);
  20 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.mysql.order;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
  5 +
  6 +/**
  7 + * @author LeeYan9
  8 + * @since 2022-08-26
  9 + */
  10 +public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
  11 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/orderitem/TradeOrderItemMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.dal.mysql.orderitem;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
  5 +
  6 +/**
  7 + * @author LeeYan9
  8 + * @since 2022-08-26
  9 + */
  10 +public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> {
  11 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.order;
  2 +
  3 +import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
  4 +
  5 +/**
  6 + * @author LeeYan9
  7 + * @since 2022-08-26
  8 + */
  9 +public interface TradeOrderService {
  10 +
  11 + /**
  12 + * 创建交易订单
  13 + * @param loginUserId 登录用户
  14 + * @param clientIp 用户ip地址
  15 + * @param createReqVO 创建交易订单请求模型
  16 + * @return 交易订单创建结果
  17 + */
  18 + Long createTradeOrder(Long loginUserId, String clientIp, AppTradeOrderCreateReqVO createReqVO);
  19 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.order;
  2 +
  3 +import cn.hutool.core.bean.BeanUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  5 +import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
  6 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  7 +import cn.iocoder.yudao.module.market.api.price.PriceApi;
  8 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateReqDTO;
  9 +import cn.iocoder.yudao.module.market.api.price.dto.PriceCalculateRespDTO;
  10 +import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
  11 +import cn.iocoder.yudao.module.pay.api.order.PayOrderDataCreateReqDTO;
  12 +import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
  13 +import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
  14 +import cn.iocoder.yudao.module.product.api.sku.dto.SkuInfoRespDTO;
  15 +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
  16 +import cn.iocoder.yudao.module.product.api.spu.dto.SpuInfoRespDTO;
  17 +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
  18 +import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
  19 +import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO.Item;
  20 +import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
  21 +import cn.iocoder.yudao.module.trade.convert.order.TradeOrderItemConvert;
  22 +import cn.iocoder.yudao.module.trade.convert.pay.PayOrderConvert;
  23 +import cn.iocoder.yudao.module.trade.convert.price.PriceConvert;
  24 +import cn.iocoder.yudao.module.trade.convert.sku.ProductSkuConvert;
  25 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
  26 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
  27 +import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
  28 +import cn.iocoder.yudao.module.trade.dal.mysql.orderitem.TradeOrderItemMapper;
  29 +import cn.iocoder.yudao.module.trade.enums.enums.ErrorCodeConstants;
  30 +import lombok.RequiredArgsConstructor;
  31 +import org.springframework.stereotype.Service;
  32 +import org.springframework.transaction.annotation.Transactional;
  33 +
  34 +import java.util.List;
  35 +import java.util.Map;
  36 +import java.util.Objects;
  37 +
  38 +/**
  39 + * @author LeeYan9
  40 + * @since 2022-08-26
  41 + */
  42 +@Service
  43 +@RequiredArgsConstructor
  44 +public class TradeOrderServiceImpl implements TradeOrderService {
  45 +
  46 + private final TradeOrderMapper tradeOrderMapper;
  47 +
  48 + private final TradeOrderItemMapper tradeOrderItemMapper;
  49 +
  50 + private final PriceApi priceApi;
  51 +
  52 + private final ProductSkuApi productSkuApi;
  53 +
  54 + private final ProductSpuApi productSpuApi;
  55 +
  56 + private final PayOrderApi payOrderApi;
  57 +
  58 +
  59 + @Override
  60 + @Transactional(rollbackFor = Exception.class)
  61 + public Long createTradeOrder(Long loginUserId, String clientIp, AppTradeOrderCreateReqVO createReqVO) {
  62 +
  63 + List<Item> items = createReqVO.getItems();
  64 + // 商品SKU检查 sku可售状态,库存
  65 + List<SkuInfoRespDTO> skuInfos = productSkuApi.getSkusByIds(CollectionUtils.convertSet(items, Item::getSkuId));
  66 + Map<Long, SkuInfoRespDTO> skuInfoMap = CollectionUtils.convertMap(skuInfos, SkuInfoRespDTO::getId);
  67 + checkSaleableAndStockFromSpu(skuInfoMap, items);
  68 +
  69 + // 商品SPU检查 sku可售状态,库存
  70 + List<SpuInfoRespDTO> spuInfos = productSpuApi.getSpusByIds(CollectionUtils.convertSet(skuInfos, SkuInfoRespDTO::getSpuId));
  71 + checkSaleableFromSpu(spuInfos);
  72 +
  73 + // 价格计算
  74 + PriceCalculateReqDTO priceCalculateReqDTO = PriceConvert.INSTANCE.convert(createReqVO, loginUserId);
  75 + PriceCalculateRespDTO priceResp = priceApi.calculatePrice(priceCalculateReqDTO);
  76 +
  77 + // 订单信息记录
  78 + TradeOrderDO tradeOrderDO = TradeOrderConvert.INSTANCE.convert(createReqVO, priceResp.getOrder());
  79 + tradeOrderMapper.insert(tradeOrderDO);
  80 +
  81 + // 订单项信息记录
  82 + List<TradeOrderItemDO> tradeOrderItems = TradeOrderItemConvert.INSTANCE.convertList(tradeOrderDO, priceResp.getItems());
  83 + //-填充订单项-SKU信息
  84 + fillItemsInfoFromSku(tradeOrderItems, skuInfoMap);
  85 + tradeOrderItemMapper.insertBatch(tradeOrderItems);
  86 +
  87 + // 库存扣减
  88 + List<SkuDecrementStockBatchReqDTO.Item> skuDecrementStockItems = ProductSkuConvert.INSTANCE.convert(tradeOrderItems);
  89 + productSkuApi.decrementStockBatch(SkuDecrementStockBatchReqDTO.of(skuDecrementStockItems));
  90 + // 生成预支付
  91 +
  92 + PayOrderDataCreateReqDTO payOrderCreateReqDTO = PayOrderConvert.INSTANCE.convert(tradeOrderDO);
  93 + return payOrderApi.createPayOrder(payOrderCreateReqDTO);
  94 + }
  95 +
  96 + private void fillItemsInfoFromSku(List<TradeOrderItemDO> tradeOrderItems,
  97 + Map<Long, SkuInfoRespDTO> spuInfos) {
  98 + for (TradeOrderItemDO tradeOrderItem : tradeOrderItems) {
  99 + // 填充SKU信息
  100 + SkuInfoRespDTO skuInfoRespDTO = spuInfos.get(tradeOrderItem.getSkuId());
  101 + tradeOrderItem.setSpuId(skuInfoRespDTO.getSpuId());
  102 + tradeOrderItem.setPicUrl(skuInfoRespDTO.getPicUrl());
  103 + tradeOrderItem.setName(skuInfoRespDTO.getName());
  104 + // todo
  105 + List<TradeOrderItemDO.Property> property =
  106 + BeanUtil.copyToList(skuInfoRespDTO.getProperties(), TradeOrderItemDO.Property.class);
  107 + tradeOrderItem.setProperties(property);
  108 + }
  109 + }
  110 +
  111 + private void checkSaleableFromSpu(List<SpuInfoRespDTO> spuInfos) {
  112 + SpuInfoRespDTO spu = CollectionUtils.findFirst(spuInfos,
  113 + spuInfoDTO -> !Objects.equals(ProductSpuStatusEnum.ENABLE.getStyle(), spuInfoDTO.getStatus()));
  114 + if (Objects.isNull(spu)) {
  115 + throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SPU_NOT_SALE);
  116 + }
  117 + }
  118 +
  119 + private void checkSaleableAndStockFromSpu(Map<Long, SkuInfoRespDTO> skuInfoMap,
  120 + List<Item> items) {
  121 + // sku 不存在
  122 + if (items.size() != skuInfoMap.size()) {
  123 + throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_FOUND);
  124 + }
  125 + for (Item item : items) {
  126 + SkuInfoRespDTO skuInfoDTO = skuInfoMap.get(item.getSkuId());
  127 + // sku禁用
  128 + if (!Objects.equals(CommonStatusEnum.ENABLE.getStatus(), skuInfoDTO.getStatus())) {
  129 + throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_NOT_SALE);
  130 + }
  131 + // sku库存不足
  132 + if (item.getCount() > skuInfoDTO.getStock()) {
  133 + throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_SKU_STOCK_NOT_ENOUGH);
  134 + }
  135 + }
  136 +
  137 + }
  138 +}
yudao-module-pay/yudao-module-pay-api/pom.xml
@@ -21,6 +21,13 @@ @@ -21,6 +21,13 @@
21 <groupId>cn.iocoder.boot</groupId> 21 <groupId>cn.iocoder.boot</groupId>
22 <artifactId>yudao-common</artifactId> 22 <artifactId>yudao-common</artifactId>
23 </dependency> 23 </dependency>
  24 +
  25 + <!-- 参数校验 -->
  26 + <dependency>
  27 + <groupId>org.springframework.boot</groupId>
  28 + <artifactId>spring-boot-starter-validation</artifactId>
  29 + <optional>true</optional>
  30 + </dependency>
24 </dependencies> 31 </dependencies>
25 32
26 </project> 33 </project>
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.api.order;
  2 +
  3 +import javax.validation.Valid;
  4 +
  5 +/**
  6 + * @author LeeYan9
  7 + * @since 2022-08-26
  8 + */
  9 +public interface PayOrderApi {
  10 +
  11 +
  12 + /**
  13 + * 创建支付单
  14 + *
  15 + * @param reqDTO 创建请求
  16 + * @return 支付单编号
  17 + */
  18 + Long createPayOrder(@Valid PayOrderDataCreateReqDTO reqDTO);
  19 +
  20 +}
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderDataCreateReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.api.order;
  2 +
  3 +import lombok.Data;
  4 +import org.hibernate.validator.constraints.Length;
  5 +
  6 +import javax.validation.constraints.DecimalMin;
  7 +import javax.validation.constraints.NotEmpty;
  8 +import javax.validation.constraints.NotNull;
  9 +import java.io.Serializable;
  10 +import java.util.Date;
  11 +
  12 +/**
  13 + * 支付单创建 Request DTO
  14 + * @author LeeYan9
  15 + */
  16 +@Data
  17 +public class PayOrderDataCreateReqDTO implements Serializable {
  18 +
  19 + /**
  20 + * 应用编号
  21 + */
  22 + @NotNull(message = "应用编号不能为空")
  23 + private Long appId;
  24 + /**
  25 + * 用户 IP
  26 + */
  27 + @NotEmpty(message = "用户 IP 不能为空")
  28 + private String userIp;
  29 +
  30 + // ========== 商户相关字段 ==========
  31 +
  32 + /**
  33 + * 商户订单编号
  34 + */
  35 + @NotEmpty(message = "商户订单编号不能为空")
  36 + private String merchantOrderId;
  37 + /**
  38 + * 商品标题
  39 + */
  40 + @NotEmpty(message = "商品标题不能为空")
  41 + @Length(max = 32, message = "商品标题不能超过 32")
  42 + private String subject;
  43 + /**
  44 + * 商品描述
  45 + */
  46 + @NotEmpty(message = "商品描述信息不能为空")
  47 + @Length(max = 128, message = "商品描述信息长度不能超过128")
  48 + private String body;
  49 +
  50 + // ========== 订单相关字段 ==========
  51 +
  52 + /**
  53 + * 支付金额,单位:分
  54 + */
  55 + @NotNull(message = "支付金额不能为空")
  56 + @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
  57 + private Integer amount;
  58 +
  59 + /**
  60 + * 支付过期时间
  61 + */
  62 + @NotNull(message = "支付过期时间不能为空")
  63 + private Date expireTime;
  64 +
  65 +}
0 \ No newline at end of file 66 \ No newline at end of file