SaaS平台开发实战(六):模拟客户下订单

本文围绕商城系统模拟客户下单展开。先修复上一章订单模块的 BUG,接着定义枚举和参数,借助 SPI 特性实现下单策略。之后阐述订单策略,包括接口定义与实现、模拟商品类等。还介绍了拦截器和配置,最后进行下单测试,通过分区分库将数据锁定到指定数据库。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

引读

定义枚举和参数

OrderType 枚举

ActivityType 活动类型枚举

OrderSubmitMode 下单模式枚举

OrderPlaceParameter 下单参数

OrderPlaceResultDTO 下单结果DTO对象

修改OrderDTO

订单策略

下单SPI接口定义与实现

模拟商品相关类

新增META-INF.services

对接下单接口

拦截器和配置

添加Dubbo接口

实现拦截器

WebMvcConfig配置

DubboReferenceConfiguration配置

修复历史问题

测试下单


引读

在上一章我们创建了订单服务模块,生成了订单服务模块的代码,本章将模拟客户下订单。

本系列源代码存放在github和gitee中,可直接clone
https://gitee.com/kutasms/saas-demo 或  https://github.com/kutasms/saas-demo

在开始之前有几个上一章的BUG需要修复一下:

  1. 订单模块增加文件 V1.0.2__alter_order_item_attr.ddl.sql
ALTER TABLE `saas_order_item_attr`
ADD COLUMN `attr_id` BIGINT NOT NULL COMMENT '订单项属性编号' FIRST,
CHANGE COLUMN `tenant_id` `tenant_id` BIGINT NOT NULL COMMENT '租户编号' ,
DROP PRIMARY KEY,
ADD PRIMARY KEY (`attr_id`);

然后重新生成下代码

定义枚举和参数

在商城系统中下单模式可能会有多种,包括普通订单、拼团订单、助力购订单等,所以在这里我们将借助SPI特性来实现下单策略。

OrderType 枚举

package org.example.saas.core.domain.enums;

import com.chia.multienty.core.domain.enums.HttpResultEnum;
import com.chia.multienty.core.exception.KutaRuntimeException;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum OrderType {

    NORMAL_ORDER(1, "普通订单"),
    GROUP_BUYING_LAUNCH(2, "发起拼团"),
    GROUP_BUYING_JOIN(3, "参与拼团");


    @JsonValue
    private Integer value;
    private String describe;

    @JsonCreator
    public static OrderType valueOf(Integer value) {
        if(value == null) {
            return null;
        }
        for (OrderType type : values()) {
            if(type.getValue().equals(value)) {
                return type;
            }
        }
        throw new KutaRuntimeException(HttpResultEnum.ENUM_ITEM_NOT_FOUND);
    }
}

ActivityType 活动类型枚举

package org.example.saas.core.domain.enums;

import com.chia.multienty.core.domain.enums.HttpResultEnum;
import com.chia.multienty.core.exception.KutaRuntimeException;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ActivityType {
    DISCOUNT(1, "限时折扣", false),
    GROUP_BUYING(2, "团购", true),
    ;
    @JsonValue
    private Integer id;
    private String describe;

    private Boolean standaloneStock;

    @JsonCreator
    public static ActivityType valueOf(Integer id) {
        if(id == null) {
            return null;
        }
        for (ActivityType type : values()) {
            if(type.getId().equals(id)) {
                return type;
            }
        }
        throw new KutaRuntimeException(HttpResultEnum.ENUM_ITEM_NOT_FOUND);
    }
}

OrderSubmitMode 下单模式枚举

package org.example.saas.core.domain.enums;

import com.chia.multienty.core.domain.enums.HttpResultEnum;
import com.chia.multienty.core.exception.KutaRuntimeException;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum OrderSubmitMode {

    SHOPPING_CART(1, "购物车"),
    IMMEDIATE(2, "直接购买");

    @JsonValue
    private Integer value;
    private String describe;

    @JsonCreator
    public static OrderSubmitMode valueOf(Integer value) {
        if(value == null) {
            return null;
        }
        for (OrderSubmitMode mode : values()) {
            if(mode.getValue().equals(value)) {
                return mode;
            }
        }
        throw new KutaRuntimeException(HttpResultEnum.ENUM_ITEM_NOT_FOUND);
    }
}

OrderPlaceParameter 下单参数

package org.example.saas.core.parameter.order;


import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.example.saas.core.domain.enums.ActivityType;
import org.example.saas.core.domain.enums.OrderSubmitMode;
import org.example.saas.core.domain.enums.OrderType;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

@Data
public class OrderPlaceParameter implements Serializable {

    /**
     * 订单提交模式
     */
    @ApiModelProperty("订单提交模式")
    private OrderSubmitMode submitMode;

    /**
     * 订单类型
     */
    @ApiModelProperty("订单类型")
    private OrderType orderType;

    /**
     * 购物车编号列表
     */
    @ApiModelProperty("购物车编号列表")
    private List<Long> cartIds;

    /**
     * 商品规格编号列表
     */
    @ApiModelProperty("商品规格编号列表")
    private Map<Long, Integer> skuMap;


    /**
     * 使用优惠券
     */
    @ApiModelProperty("使用优惠券")
    private Long couponId;

    /**
     * 活动编号
     */
    @ApiModelProperty("活动编号")
    private Long activityId;
    /**
     * 活动类型
     */
    @ApiModelProperty("活动类型")
    private ActivityType activityType;
}

OrderPlaceResultDTO 下单结果DTO对象

package org.example.saas.core.domain.dto;


import lombok.Data;
import lombok.experimental.Accessors;
import org.example.saas.core.pojo.Trade;

import java.util.List;

@Data
@Accessors(chain = true)
public class OrderPlaceResultDTO {
    private String payUrl;
    private Trade trade;
    private List<OrderDTO> orders;
}

创建CustomerContext用于存储线程安全的客户信息存储单元

package org.example.saas.core.tools;


import org.example.saas.core.domain.dto.CustomerDTO;

public class CustomerContext {
    private static ThreadLocal<CustomerDTO> cachedCustomer = new ThreadLocal<>();

    public static void setCustomer(CustomerDTO customer) {
        cachedCustomer.set(customer);
    }

    public static CustomerDTO getCustomer() {
        return cachedCustomer.get();
    }
}

修改OrderDTO

package org.example.saas.core.domain.dto;

import org.example.saas.core.pojo.Order;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.example.saas.core.pojo.OrderDetail;
import org.example.saas.core.pojo.OrderItem;

import java.util.List;

/**
 * <p>
 * 订单 DTO对象
 * </p>
 *
 * @author Multi Tenant Auto Generator
 * @since 2024-02-25
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value="OrderDTO", description="订单DTO对象")
public class OrderDTO extends Order {
    private OrderDetail detail;

    private List<OrderItem> items;
    /**
     * 是否包含订单详情
     */
    @ApiModelProperty("是否包含订单详情")
    private Boolean containsDetail;
    /**
     * 是否包含订单项
     */
    @ApiModelProperty("是否包含订单项")
    private Boolean containsItems;
}

订单策略

下单SPI接口定义与实现

接下来我们需要定义一个下单的接口,后面将通过SPI的方式加载

package org.example.saas.core.service.order.strategy;

import org.example.saas.core.domain.dto.OrderPlaceResultDTO;
import org.example.saas.core.domain.enums.OrderType;
import org.example.saas.core.parameter.order.OrderPlaceParameter;

public interface IOrderPlaceProvider {
    OrderType getType();

    OrderPlaceResultDTO placeOrder(OrderPlaceParameter parameter);
}

添加一个抽象提供者对象AbstractOrderPlaceProvider,实现基础方法

package org.example.saas.core.service.order.strategy;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;

import com.chia.multienty.core.domain.constants.MultiTenantConstants;
import com.chia.multienty.core.domain.enums.StatusEnum;
import com.chia.multienty.core.tools.IdWorkerProvider;
import com.chia.multienty.core.tools.MultiTenantContext;
import io.seata.spring.annotation.GlobalTransactional;
import org.example.saas.core.domain.dto.OrderDTO;
import org.example.saas.core.domain.dto.OrderPlaceResultDTO;
import org.example.saas.core.parameter.order.OrderPlaceParameter;
import org.example.saas.core.pojo.Trade;
import org.example.saas.core.service.order.OrderService;
import org.example.saas.core.service.order.TradeService;
import org.example.saas.core.tools.CustomerContext;
import org.springframework.beans.factory.annotation.Autowired;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

public abstract class AbstractOrderPlaceProvider implements IOrderPlaceProvider {
    @Autowired
    protected OrderService orderService;

    @Autowired
    protected TradeService tradeService;


    @Override
    @GlobalTransactional
    public OrderPlaceResultDTO placeOrder(OrderPlaceParameter parameter) {
        DynamicDataSourceContextHolder.push(MultiTenantConstants.DS_SHARDING);
        Trade trade = createTrade(parameter);
        List<OrderDTO> orders = createOrders(parameter, trade);
        OrderPlaceResultDTO result = new OrderPlaceResultDTO()
                .setOrders(orders)
                .setTrade(trade);

        return result;
    }

    /**
     * 创建交易
     * @param parameter
     * @return
     */
    @DS(MultiTenantConstants.DS_SHARDING)
    protected Trade createTrade(OrderPlaceParameter parameter) {
        Trade trade = new Trade();
        trade.setTradeId(IdWorkerProvider.next());
        trade.setPaid(false);
        trade.setStatus(StatusEnum.NORMAL.getCode());
        trade.setCustomerId(CustomerContext.getCustomer().getCustomerId());
        trade.setTenantId(MultiTenantContext.getTenant().getTenantId());
        trade.setCreateTime(LocalDateTime.now());
        tradeService.saveTE(trade);
        return trade;
    }

    protected abstract List<OrderDTO> createOrders(OrderPlaceParameter parameter, Trade trade);
}

模拟商品相关类

因为我们并未创建商品相关模块,所以我们模拟创建商品类和Sku类

package org.example.saas.core.pojo;

import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * <p>
 * 商品
 * </p>
 *
 * @author Multi Tenant Auto Generator
 * @since 2024-02-17
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("saas_product")
@ApiModel(value="Product对象", description="商品")
public class Product implements Serializable {


    /**
     * 商品编号
     */
    @ApiModelProperty(value = "商品编号")
    @TableId(value = "product_id", type = IdType.AUTO)
    private Long productId;

    /**
     * 租户编号
     */
    @ApiModelProperty(value = "租户编号")
    @TableField("`tenant_id`")
    private Long tenantId;

    /**
     * 商品名称
     */
    @ApiModelProperty(value = "商品名称")
    @TableField("`name`")
    private String name;

    /**
     * 副标题
     */
    @ApiModelProperty(value = "副标题")
    @TableField("`subtitle`")
    private String subtitle;

    /**
     * 简述
     */
    @ApiModelProperty(value = "简述")
    @TableField("`sketch`")
    private String sketch;

    /**
     * 目录
     */
    @ApiModelProperty(value = "目录")
    @TableField("`category_id`")
    private Long categoryId;

    /**
     * 详情
     */
    @ApiModelProperty(value = "详情")
    @TableField("`detail`")
    private String detail;

    /**
     * 状态
     */
    @ApiModelProperty(value = "状态")
    @TableField(value = "`status`", fill = FieldFill.INSERT)
    private String status;

    /**
     * 价格区间-开始
     */
    @ApiModelProperty(value = "价格区间-开始")
    @TableField("`price_start`")
    private BigDecimal priceStart;

    /**
     * 价格区间-结束
     */
    @ApiModelProperty(value = "价格区间-结束")
    @TableField("`price_end`")
    private BigDecimal priceEnd;

    /**
     * 是否已上架
     */
    @ApiModelProperty(value = "是否已上架")
    @TableField("`listed`")
    private Boolean listed;

    /**
     * 是否已删除
     */
    @ApiModelProperty(value = "是否已删除")
    @TableField(value = "`deleted`", fill = FieldFill.INSERT)
    @TableLogic
    private Boolean deleted;

    /**
     * 乐观锁版本号
     */
    @ApiModelProperty(value = "乐观锁版本号")
    @TableField(value = "`version`", fill = FieldFill.INSERT)
    private Long version;

    /**
     * 创建日期
     */
    @ApiModelProperty(value = "创建日期")
    @TableField(value = "`create_time`", fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     * 更新日期
     */
    @ApiModelProperty(value = "更新日期")
    @TableField(value = "`update_time`", fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;

    /**
     * 卖点
     */
    @ApiModelProperty(value = "卖点")
    @TableField("`brief`")
    private String brief;

    /**
     * 允许使用优惠券
     */
    @ApiModelProperty(value = "允许使用优惠券")
    @TableField("`allow_coupon`")
    private Boolean allowCoupon;


}

ProductSku类

package org.example.saas.core.pojo;

import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * <p>
 * 商品规格
 * </p>
 *
 * @author Multi Tenant Auto Generator
 * @since 2024-02-17
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("saas_product_sku")
@ApiModel(value="ProductSku对象", description="商品规格")
public class ProductSku implements Serializable {


    /**
     * sku编号
     */
    @ApiModelProperty(value = "sku编号")
    @TableId(value = "sku_id", type = IdType.AUTO)
    private Long skuId;

    /**
     * 货物编号
     */
    @ApiModelProperty(value = "货物编号")
    @TableField("`product_id`")
    private Long productId;

    /**
     * 租户编号
     */
    @ApiModelProperty(value = "租户编号")
    @TableField("`tenant_id`")
    private Long tenantId;

    /**
     * 现价
     */
    @ApiModelProperty(value = "现价")
    @TableField("`price`")
    private BigDecimal price;

    /**
     * 原价
     */
    @ApiModelProperty(value = "原价")
    @TableField("`original_Price`")
    private BigDecimal originalPrice;

    /**
     * 库存
     */
    @ApiModelProperty(value = "库存")
    @TableField("`stock`")
    private Integer stock;

    /**
     * 图片地址
     */
    @ApiModelProperty(value = "图片地址")
    @TableField("`image_url`")
    private String imageUrl;

    /**
     * 文件编号
     */
    @ApiModelProperty(value = "文件编号")
    @TableField("`image_file_id`")
    private Long imageFileId;

    /**
     * 模式
     */
    @ApiModelProperty(value = "模式")
    @TableField("`mode`")
    private Byte mode;

    /**
     * 是否可用
     */
    @ApiModelProperty(value = "是否可用")
    @TableField("`enable`")
    private Boolean enable;

    /**
     * 乐观锁版本号
     */
    @ApiModelProperty(value = "乐观锁版本号")
    @TableField(value = "`version`", fill = FieldFill.INSERT)
    private Long version;

    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间")
    @TableField(value = "`create_time`", fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    @ApiModelProperty(value = "更新时间")
    @TableField(value = "`update_time`", fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;

    /**
     * 是否已删除
     */
    @ApiModelProperty(value = "是否已删除")
    @TableField(value = "`deleted`", fill = FieldFill.INSERT)
    @TableLogic
    private Boolean deleted;


}

SkuAttribute类

package org.example.saas.core.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * <p>
 * 商品规格属性
 * </p>
 *
 * @author Multi Tenant Auto Generator
 * @since 2024-02-17
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("saas_sku_attribute")
@ApiModel(value="SkuAttribute对象", description="商品规格属性")
public class SkuAttribute implements Serializable {


    /**
     * 规格属性编号
     */
    @ApiModelProperty(value = "规格属性编号")
    @TableId(value = "attr_id", type = IdType.AUTO)
    private Long attrId;

    /**
     * 规格编号
     */
    @ApiModelProperty(value = "规格编号")
    @TableField("`sku_id`")
    private Long skuId;

    /**
     * 租户编号
     */
    @ApiModelProperty(value = "租户编号")
    @TableField("`tenant_id`")
    private Long tenantId;

    /**
     * 规格名称
     */
    @ApiModelProperty(value = "规格名称")
    @TableField("`name`")
    private String name;

    /**
     * 规格值
     */
    @ApiModelProperty(value = "规格值")
    @TableField("`value`")
    private String value;

    /**
     * 规格代码名称
     */
    @ApiModelProperty(value = "规格代码名称")
    @TableField("`code_name`")
    private String codeName;

    /**
     * 规格代码值
     */
    @ApiModelProperty(value = "规格代码值")
    @TableField("`code_value`")
    private String codeValue;

    /**
     * 规格图片
     */
    @ApiModelProperty(value = "规格图片")
    @TableField("`image`")
    private String image;

    /**
     * 排序索引
     */
    @ApiModelProperty(value = "排序索引")
    @TableField("`index`")
    private Byte index;


}

ProductSkuDTO类

package org.example.saas.core.domain.dto;


import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.example.saas.core.pojo.ProductSku;
import org.example.saas.core.pojo.SkuAttribute;

import java.util.List;

/**
 * <p>
 * 商品规格 DTO对象
 * </p>
 *
 * @author Multi Tenant Auto Generator
 * @since 2024-01-12
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value="ProductSkuDTO", description="商品规格DTO对象")
public class ProductSkuDTO extends ProductSku {
    /**
     * SKU特性
     */
    private List<SkuAttribute> attributes;


}

ProductDTO类

package org.example.saas.core.domain.dto;

import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.example.saas.core.pojo.Product;

import java.util.List;

/**
 * <p>
 * 商品 DTO对象
 * </p>
 *
 * @author Multi Tenant Auto Generator
 * @since 2024-01-12
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value="ProductDTO", description="商品DTO对象")
public class ProductDTO extends Product {
    private List<ProductSkuDTO> skus;
    private String mainImageUrl;
}

接下来我们创建NormalOrderPlaceProvider,正常下单模式

package org.example.saas.core.service.order.strategy;

import com.chia.multienty.core.domain.basic.KeyValuePair;
import com.chia.multienty.core.domain.enums.StatusEnum;
import com.chia.multienty.core.tools.IdWorkerProvider;
import com.chia.multienty.core.tools.MultiTenantContext;
import org.example.saas.core.domain.dto.OrderDTO;
import org.example.saas.core.domain.dto.ProductDTO;
import org.example.saas.core.domain.dto.ProductSkuDTO;
import org.example.saas.core.domain.enums.OrderType;
import org.example.saas.core.parameter.order.OrderPlaceParameter;
import org.example.saas.core.parameter.order.TradeUpdateParameter;
import org.example.saas.core.pojo.*;
import org.example.saas.core.service.order.OrderDetailService;
import org.example.saas.core.service.order.OrderItemAttrService;
import org.example.saas.core.service.order.OrderItemService;
import org.example.saas.core.tools.CustomerContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class NormalOrderPlaceProvider extends AbstractOrderPlaceProvider{
    @Autowired
    private OrderDetailService orderDetailService;
    @Autowired
    private OrderItemService orderItemService;
    @Autowired
    private OrderItemAttrService orderItemAttrService;
    @Override
    protected List<OrderDTO> createOrders(OrderPlaceParameter parameter, Trade trade) {
        List<OrderDTO> orders = new ArrayList<>();

        BigDecimal total = BigDecimal.ZERO;


        BigDecimal discountAmount = BigDecimal.ZERO;
        if(parameter.getCouponId() != null) {
            //模拟2.5元优惠券
            discountAmount = BigDecimal.valueOf(2.5);
        }
        OrderDTO order = new OrderDTO();
        order.setOrderId(IdWorkerProvider.next());
        order.setOrderType(parameter.getOrderType().getValue().shortValue());
        order.setStatus(StatusEnum.WORKING.getCode());
        order.setPaid(false);
        order.setTenantId(trade.getTenantId());
        order.setRefunded(false);
        order.setCreateTime(trade.getCreateTime());
        order.setCustomerId(CustomerContext.getCustomer().getCustomerId());
        order.setDiscountAmount(discountAmount);
        order.setPaidAmount(total.subtract(discountAmount));
        order.setTotalAmount(total);

        orders.add(order);
        orderService.batchSaveDTOTE(orders);
        // 保存订单详情
        OrderDetail orderDetail = new OrderDetail()
                .setOrderId(order.getOrderId())
                .setTradeId(trade.getTradeId())
                .setTenantId(trade.getTenantId())
                .setCouponId(parameter.getCouponId())
                .setActivityId(parameter.getActivityId())
                .setActivityType(parameter.getActivityType().getId().shortValue())
                .setCustomerNick(CustomerContext.getCustomer().getNickName())
                .setCustomerPhone(CustomerContext.getCustomer().getPhoneNumber())
                .setCreateTime(trade.getCreateTime())
                .setReceived(false);
        orderDetailService.saveTE(orderDetail);
        order.setDetail(orderDetail);
        List<ProductDTO> products = mockProducts();
        // 保存订单子项
        saveOrderItems(parameter, trade, order, products);
        // 修改交易金额
        trade.setDiscountAmount(orders.stream().map(m->m.getDiscountAmount()).reduce((a,b) -> a.add(b)).get());
        trade.setTotalAmount(orders.stream().map(m->m.getTotalAmount()).reduce((a,b)-> a.add(b)).get());
        trade.setPaidAmount(trade.getTotalAmount().subtract(trade.getDiscountAmount()));
        TradeUpdateParameter tradeUpdateParameter = new TradeUpdateParameter();
        BeanUtils.copyProperties(trade, tradeUpdateParameter);
        tradeService.update(tradeUpdateParameter);
        return orders;
    }

    public void saveOrderItems(OrderPlaceParameter parameter, Trade trade, OrderDTO order, List<ProductDTO> products) {
        List<OrderItem> orderItems = new ArrayList<>();
        List<OrderItemAttr> orderItemAttrs = new ArrayList<>();
        for (ProductDTO product : products) {
            for (ProductSkuDTO sku : product.getSkus()) {
                OrderItem orderItem = new OrderItem()
                        .setItemId(IdWorkerProvider.next())
                        .setOrderId(order.getOrderId())
                        .setImage(sku.getImageUrl() == null ? product.getMainImageUrl() : sku.getImageUrl())
                        .setProductId(product.getProductId())
                        .setPrice(sku.getPrice())
                        .setDiffAmount(BigDecimal.ZERO)
                        .setCreateTime(trade.getCreateTime())
                        .setQuantity(parameter.getSkuMap().get(sku.getSkuId()))
                        .setProductName(product.getName())
                        .setSkuName(sku.getAttributes().stream().map(m->m.getValue()).collect(Collectors.joining(",")))
                        .setSkuId(sku.getSkuId())
                        .setTenantId(trade.getTenantId())
                        ;
                orderItem.setTotalAmount(BigDecimal.valueOf(orderItem.getQuantity()).multiply(sku.getPrice()));
                orderItems.add(orderItem);
                for (SkuAttribute attribute : sku.getAttributes()) {
                    OrderItemAttr attr = new OrderItemAttr();
                    attr.setItemId(orderItem.getItemId());
                    attr.setName(attribute.getName());
                    attr.setValue(attribute.getValue());
                    attr.setTenantId(trade.getTenantId());
                    attr.setCodeValue(attribute.getCodeValue());
                    attr.setCodeName(attribute.getCodeName());
                    attr.setCreateTime(trade.getCreateTime());
                    orderItemAttrs.add(attr);
                }
            }
        }

        orderItemService.saveBatchTE(orderItems);
        orderItemAttrService.saveBatchTE(orderItemAttrs);
    }


    private List<ProductDTO> mockProducts() {
        List<ProductDTO> products = new ArrayList<>();
        ProductDTO product1 = new ProductDTO();
        product1.setBrief("商品1");
        product1.setName("商品1");
        product1.setDeleted(false);
        product1.setAllowCoupon(true);
        product1.setDetail("商品1的详情");
        product1.setListed(true);
        product1.setProductId(123L);
        product1.setCategoryId(111L);
        product1.setSkus(mockSkus(product1.getProductId()));
        return products;
    }

    private List<ProductSkuDTO> mockSkus(Long productId) {
        List<ProductSkuDTO> skus = new ArrayList<>();
        if(productId.equals(123L)) {
            ProductSkuDTO sku = new ProductSkuDTO();
            sku.setSkuId(1L);
            sku.setAttributes(mockSkuAttr(sku.getSkuId()));
            sku.setDeleted(false);
            sku.setPrice(BigDecimal.valueOf(12.5));
            sku.setEnable(true);
            sku.setMode((byte) 1);
            sku.setCreateTime(LocalDateTime.now());
            sku.setImageFileId(333L);
            sku.setOriginalPrice(BigDecimal.valueOf(16.5));
            sku.setProductId(productId);
            sku.setStock(500);
            sku.setTenantId(MultiTenantContext.getTenant().getTenantId());
            sku.setImageUrl("https://image.baidu.com?t=281643");
            sku.setVersion(1L);
            skus.add(sku);
        } else {
            ProductSkuDTO sku = new ProductSkuDTO();
            sku.setSkuId(2L);
            sku.setAttributes(mockSkuAttr(sku.getSkuId()));
            sku.setDeleted(false);
            sku.setPrice(BigDecimal.valueOf(19.8));
            sku.setEnable(true);
            sku.setMode((byte) 1);
            sku.setCreateTime(LocalDateTime.now());
            sku.setImageFileId(222L);
            sku.setOriginalPrice(BigDecimal.valueOf(26.5));
            sku.setProductId(productId);
            sku.setStock(300);
            sku.setTenantId(MultiTenantContext.getTenant().getTenantId());
            sku.setImageUrl("https://image.baidu.com?t=153917B2382");
            sku.setVersion(2L);
            skus.add(sku);
        }
        return skus;
    }

    private List<SkuAttribute> mockSkuAttr(Long skuId) {
        List<SkuAttribute> attributes = new ArrayList<>();
        if(skuId.equals(1L)) {
            SkuAttribute attribute = new SkuAttribute();
            attribute.setName("颜色");
            attribute.setIndex((byte) 1);
            attribute.setImage("https://image.baidu.com?t=1531233082");
            attribute.setAttrId(39178702L);
            attribute.setValue("红色");
            attribute.setCodeName("attr_1");
            attribute.setCodeValue("attr_v_0");
            attribute.setSkuId(skuId);
            attribute.setTenantId(MultiTenantContext.getTenant().getTenantId());
            attributes.add(attribute);
            SkuAttribute attribute2 = new SkuAttribute();
            attribute2.setName("尺码");
            attribute2.setIndex((byte) 2);
            attribute2.setImage("https://image.baidu.com?t=2321354534");
            attribute2.setAttrId(53243112L);
            attribute2.setValue("XL");
            attribute2.setCodeName("attr_2");
            attribute2.setCodeValue("attr_v_1");
            attribute2.setSkuId(skuId);
            attribute2.setTenantId(MultiTenantContext.getTenant().getTenantId());
            attributes.add(attribute2);
        } else {
            SkuAttribute attribute = new SkuAttribute();
            attribute.setName("颜色");
            attribute.setIndex((byte) 1);
            attribute.setImage("https://image.baidu.com?t=1533212482");
            attribute.setAttrId(339288702L);
            attribute.setValue("蓝色");
            attribute.setCodeName("attr_1");
            attribute.setCodeValue("attr_v_0");
            attribute.setSkuId(skuId);
            attribute.setTenantId(MultiTenantContext.getTenant().getTenantId());
            attributes.add(attribute);
            SkuAttribute attribute2 = new SkuAttribute();
            attribute2.setName("尺码");
            attribute2.setIndex((byte) 2);
            attribute2.setImage("https://image.baidu.com?t=2329874534");
            attribute2.setAttrId(5374652912L);
            attribute2.setValue("XXL");
            attribute2.setCodeName("attr_2");
            attribute2.setCodeValue("attr_v_1");
            attribute2.setSkuId(skuId);
            attribute2.setTenantId(MultiTenantContext.getTenant().getTenantId());
            attributes.add(attribute2);
        }
        return attributes;
    }

    @Override
    public OrderType getType() {
        return OrderType.NORMAL_ORDER;
    }
}

新增META-INF.services

在resources资源目录下创建META-INF/services

在后期如果要加入其它的实现方式,我们只需要在这里添加即可。

然后添加一个全局定位类

package org.example.saas.core.domain.constants;

public class SaasConstants {
    public static final String ORDER_PLACE_PROVIDER_PREFIX = "ORDER_PLACE";

    public static final String MOCK_CUSTOMER_ID = "X-MOCK-ID";
    /**
     * http头 客户编号
     */
    public static final String HEADER_CUSTOMER_ID = "X-CUSTOMER-ID";
}

然后增加下单提供者注册器

package org.example.saas.core.config;


import com.chia.multienty.core.tools.MultiTenantServiceLoader;
import org.example.saas.core.domain.constants.SaasConstants;
import org.example.saas.core.service.order.strategy.IOrderPlaceProvider;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;

import java.util.Collection;

public class OrderPlaceProviderRegistry implements BeanDefinitionRegistryPostProcessor {


    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        MultiTenantServiceLoader.register(IOrderPlaceProvider.class);

        Collection<IOrderPlaceProvider> providers = MultiTenantServiceLoader.getInstances(IOrderPlaceProvider.class);
        for (IOrderPlaceProvider provider : providers) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(provider.getClass());
            registry.registerBeanDefinition(SaasConstants.ORDER_PLACE_PROVIDER_PREFIX + provider.getType().name(), builder.getBeanDefinition());
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

对接下单接口

package org.example.saas.core.service.order.strategy;


import lombok.RequiredArgsConstructor;
import org.example.saas.core.domain.constants.SaasConstants;
import org.example.saas.core.domain.dto.OrderPlaceResultDTO;
import org.example.saas.core.parameter.order.OrderPlaceParameter;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 下单策略
 */
@Component
@RequiredArgsConstructor
public class OrderPlaceStrategy {
    private final ObjectProvider<Map<String, IOrderPlaceProvider>> provider;
    public OrderPlaceResultDTO placeOrder(OrderPlaceParameter parameter) {
        Map<String, IOrderPlaceProvider> map = provider.getIfAvailable();
        if(map != null) {
            IOrderPlaceProvider service = map.get(SaasConstants.ORDER_PLACE_PROVIDER_PREFIX + parameter.getOrderType().name());
            return service.placeOrder(parameter);
        }
        return null;
    }
}

新增SaasDemoConfiguration

package org.example.saas.core.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SaasDemoConfiguration {
    @Bean
    public static OrderPlaceProviderRegistry orderPlaceProviderRegistry() {
        return new OrderPlaceProviderRegistry();
    }
}

修改saasdemo-order模块的TradeController,增加下单接口

package org.example.saas.order.controller;


import org.example.saas.core.domain.dto.OrderPlaceResultDTO;
import org.example.saas.core.parameter.order.*;
import org.example.saas.core.service.order.strategy.OrderPlaceStrategy;
import org.springframework.web.bind.annotation.RequestMapping;
import org.example.saas.core.service.order.TradeService;
import org.example.saas.core.domain.dto.TradeDTO;
import org.example.saas.core.pojo.Trade;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import com.chia.multienty.core.annotation.WebLog;
import com.chia.multienty.core.domain.basic.Result;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
/**
 * <p>
 * 交易 服务
 * </p>
 *
 * @author Multi Tenant Auto Generator
 * @since 2024-02-25
 */
@RestController
@Validated
@RequestMapping("/trade")
@RequiredArgsConstructor
@Api(tags = "交易")
public class TradeController {
    private final TradeService tradeService;

    private final OrderPlaceStrategy placeStrategy;

    @PostMapping("/detail")
    @ApiOperation("获取交易详情")
    public Result<TradeDTO> getDetail(@Validated @RequestBody TradeDetailGetParameter parameter) {
        TradeDTO detail = tradeService.getDetail(parameter);
        return new Result<>(detail);
    }

    @PostMapping("/page")
    @ApiOperation("获取交易分页列表")
    public Result<IPage<TradeDTO>> getPage(@Validated @RequestBody TradePageGetParameter parameter) {
        IPage<TradeDTO> page = tradeService.getPage(parameter);
        return new Result<>(page);
    }

    @PostMapping("/update")
    @ApiOperation("更新交易")
    @WebLog
    public Result<Boolean> update(@Validated @RequestBody TradeUpdateParameter parameter) {
        tradeService.update(parameter);
        return new Result<>(true);
    }

    @PostMapping("/save")
    @ApiOperation("保存交易")
    @WebLog
    public Result<Boolean> save(@Validated @RequestBody TradeSaveParameter parameter) {
        tradeService.save(parameter);
        return new Result<>(true);
    }

    @PostMapping("/enable")
    @ApiOperation("启用交易")
    @WebLog
    public Result<Boolean> enable(@Validated @RequestBody TradeEnableParameter parameter) {
        tradeService.enable(parameter);
        return new Result<>(true);
    }

    @PostMapping("/disable")
    @ApiOperation("禁用交易")
    @WebLog
    public Result<Boolean> save(@Validated @RequestBody TradeDisableParameter parameter) {
        tradeService.disable(parameter);
        return new Result<>(true);
    }

    @DeleteMapping("/delete")
    @ApiOperation("删除交易")
    @WebLog
    public Result<Boolean> delete(@Validated @RequestBody TradeDeleteParameter parameter) {
        tradeService.delete(parameter);
        return new Result<>(true);
    }

    @PostMapping("/place-order")
    @ApiOperation("下单")
    @WebLog
    public Result<OrderPlaceResultDTO> placeOrder(@Validated @RequestBody OrderPlaceParameter parameter) {
        OrderPlaceResultDTO dto = placeStrategy.placeOrder(parameter);
        return new Result<>(dto);
    }
}

拦截器和配置

添加Dubbo接口

DubboUserService新增getCustomer接口

package org.example.saas.core.dubbo.service;


import org.example.saas.core.domain.dto.CustomerDTO;
import org.example.saas.core.parameter.user.CustomerDetailGetParameter;

public interface DubboUserService {

    /**
     * 获取客户信息
     * @param parameter
     * @return
     */
    CustomerDTO getCustomer(CustomerDetailGetParameter parameter);
}

用户模块新增DubboUserServiceImpl

package org.example.saas.user.dubbo.service.impl;


import lombok.RequiredArgsConstructor;
import org.apache.dubbo.config.annotation.DubboService;
import org.example.saas.core.domain.dto.CustomerDTO;
import org.example.saas.core.dubbo.service.DubboUserService;
import org.example.saas.core.parameter.user.CustomerDetailGetParameter;
import org.example.saas.core.service.user.CustomerService;

@DubboService
@RequiredArgsConstructor
public class DubboUserServiceImpl implements DubboUserService {

    private final CustomerService customerService;

    @Override
    public CustomerDTO getCustomer(CustomerDetailGetParameter parameter) {
        return customerService.getDetail(parameter);
    }
}

实现拦截器

saasdemo-core新增拦截器

package org.example.saas.core.interceptor;

import com.chia.multienty.core.domain.basic.Result;
import com.chia.multienty.core.domain.constants.MultiTenantHeaderConstants;
import com.chia.multienty.core.properties.yaml.YamlMultiTenantProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.example.saas.core.domain.constants.SaasConstants;
import org.example.saas.core.domain.dto.CustomerDTO;
import org.example.saas.core.dubbo.service.DubboUserService;
import org.example.saas.core.parameter.user.CustomerDetailGetParameter;
import org.example.saas.core.tools.CustomerContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class SaasInterceptor implements HandlerInterceptor {

    @Autowired
    private DubboUserService dubboUserService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        response.setHeader("Content-Type", "json/application; charset=utf-8");
        Result result = null;
        String strTenantId = request.getHeader(MultiTenantHeaderConstants.TENANT_ID_KEY);
        Long tenantId = Long.parseLong(strTenantId);
        String mockCustomerId = request.getHeader(SaasConstants.MOCK_CUSTOMER_ID);

        if(Strings.isNotEmpty(mockCustomerId)) {
            CustomerDTO customer = dubboUserService.getCustomer(new CustomerDetailGetParameter()
                    .setTenantId(tenantId).setCustomerId(Long.parseLong(mockCustomerId)));
            CustomerContext.setCustomer(customer);
        }
        return true;
    }
}

saasdemo-order模块新增配置

WebMvcConfig配置

package org.example.saas.order.config;

import org.example.saas.core.interceptor.SaasInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Bean
    public SaasInterceptor kutaShopInterceptor() {
        return new SaasInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(kutaShopInterceptor()).addPathPatterns("/**");
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
//        config.addAllowedOrigin("*");
        config.addAllowedOriginPattern("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

DubboReferenceConfiguration配置

package org.example.saas.order.config;


import com.chia.multienty.core.dubbo.service.DubboWechatService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.spring.ReferenceBean;
import org.example.saas.core.dubbo.service.DubboMasterService;
import org.example.saas.core.dubbo.service.DubboUserService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnProperty(prefix = "dubbo", name = "enabled", havingValue = "true")
public class DubboReferenceConfiguration {

    @Bean
    @DubboReference(timeout = 5000)
    public ReferenceBean<DubboMasterService> dubboMasterServiceReferenceBean() {
        return new ReferenceBean<>();
    }

    @Bean
    public DubboMasterService dubboMasterService(ReferenceBean<DubboMasterService> dubboMasterServiceReferenceBean) {
        DubboMasterService dubboMasterService = dubboMasterServiceReferenceBean.getObject();
        return dubboMasterService;
    }

    @Bean
    @DubboReference(timeout = 5000)
    public ReferenceBean<DubboUserService> dubboUserServiceReferenceBean() {
        return new ReferenceBean<>();
    }

    @Bean
    public DubboUserService dubboUserService(ReferenceBean<DubboUserService> dubboUserServiceReferenceBean) {
        DubboUserService dubboUserService = dubboUserServiceReferenceBean.getObject();
        return dubboUserService;
    }
}

修复历史问题

1.修改CustomerDetailGetParameter implements Serializable

2.重新拉取multienty源码并执行 mvn clean install

3.修改在上一章节中我们在nacos中shardingsphere-saasdemo-order.yml配置

com.chia.multienty.core.sharding.tools.ShardingAlgorithmTool.tableNames中逻辑表名称ks_开头修改为saas_开头

4.修改nacos中shardingsphere-saasdemo-order.yml配置如下,下面的是示例, 如果你有多个数据源都要加上druid的配置。

            ds_order_1:
                type: com.alibaba.druid.pool.DruidDataSource
                driverClassName: com.mysql.cj.jdbc.Driver
                url: jdbc:mysql://xxxx:3301/saas_order_1?autoReconnect=true&useUnicode=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
                username: saas_order_1
                password: xxxx
                initialSize: 1
                minIdle: 3
                maxActive: 20
                # 配置获取连接等待超时的时间
                maxWait: 60000
                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
                timeBetweenEvictionRunsMillis: 10000
                # 配置一个连接在池中最小生存的时间,单位是毫秒
                minEvictableIdleTimeMillis: 30000
                # 验证的SQL
                validationQuery: select 'x'
                # 验证SQL超时时间
                validationQueryTimeout: 5000
                # 每隔timeBetweenEvictionRunsMillis时长使用validationQuery去验证非活动的连接
                testWhileIdle: true
                testOnBorrow: false
                testOnReturn: false
                
                # 打开PSCache,并且指定每个连接上PSCache的大小
                poolPreparedStatements: true
                maxPoolPreparedStatementPerConnectionSize: 20
                # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
                connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;
                # 合并多个DruidDataSource的监控数据
                useGlobalDataSourceStat: true

测试下单

在postman执行订单生成

最后我们查看数据库中已生成数据。

OK, 通过shardingsphere的分区分库,我们将数据锁定到saas_order_1数据库中,下一章我们将讲解如何关联查询。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Multienty

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值