目录
一、创建秒杀服务
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 秒杀
数据库: