京东秒杀之秒杀实现

1 登录判断

用户在未登录状态下可以查看商品列别以及秒杀商品详情,但不可以在未登录状态进行秒杀商品的操作,当用户点击开始秒杀时,进行登陆验证

1.1 编写前端登录判断

商品详情

秒杀商品详情

没有收货地址的提示。。。

商品名称
商品图片
秒杀开始时间

商品原价
秒杀价
库存数量

1.2 编写后端登录判断方法

1 编写sevice接口及其实现类方法

sevice接口

public User getUserByToken(String token);

实现类

/**

* 根据token查询用户

* @param token

* @return

*/

@Override

public User getUserByToken(String token) {

return myRedisTemplate.get(MemberServerKeyPrefix.USER_TOKEN, token, User.class);

}

2 编写controller层方法

/**

* 获取当前用户

* @param token 利用cookie中存储的token来判断用户

* @return

*/

@GetMapping("/getCurrent")

public Result getCurrent(@CookieValue(value = CookieUtil.TOKEN_COOKIE_NAME, required = false) String token){

User user = userService.getUserByToken(token);

return Result.success(user);

}

3 测试

2 后端登陆判断方法的优化

2.1 调整项目结构

1 添加依赖

2 将member-server下MemberServerKeyPrefix类移动至member-api下

3 将member-server下CookieUtil类移动至member-api下

4 编写getCookie方法

/**

* 获取cookie

* @param request

* @param tokenCookieName

* @return

*/

public static String getCookie(HttpServletRequest request, String tokenCookieName) {

Cookie[] cookies = request.getCookies();

if (cookies != null && cookies.length > 0){

for (Cookie cookie : cookies) {

//找到cookie

if (cookie.getName().equals(tokenCookieName)){

return cookie.getValue();

}

}

}

return null;

}

2.2 编写自定义SpringMVC参数解析器

public class UserMethodArgumentResolver implements HandlerMethodArgumentResolver {

@Autowired

private MyRedisTemplate myRedisTemplate;

/**

* 判断参数类型是否为User

* @param methodParameter

* @return

*/

@Override

public boolean supportsParameter(MethodParameter methodParameter) {

return methodParameter.getParameterType() == User.class &&

methodParameter.getParameterAnnotation(RedisValue.class) != null;

}

/**

* 自定义参数解析器

* @param parameter

* @param mavContainer

* @param webRequest

* @param binderFactory

* @return

* @throws Exception

*/

@Override

public Object resolveArgument(MethodParameter parameter,

ModelAndViewContainer mavContainer,

NativeWebRequest webRequest,

WebDataBinderFactory binderFactory) throws Exception {

//获取请求对象

HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);

//获取cookie

String token = CookieUtil.getCookie(request, CookieUtil.TOKEN_COOKIE_NAME);

if (StringUtils.isEmpty(token)){

return null;

}

return myRedisTemplate.get(MemberServerKeyPrefix.USER_TOKEN, token, User.class);

}

}

2.3 编写配置类

@Configuration

public class WebConfig implements WebMvcConfigurer {

//注入参数解析器

@Autowired

private UserMethodArgumentResolver userMethodArgumentResolver;

/**

* 添加参数解析器

* @param resolvers

*/

@Override

public void addArgumentResolvers(List resolvers) {

resolvers.add(userMethodArgumentResolver);

}

@Bean

public UserMethodArgumentResolver userMethodArgumentResolver() {

return new UserMethodArgumentResolver();

}

}

2.4 编写自定义注解

@Target(ElementType.PARAMETER) //定义该注解应用于方法参数上

@Retention(RetentionPolicy.RUNTIME) //不仅保存在class中,JVM加载时也存在

public @interface RedisValue {

}

2.5 编写登陆判断方法

@RequestMapping("/getCurrent")

public Result getCurrent(@RedisValue User user){

return Result.success(user);

}

3 秒杀实现(同步)

实现流程:

判断用户是否登录

判断场次是否正常

根据秒杀的场次查出当前场次对应的秒杀商品

秒杀时间的问题 处于秒杀中才能抢购

判断用户是否已经参与过当前场次的抢购

判断库存是否足够

秒杀的原子性 【 做三件事 】

7.1 商品的库存 -1 t_seckill_goods(生成抢购订单)7.2 创建商品的订单表 t_order_info(参与抢购的商品表)7.3 创建秒杀订单 t_seckill_order(防止用户重复抢购的表)

3.1 编写前端秒杀动作

function doSeckill(){

//判断用户是否登录

if (!user){

layer.msg("请先登录!");

return;

}

$.ajax({

url: "http://localhost:9000/seckill/order/doSeckill",

type: "post",

xhrFields: {withCredentials: true}, //启用cookie

data:{seckillId:seckillId},

success:function (data) {

if(data.code==200){

window.location.href="order_detail.html?orderNo=" + data.data;

}else{

layer.msg(data.msg)

}

}

});

}

3.2 调整项目结构

1 添加依赖

2 添加参数解析器的配置类

将member-server下的参数解析器的配置类复制到seckill-server下

3.3 创建实体类

1 秒杀订单类

@Data

public class SeckillOrder implements Serializable {

private Long id;

private Long userId;

private String orderNo;

private Long seckillId;

}

3 订单商品类

@Data

public class OrderInfo implements Serializable {

public static final Integer STATUS_CANCEL=2; //手动取消订单

public static final Integer STATUS_NO_PAY=0;//未付款

public static final Integer STATUS_TIMEOUT=1; //订单超时未支付

public static final Integer STATUS_ACCOUNT_PAID=3;//已经付款过了

private String orderNo;

private Long userId;

private Long goodId;

private String goodImg;

private Long deliveryAddrId;

private String goodName;

private Integer goodCount;

private BigDecimal goodPrice;

private BigDecimal seckillPrice;

private Integer status=STATUS_NO_PAY;

private Date createDate;

private Date payDate;

}

3.4 编写属于订单的codeMsg

public static final SeckillCodeMsg SECKILL_OVER= new SeckillCodeMsg(500013,"秒杀结束了!");

public static final SeckillCodeMsg SECKILL_NOTBEGIN_OKOVER= new SeckillCodeMsg(500014,"不在秒杀时间段!!");

public static final SeckillCodeMsg REPEAT_SECKILL= new SeckillCodeMsg(500015,"您已经参与过秒杀了!");

3.5 创建雪花算法处理类

public class IdGenerateUtil {

private long workerId;

private long datacenterId;

private long sequence = 0L;

private long twepoch = 1288834974657L;

private long workerIdBits = 5L;

private long datacenterIdBits = 5L;

private long maxWorkerId = -1L ^ (-1L << workerIdBits);

private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

private long sequenceBits = 12L;

private long workerIdShift = sequenceBits;

private long datacenterIdShift = sequenceBits + workerIdBits;

private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

private long sequenceMask = -1L ^ (-1L << sequenceBits); //4095

private long lastTimestamp = -1L;

private static class IdGenHolder {

private static final IdGenerateUtil instance = new IdGenerateUtil();

}

public static IdGenerateUtil get() {

return IdGenHolder.instance;

}

public IdGenerateUtil() {

this(0L, 0L);

}

public IdGenerateUtil(long workerId, long datacenterId) {

if (workerId > maxWorkerId || workerId < 0) {

throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));

}

if (datacenterId > maxDatacenterId || datacenterId < 0) {

throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));

}

this.workerId = workerId;

this.datacenterId = datacenterId;

}

public synchronized long nextId() {

long timestamp = timeGen();

if (timestamp < lastTimestamp) {

throw new RuntimeException(String.format(

"Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));

}

//如果上次生成时间和当前时间相同,在同一毫秒内

if (lastTimestamp == timestamp) {

sequence = (sequence + 1) & sequenceMask;

if (sequence == 0) {

timestamp = tilNextMillis(lastTimestamp);

}

} else {

sequence = 0L;

}

lastTimestamp = timestamp;

return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)

| (workerId << workerIdShift) | sequence;

}

protected long tilNextMillis(long lastTimestamp) {

long timestamp = timeGen();

while (timestamp <= lastTimestamp) {

timestamp = timeGen();

}

return timestamp;

}

protected long timeGen() {

return System.currentTimeMillis();

}

}

3.5 创建mapper层

1 商品订单表(根据用户id和秒杀场次查询商品订单)

@Mapper

public interface SeckillOrderMapper {

@Select("select * from t_seckill_order where user_id=#{userId} and seckill_id=#{seckillId}")

public SeckillOrder findUserIdAndSeckillId(Long userId, Long seckillId) ;

@Insert("INSERT INTO t_seckill_order (user_id,order_no,seckill_id) VALUES (#{userId},#{orderNo},#{seckillId})")

public void createSeckillOrder(Long userId, Long seckillId, String orderNo);

}

2 订单详情表(根据用户id和秒杀场次查询商品的详细信息)

@Mapper

public interface OrderInfoMapper {

/**

* 添加订单到数据库中

* @param orderInfo

*/

@Insert("INSERT INTO t_order_info (order_no,user_id,good_id,good_img,delivery_addr_id,good_name,good_count,good_price,seckill_price,status,create_date,pay_date) VALUES \n" +

"(#{orderNo},#{userId},#{goodId},#{goodImg},#{deliveryAddrId},#{goodName},#{goodCount},#{goodPrice},#{seckillPrice},#{status},#{createDate},null)")

public void insert(OrderInfo orderInfo);

}

3.5 创建订单service业务逻辑接口及其实现类

1 商品订单表(根据用户id和秒杀场次查询商品订单)

public interface SeckillOrderService {

public SeckillOrder findUserIdAndSeckillId(Long id, Long seckillId);

public void createSeckillOrder(Long userId, Long seckillId, String orderNo);

}

@Service

public class SeckillOrderServiceImpl implements SeckillOrderService {

@Autowired

private SeckillOrderMapper seckillOrderMapper;

@Override

public SeckillOrder findUserIdAndSeckillId(Long id, Long seckillId) {

return seckillOrderMapper.findUserIdAndSeckillId(id, seckillId);

}

@Override

public void createSeckillOrder(Long userId, Long seckillId, String orderNo) {

seckillOrderMapper.createSeckillOrder(userId, seckillId, orderNo);

}

}

2 订单详情表(根据用户id和秒杀场次查询商品的详细信息)

public interface OrderInfoService {

public String doSeckill(Long userId, Long seckillId);

}

@Service

public class OrderInfoServiceImpl implements OrderInfoService {

@Autowired

private OrderInfoMapper orderInfoMapper;

@Autowired

private SeckillGoodService seckillGoodService;

@Autowired

private SeckillOrderService seckillOrderService;

@Transactional //使用事务

@Override

public String doSeckill(Long userId, Long seckillId) {

// 7. 秒杀的原子性 【 需要做三件事 】

// 7.1 商品的库存 -1 t_seckill_goods

seckillGoodService.decrStock(seckillId);

// 7.2 创建商品的订单表 t_order_info

String orderNo = this.createOrderInfo(userId, seckillId);

// 7.3 创建秒杀订单 t_seckill_order

seckillOrderService.createSeckillOrder(userId, seckillId, orderNo);

return orderNo;

}

/**

* 创建订单

* @param userId

* @param seckillId

* @return

*/

private String createOrderInfo(Long userId, Long seckillId){

OrderInfo orderInfo = new OrderInfo();

//查询商品信息

SeckillGoodVo vo = seckillGoodService.find(seckillId);

//填写订单表

orderInfo.setCreateDate(new Date());

//送货地址 没有地址

orderInfo.setDeliveryAddrId(null);

//抢购,限制只卖一件

orderInfo.setGoodCount(1);

orderInfo.setGoodId(vo.getGoodId());

orderInfo.setGoodImg(vo.getGoodImg());

orderInfo.setGoodName(vo.getGoodName());

//原价

orderInfo.setGoodPrice(vo.getGoodPrice());

//秒杀价格

orderInfo.setSeckillPrice(vo.getSeckillPrice());

//处理用户的id

orderInfo.setUserId(userId);

//用雪花算法加密订单编号

orderInfo.setOrderNo(IdGenerateUtil.get().nextId()+"");

//添加数据到 订单的表中 【配送 、售后 】

orderInfoMapper.insert(orderInfo);

//获取订单编号

return orderInfo.getOrderNo();

}

}

3.6 创建controller层

@RestController

@RequestMapping("/order")

public class OrderInfoController {

@Autowired

private SeckillGoodService seckillGoodService;

@Autowired

private SeckillOrderService seckillOrderService;

@Autowired

private OrderInfoService orderInfoService;

@RequestMapping("/doSeckill")

public Result doSeckill(Long seckillId, @RedisValue User user){

// 1. 判断用户是否登录

if(user == null){

return Result.error(SeckillCodeMsg.LOGIN_TIMEOUT);

}

// 2. 判断场次是否正常[参数]

if(seckillId == null || seckillId <= 0){

return Result.error(SeckillCodeMsg.OP_ERROR);

}

// 3. 根据秒杀的场次查出当前场次对应的秒杀商品

SeckillGoodVo seckillGoodVo = seckillGoodService.find(seckillId);

if(seckillGoodVo == null){ // 11 12 16

return Result.error(SeckillCodeMsg.OP_ERROR);

}

// 4. 秒杀时间的问题 处于秒杀中才能抢购

Date now = new Date();

if(now.getTime() < seckillGoodVo.getStartDate().getTime() ||

now.getTime() > seckillGoodVo.getEndDate().getTime()){

//秒杀结束了

return Result.error(SeckillCodeMsg.SECKILL_NOTBEGIN_OKOVER);

}

// 5. 判断用户是否已经参与过当前场次的抢购

SeckillOrder seckillOrder = seckillOrderService.findUserIdAndSeckillId(user.getId(), seckillId);

if(seckillOrder != null){ // 11 12 16

return Result.error(SeckillCodeMsg.REPEAT_SECKILL);

}

// 6. 判断库存是否足够

if(seckillGoodVo.getStockCount() <= 0){

// 超卖

return Result.error(SeckillCodeMsg.SECKILL_OVER);

}

// 7. 秒杀的原子性 【 需要做三件事 】

// 7.1 商品的库存 -1 t_seckill_goods

// 7.2 创建商品的订单表 t_order_info

// 7.3 创建秒杀订单 t_seckill_order

String orderNo= orderInfoService.doSeckill(user.getId(), seckillId);

return Result.success(orderNo);

}

}

3.7 测试

3.8 订单详情展示

1 编写前端页面

商品详情

秒杀订单详情

商品名称
商品图片
订单价格
下单时间
订单状态
收货人 liushao 18698789339
收货地址 北京市海淀区成府路

2 编写Mapper层方法

@Select("SELECT * FROM t_order_info WHERE order_no=#{orderNo}")

public OrderInfo find(String orderNo);

3 编写service业务逻辑接口及其实现类

接口

public OrderInfo find(String orderNo);

实现类

@Override

public OrderInfo find(String orderNo) {

return orderInfoMapper.find(orderNo);

}

4 编写controller层

@RequestMapping("/find")

public Result find(String orderNo, @RedisValue User user){

//判断

if(StringUtils.isEmpty(orderNo)){

return Result.error(SeckillCodeMsg.OP_ERROR);

}

if(user == null){

return Result.error(SeckillCodeMsg.LOGIN_TIMEOUT);

}

//根据订单编号查询订单

OrderInfo orderInfo= orderInfoService.find(orderNo);

//只能查看自己的订单

if(orderInfo==null || !orderInfo.getUserId().equals(user.getId())){

//订单编号有问题 或者用户和订单不匹配

return Result.error(SeckillCodeMsg.OP_ERROR);

}

return Result.success(orderInfo);

}

5 测试

4 秒杀优化(异步)