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