乐优商城(四十二)秒杀功能

目录

一、创建秒杀服务

1.1 创建module

1.2 pom文件

1.3 application.yml

二、页面分析

2.1 页面效果

2.2 需要的数据

2.2.1 秒杀商品列表页面

2.2.2 秒杀商品详情页面

2.2.3 最终数据结构

三、后台开发

3.1 添加秒杀商品

3.1.1 功能需求

3.1.2 Controller

3.1.3 Mapper

3.1.4 Service

3.2 秒杀商品查询

3.2.1 Controller

3.2.2 Service

3.3 秒杀

3.3.1 Controller

3.3.2 Service

3.3.3 Mapper

3.3.4 配置

四、测试

4.1 添加秒杀商品

4.2 查询秒杀商品

4.3 秒杀


一、创建秒杀服务

1.1 创建module

1.2 pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>leyou</artifactId>
        <groupId>com.leyou.parent</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou.seckill</groupId>
    <artifactId>leyou-secskill</artifactId>

    <dependencies>
        <!-- eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--商品微服务-->
        <dependency>
            <groupId>com.leyou.item.interface</groupId>
            <artifactId>leyou-item-interface</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

1.3 application.yml

server:
  port: 8090
spring:
  application:
    name: secskill-service
  data:
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: 192.168.19.121:9300
  rabbitmq:
    virtual-host: /leyou
    username: /leyou
    password: leyou
    host: 192.168.19.121
  jackson:
    default-property-inclusion: non_null # 配置json处理时忽略空值
  resources:
    add-mappings: true
    chain:
      enabled: true
      gzipped: true
      html-application-cache: true
    cache:
      period: 3600m
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
    registry-fetch-interval-seconds: 5
  instance:
    instance-id: ${spring.application.name}:${server.port}
    prefer-ip-address: true  #当你获取host时,返回的不是主机名,而是ip
    ip-address: 127.0.0.1
    lease-expiration-duration-in-seconds: 10 #10秒不发送九过期
    lease-renewal-interval-in-seconds: 5 #每隔5秒发一次心跳

二、页面分析

2.1 页面效果

秒杀商品展示页面:

秒杀商品详情页面:

2.2 需要的数据

2.2.1 秒杀商品列表页面

image、title、price,seckill_stock

2.2.2 秒杀商品详情页面

和普通商品详情页面一样

2.2.3 最终数据结构

 

package com.leyou.seckill.vo;

import javax.persistence.*;
import java.util.Date;

/**
 * @Author: 98050
 * @Time: 2018-11-10 11:48
 * @Feature: 秒杀商品对象
 */
@Table(name = "tb_seckill_sku")
public class SeckillGoods {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    /**
     * 秒杀商品的id
     */
    private Long skuId;
    /**
     * 秒杀开始时间
     */
    private Date startTime;
    /**
     * 秒杀结束时间
     */
    private Date endTime;
    /**
     * 秒杀价格
     */
    private Double seckillPrice;
    /**
     * 商品标题
     */
    private String title;

    /**
     * 商品图片
     */
    private String image;

    /**
     * 是否可以秒杀
     */
    private Boolean enable;

    /**
     * 秒杀库存
     */
    @Transient
    private Integer stock;

    @Transient
    private Integer seckillTotal;

}

三、后台开发

3.1 添加秒杀商品

3.1.1 功能需求

根据传入的商品id,将其设置为可以秒杀的商品。主要是针对后台管理开发的功能。

3.1.2 Controller

  • 请求方式:POST
  • 请求路径:api.leyou.com/api/seckill/addSeckill
  • 参数:SeckillParameter对象
  • 返回结果:200添加成功,500内部服务器异常
    @PostMapping("addSeckill")
    public ResponseEntity<Boolean> addSeckillGoods(@RequestBody SeckillParameter seckillParameter){
        if (seckillParameter != null){
            this.seckillService.addSeckillGoods(seckillParameter);
        }else {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
        return ResponseEntity.ok().build();
    }

SeckillParameter

封装请求参数

package com.leyou.seckill.vo;

import java.util.Date;
import java.util.List;

/**
 * @Author: 98050
 * @Time: 2018-11-10 17:30
 * @Feature: 秒杀设置参数
 */
public class SeckillParameter {

    /**
     * 要秒杀的sku id
     */
    private Long id;

    /**
     * 秒杀开始时间
     */
    private Date startTime;

    /**
     * 秒杀结束时间
     */
    private Date endTime;

    /**
     * 参与秒杀的商品数量
     */
    private Integer count;

    /**
     * 折扣
     */
    private double  discount;

}

3.1.3 Mapper

package com.leyou.seckill.mapper;

import com.leyou.seckill.vo.SeckillGoods;
import tk.mybatis.mapper.common.Mapper;

/**
 * @Author: 98050
 * @Time: 2018-11-10 16:54
 * @Feature:
 */
public interface SeckillMapper extends Mapper<SeckillGoods> {
}
package com.leyou.seckill.mapper;

import com.leyou.item.pojo.Stock;
import tk.mybatis.mapper.common.Mapper;

/**
 * @Author: 98050
 * @Time: 2018-11-10 17:58
 * @Feature:
 */
public interface StockMapper extends Mapper<Stock> {
}

3.1.4 Service

接口

    /**
     * 添加秒杀商品
     * @param seckillParameter
     */
    void addSeckillGoods(SeckillParameter seckillParameter);

实现

为了方便测试,秒杀的开始和结束时间在系统内部设定。

    /**
     * 添加秒杀商品
     * @param seckillParameter
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addSeckillGoods(SeckillParameter seckillParameter) {


        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        seckillParameter.setStartTime(calendar.getTime());
        calendar.add(Calendar.HOUR,2);
        seckillParameter.setEndTime(calendar.getTime());

        //1.根据spu_id查询商品
        Long id = seckillParameter.getId();
        Sku sku = goodsClient.querySkuById(id);
        //2.插入到秒杀商品表中
        SeckillGoods seckillGoods = new SeckillGoods();
        seckillGoods.setEnable(true);
        seckillGoods.setStartTime(seckillParameter.getStartTime());
        seckillGoods.setEndTime(seckillParameter.getEndTime());
        seckillGoods.setImage(sku.getImages());
        seckillGoods.setSkuId(sku.getId());
        seckillGoods.setStock(seckillParameter.getCount());
        seckillGoods.setTitle(sku.getTitle());
        seckillGoods.setSeckillPrice(sku.getPrice()*seckillParameter.getDiscount());
        this.seckillMapper.insert(seckillGoods);
        //3.更新对应的库存信息,tb_stock
        Stock stock = stockMapper.selectByPrimaryKey(sku.getId());
        stock.setSeckillStock(stock.getSeckillStock() + seckillParameter.getCount());
        stock.setSeckillTotal(stock.getSeckillTotal() + seckillParameter.getCount());
        stock.setStock(stock.getStock() - seckillParameter.getCount());
        this.stockMapper.updateByPrimaryKeySelective(stock);

    }

3.2 秒杀商品查询

3.2.1 Controller

  • 请求方式:GET
  • 请求路径:api.leyou.com/api/seckill/list
  • 请求参数:无
  • 返回结果:秒杀商品列表
    /**
     * 查询秒杀商品
     * @return
     */
    @GetMapping("list")
    public ResponseEntity<List<SeckillGoods>> querySeckillGoods(){
        List<SeckillGoods> list = this.seckillService.querySeckillGoods();
        if (list == null || list.size() < 0){
            return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
        }
        return ResponseEntity.ok(list);
    }

3.2.2 Service

接口

    /**
     * 查询秒杀商品
     * @return
     */
    List<SeckillGoods> querySeckillGoods();

实现

    /**
     * 查询秒杀商品
     * @return
     */
    @Override
    public List<SeckillGoods> querySeckillGoods() {
        Example example = new Example(SeckillGoods.class);
        example.createCriteria().andEqualTo("enable",true);
        return this.seckillMapper.selectByExample(example);
    }

3.3 秒杀

每个用户只能秒杀一件商品……

3.3.1 Controller

  • 请求方式:POST
  • 请求路径:api.leyou.com/api/seckill/seck
  • 请求参数:SeckillGoods对象
  • 返回结果:秒杀成功返回订单号
    @PostMapping("seck")
    public ResponseEntity<Long> seckillOrder(@RequestBody SeckillGoods seckillGoods){
        //1.创建订单
        Long id = this.seckillService.createOrder(seckillGoods);
        //2.判断秒杀是否成功
        if (id == null){
           return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).build();
        }
        return ResponseEntity.ok(id);
    }

3.3.2 Service

接口

    /**
     * 创建订单
     * @param seckillGoods
     * @return
     */
    Long createOrder(SeckillGoods seckillGoods);

实现

思路就是调用订单微服务创建订单,所以要封装order对象。根据order需要的数据进行填充

需要的数据:

{
  "totalPay": 236800,
  "postFee": 0,
  "paymentType": 2,
  "actualPay": 236800,
  "buyerMessage": null,
  "buyerNick": "huge",
  "orderDetails": [
    {
      "skuId": 3893493,
      "num": 1,
      "title": "苹果(Apple)iPhone 6 (A1586) 16GB 金色 移动联通电信4G手机3",
      "price": 236800,
      "ownSpec": "{\"机身颜色\":\"钻雕蓝\",\"内存\":\"4GB\",\"机身存储\":\"64GB\"}",
      "image": "http://image.leyou.com/images/9/4/1524297342728.jpg"
    }
  ],
  "receiver": "锋哥",
  "receiverMobile": "15800000000",
  "receiverState": "上海",
  "receiverCity": "上海",
  "receiverDistrict": "浦东新签",
  "receiverAddress": "航头镇航头路18号传智播客3号楼",
  "receiverZip": "210000",
  "invoiceType": 0,
  "sourceType":2
}

 这里面主要缺少的是地址信息,这一块先跳过,地址默认设置,后面再进行修改。

/**
     * 创建订单
     * @param seckillGoods
     * @return
     */
    @Override
    public Long createOrder(SeckillGoods seckillGoods) {

        Order order = new Order();
        order.setPaymentType(1);
        order.setTotalPay(seckillGoods.getSeckillPrice());
        order.setActualPay(seckillGoods.getSeckillPrice());
        order.setPostFee(0+"");
        order.setReceiver("李四");
        order.setReceiverMobile("15812312312");
        order.setReceiverCity("西安");
        order.setReceiverDistrict("碑林区");
        order.setReceiverState("陕西");
        order.setReceiverZip("000000000");
        order.setInvoiceType(0);
        order.setSourceType(2);

        OrderDetail orderDetail = new OrderDetail();
        orderDetail.setSkuId(seckillGoods.getSkuId());
        orderDetail.setNum(1);
        orderDetail.setTitle(seckillGoods.getTitle());
        orderDetail.setImage(seckillGoods.getImage());
        orderDetail.setPrice(seckillGoods.getSeckillPrice());
        orderDetail.setOwnSpec(this.skuMapper.selectByPrimaryKey(seckillGoods.getSkuId()).getOwnSpec());

        order.setOrderDetails(Arrays.asList(orderDetail));

        String seck = "seckill";
        ResponseEntity<List<Long>> responseEntity = this.orderClient.createOrder(seck,order);


        if (responseEntity.getStatusCode() == HttpStatus.OK){
            //库存不足,返回null
            return null;
        }
        //修改秒杀商品的库存

        return responseEntity.getBody().get(0);
    }

封装好order对象后,直接调用orderApi即可,传入秒杀标记。

3.3.3 Mapper

要查询sku具体信息

package com.leyou.seckill.mapper;

import com.leyou.item.pojo.Sku;
import tk.mybatis.mapper.common.Mapper;

/**
 * @Author: 98050
 * @Time: 2018-11-12 16:21
 * @Feature:
 */
public interface SkuMapper extends Mapper<Sku> {
}

3.3.4 配置

问题:订单微服务在创建订单的时候要进行用户登录认证,所以这里面通过feign client来调用订单服务的时候需要将header中的信息进行转发。因为订单微服务需要拿到token。

OrderClient

指定配置类OrderConfig

package com.leyou.seckill.client;

import com.leyou.order.api.OrderApi;
import com.leyou.seckill.config.OrderConfig;
import org.springframework.cloud.openfeign.FeignClient;

/**
 * @Author: 98050
 * @Time: 2018-11-12 15:19
 * @Feature: 订单接口
 */
@FeignClient(value = "order-service",configuration = OrderConfig.class)
public interface OrderClient extends OrderApi {
}

OrderConfig

将header中的信息转发给订单微服务

package com.leyou.seckill.config;


import feign.Feign;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;


/**
 * @Author: 98050
 * @Time: 2018-11-13 22:39
 * @Feature: order服务配置,转发header
 */
@Configuration
public class OrderConfig {
    @Bean
    public Feign.Builder feignBuilder(){
        return Feign.builder().requestInterceptor(requestTemplate -> {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            Enumeration<String> headerNames = request.getHeaderNames();
            if (headerNames != null) {
                while (headerNames.hasMoreElements()) {
                    String name = headerNames.nextElement();
                    String values = request.getHeader(name);
                    requestTemplate.header(name, values);
                }
            }
        });
    }
}

四、测试

4.1 添加秒杀商品

数据库中:

4.2 查询秒杀商品

4.3 秒杀

数据库:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值