SpringBoot构建电商秒杀项目(四)交易模块开发

本文详细介绍了在SpringBoot项目中如何构建交易模块,包括创建用户下单模型OrderModel,设计order_info表,使用Mybatis Generator,实现交易下单流程,如校验状态、落单减库存、生成订单号和销量增加,并探讨了下单策略。同时涵盖了Controller层的实现和前端设计。

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

第四章 交易模块开发

4.1 交易模型管理–交易模型创建

1. 创建用户下单模型OrderModel

//用户下单的交易模型
public class OrderModel {
    //交易单号,例如2019052100001212,使用string类型
    private String id;

    //购买的用户id
    private Integer userId;

    //购买的商品id
    private Integer itemId;

    //购买时商品的单价
    private BigDecimal itemPrice;

    //购买数量
    private Integer amount;

    //购买总金额
    private BigDecimal orderPrice;
    
}

2. 数据库创建order_info表

类型说明
idString交易号
userIdInteger购买用户id
itemIdInteger购买商品id
itemPriceBigDecimal购买商品单价
amountInteger购买数量
orderPriceBigDecimal购买总金额

3. mybatis generator生成数据库映射

mybatis generator生成order_info表及OrderDO
运行 mvn mybatis-generator:generate

<table tableName="order_info" domainObjectName="OrderDO"
       enableCountByExample="false"
       enableUpdateByExample="false"
       enableDeleteByExample="false"
       enableSelectByExample="false"
       selectByExampleQueryId="false" ></table>

4.2 交易模型管理-交易下单

1.OrderService接口部分

实现createOrder函数,通过用户id,商品id以及购买数量创建订单

 OrderModel createOrder(Integer userId, Integer itemId, Integer amount) throws BusinessException;

2.OrderServiceImpl实现类

用户下单的流程主要包括:

  • 校验下单状态,包括下单商品是否存在,用户是否合法,购买数量是否正确
  • 减少库存量(落单减库存)
  • 订单入库
  • 返回前端
1. 校验下单状态

校验下单商品是否存在是通过商品id在itemService查询itemModel,校验用户是否合法是用userId在userService里查找userModel,校验购买数量我们规定一个用户最少购买1件最多不超过100

 //1.校验下单状态,下单的商品是否存在,用户是否合法,购买数量是否正确
    ItemModel itemModel = itemService.getItemById(itemId);
    if (itemModel == null) {
        throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "商品信息不存在");
    }

    UserModel userModel = userService.getUserById(userId);
    if (userModel == null) {
        throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "用户信息不存在");
    }

    if (amount <= 0 || amount > 99) {
        throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "数量信息不存在");
    }
2. 落单减库存

减少库存量有两种方式:落单减库存以及支付减库存
对应的就是在用户下单成功还是支付成功后减少库存,支付成功后减库存因为支付时未加库存锁,所以会出现支付成功但因库存不够退单情况。而落单减库存则可能出现恶意刷单的情况。这里我们采取落单减库存的方式。

ItemStockMapper增加对item_stock数据库减操作
ItemStockMapper.xml

  <update id="decreaseStock">

    update item_stock
    set stock = stock-#{amount}
    where item_id = #{item_id} and stock>=#{amount}
  </update>

ItemStockMapper

    int decreaseStock(@Param("itemId") Integer itemId, @Param("amount") Integer amount);

ItemService接口

//库存扣减
    boolean decreaseStock(Integer itemId,Integer amount) throws BusinessException;

ItemServiceImpl实现类

   @Override
    @Transactional
    public boolean decreaseStock(Integer itemId, Integer amount) throws BusinessException {
        int affectedRow = itemStockDOMapper.decreaseStock(itemId, amount);
        if (affectedRow > 0) {
            //更新库存成功
            return true;
        } else {
            //更新库存失败
            return false;
        }
    }
3. 生成交易订单号

交易订单号有16类,比如"2020031100000100",前8位为时间信息,年月日,中间6位为自增序列,最后2位为分库分表位,我们暂时不考虑
前8位我们用LocalDateTime类获取当前时间并转化为ISO_DATE类型的字符串

//订单号有16位
        StringBuilder stringBuilder = new StringBuilder();
        //前8位为时间信息,年月日
        LocalDateTime now = LocalDateTime.now();
        //2020-03-08
        String nowDate = now.format(DateTimeFormatter.ISO_DATE).replace("-","");
        stringBuilder.append(nowDate);

中间6位为自增序列,初始值为0,我们新建一个sequence_info的数据库,每次访问要加锁,表示一个下单数

sequ_info表

类型说明
nameString订单表
current_valueint当前订单数
stepint下单数加1

修改mybatis-generator生成SequenceDO,SequenceDOMapper

运行 mvn mybatis-generator:generate

<table tableName="sequence_info" domainObjectName="SequenceDO"
       enableCountByExample="false"
       enableUpdateByExample="false"
       enableDeleteByExample="false"
       enableSelectByExample="false"
       selectByExampleQueryId="false" ></table>

修改SequenceDOMapper.xml,自增时用for update添加行锁

<select id="getSequenceByName" parameterType="java.lang.String" resultMap="BaseResultMap">
  select
  <include refid="Base_Column_List" />
  from sequence_info
  where name = #{name,jdbcType=VARCHAR} for update
</select>

生成订单号

    private String generateOrderNo(){
        //订单号有16位
        StringBuilder stringBuilder = new StringBuilder();
        //前8位为时间信息,年月日
        LocalDateTime now = LocalDateTime.now();
        //2020-03-08
        String nowDate = now.format(DateTimeFormatter.ISO_DATE).replace("-","");
        stringBuilder.append(nowDate);

        //中间6位为自增序列
        //新建sequence_info表,初始值为0,实现每次获取加一
        //获取当前sequence
        int sequence = 0;
        SequenceDO sequenceDO = sequenceDOMapper.getSequenceByName("order_info");
        sequence = sequenceDO.getCurrentValue();
        sequenceDO.setCurrentValue(sequenceDO.getCurrentValue() + sequenceDO.getStep());
        sequenceDOMapper.updateByPrimaryKeySelective(sequenceDO);
        //拼足中间6位
        String sequenceStr = String.valueOf(sequence);
        for(int i = 0; i< 6-sequenceStr.length();i++) {
            stringBuilder.append(0);
        }
        stringBuilder.append(sequenceStr);
        //最后2位为分库分表位,暂时写死
        stringBuilder.append("00");

        return stringBuilder.toString();
4. 销量增加

itemDOMapper.xml

<update id="increaseSales">
  update item
  set sales = sales+ #{amount}
  where id = #{id,jdbcType=INTEGER}
</update>

itemDOMapper

int increaseSales(@Param("id") Integer id, @Param("amount") Integer amount);

ItemServiceImpl

@Override
@Transactional 
public void increaseSales(Integer itemId, Integer amount) throws BusinessException {
    itemDOMapper.increaseSales(itemId,amount);
}
5. 最终的OrderServiceImpl类

注解@Transactional的作用保证事务的一致
下单函数createOrder中用到了@Transactional(propagation = Propagation.REQUIRED)表示执行代码后,不管成功与否,直接提交事务。不管该方法是否在事务中,都会开启一个新的事务,为了保证订单号的唯一性,防止下单失败后订单号的回滚

@Override
    @Transactional(propagation = Propagation.REQUIRED) //propagation.REQUIRED表示执行代码后不管成功与否,直接提交事务
    public OrderModel createOrder(Integer userId, Integer itemId, Integer promoId, Integer amount) throws BusinessException {
        //1.校验下单状态,下单的商品是否存在,用户是否合法,购买的数量是否正确
        ItemModel itemModel = itemService.getItemById(itemId);
        if(itemModel == null)
        {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"商品信息不存在");
        }
        UserModel userModel = userService.getUserById(userId);
        if(userModel == null) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"用户信息不存在");
        }
        if(amount <= 0 || amount > 99) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"数量信息不正确");
        }

        //校验活动信息
        if(promoId!=null) {
            //(1)校验对应活动是否存在在这个适用商品
            if(promoId.intValue()!=itemModel.getPromoModel().getId()) {
                throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"活动信息不正常");
            //(2)校验活动是否正在进行中
            }else if(itemModel.getPromoModel().getStatus().intValue()!=2) {
                throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"活动信息还未开始");
            }
        }

        //2.落单减库存(还有一种:支付减库存(先支付成功,再看库存量)
        boolean result = itemService.decreaseStock(itemId,amount);
        if(!result) {
            throw new BusinessException(EmBusinessError.STOCK_NOT_ENOUGH);
        }
        //3.订单入库
        OrderModel orderModel = new OrderModel();
        orderModel.setUserId(userId);
        orderModel.setItemId(itemId);
        orderModel.setAmount(amount);
        orderModel.setPromoId(promoId);
        //是秒杀活动,价格为秒杀价格,否则为平销价格
        if(promoId != null) {
            orderModel.setItemPrice(itemModel.getPromoModel().getPromoItemPrice());
        }else {
            orderModel.setItemPrice(itemModel.getPrice());
        }
        orderModel.setOrderPrice(orderModel.getItemPrice().multiply(new BigDecimal(amount)));

        //生成交易流水号,订单号
        orderModel.setId(generateOrderNo());
        OrderDO orderDO = convertFromOrderModel(orderModel);
        orderDOMapper.insertSelective(orderDO);

        //加上商品的销量
        itemService.increaseSales(itemId, amount);
        //4.返回前端
        return orderModel;
    }

3. Controller层创建下单

//封装下单请求
@RequestMapping(value = "/createorder", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
@ResponseBody
public CommonReturnType createOrder(@RequestParam(name = "itemId") Integer itemId,
                                    @RequestParam(name = "amount") Integer amount) throws BusinessException {

    //获取用户登录信息
    Boolean isLogin = (Boolean) httpServletRequest.getSession().getAttribute("IS_LOGIN");
    if (isLogin == null || !isLogin.booleanValue()) {
        throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "用户还未登录,不能下单");
    }
    UserModel userModel = (UserModel) httpServletRequest.getSession().getAttribute("LOGIN_USER");


    OrderModel orderModel = orderService.createOrder(userModel.getId(), itemId, amount);

    return CommonReturnType.create(null);
}

4. 前端设计

____________________________________________________________【系列笔记已经更新完毕】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值