创建模拟秒杀下单活动的service层接口
com\imooc\service\SecKillService.java
package com.imooc.service;
public interface SecKillService {
/**
* 查询秒杀活动特价商品的信息
* @param productId
* @return
*/
String querySecKillProductInfo(String productId);
/**
* 模拟不同用户秒杀同一商品的请求
* @param productId
* @return
*/
void orderProductMockDiffUser(String productId);
}
创建模拟秒杀下单活动的service接口的实现类
com\imooc\service\imp\SecKillServiceImp.java
package com.imooc.service.imp;
import com.imooc.exception.SellException;
import com.imooc.service.RedisLock;
import com.imooc.service.SecKillService;
import com.imooc.utils.KeyUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
Service
@Slf4j
public class SecKillServiceImp implements SecKillService {
private static final int TIMEOUT = 10 * 1000; //超时时间 10s
@Autowired
private RedisLock redisLock;
/**
* 国庆活动,皮蛋粥特价,限量100000份
*/
static Map<String,Integer> products;
static Map<String,Integer> stock;
static Map<String,String> orders;
static
{
/**
* 模拟多个表,商品信息表,库存表,秒杀成功订单表
*/
products = new HashMap<>();
stock = new HashMap<>();
orders = new HashMap<>();
products.put("123456", 100000);
stock.put("123456", 100000);
}
private String queryMap(String productId)
{
return "国庆活动,皮蛋粥特价,限量份"
+ products.get(productId)
+" 还剩:" + stock.get(productId)+" 份"
+" 该商品成功下单用户数目:"
+ orders.size() +" 人" ;
}
/*查询秒杀活动特价商品的信息*/
@Override
public String querySecKillProductInfo(String productId)
{
return this.queryMap(productId);
}
/*模拟不同用户秒杀同一商品的请求*/
@Override
public void orderProductMockDiffUser(String productId)
{
//加锁
long time = System.currentTimeMillis() + TIMEOUT;
log.info(String.valueOf(time));
Boolean str = redisLock.lock(productId,String.valueOf(time));
if(!str){
throw new SellException(101,"哎呦喂 人太多了,换个姿势再试试!");
}
//1.查询该商品库存,为0则活动结束。
int stockNum = stock.get(productId);
if(stockNum == 0) {
throw new SellException(100,"活动结束");
}else {
//2.下单(模拟不同用户openid不同)
orders.put(KeyUtil.genUniqueKey(),productId);
//3.减库存
stockNum =stockNum-1;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
stock.put(productId,stockNum);
}
//解锁
redisLock.unlock(productId,String.valueOf(time));
}
}
编写加锁和解锁的实现类
在秒杀或者下单的业务方法中调用
com\imooc\service\RedisLock.java
package com.imooc.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
@Slf4j
public class RedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 加锁
* @param key 例如:productId:123456
* @param value 当前时间+超时时间 例如:1582891777814
* @return
*/
public boolean lock(String key, String value) {
log.info("value={}",value);
/*如果key不存在 就赋值 返回true 加锁成功 相当于reids的setnx*/
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
//如果key存在 获取当前key已经存在的value
String currentValue = redisTemplate.opsForValue().get(key);
log.info("currentValue={}",currentValue);
//如果锁过期
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {/*如果当前时间大于超时时间 说明已经抢单加锁超过10秒了*/
//获取上一个锁的时间
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);/*获取当前key的旧value 同时把新的value set进去*/
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {/*代码严谨性判断*/
return true;
}
}
return false;
}
/**
* 解锁
* @param key
* @param value
*/
public void unlock(String key, String value) {
try {
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
}catch (Exception e) {
log.error("【redis分布式锁】解锁异常, {}", e);
}
}
}
编写websocket的实现类
com\imooc\service\WebSocket.java
package com.imooc.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @author: menghaibin
* @create: 2020-02-27 20:26
* @description: websocket业务实现
**/
Component
@ServerEndpoint("/webSocket")
@Slf4j
public class WebSocket {
private Session session;
/*定义websocket容器*/
private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<>();
/*得到连接时*/
@OnOpen
public void onOpen(Session session){
this.session = session;
webSocketSet.add(this);
log.info("websocket 有新的链接 总数为:{}",webSocketSet.size());
}
/*关闭连接时*/
@OnClose
public void onClose(){
webSocketSet.remove(this);
log.info("websocket 链接断开 总数为:{}",webSocketSet.size());
}
/*收到消息*/
@OnMessage
public void onMessage(String message){
log.info("websocket 收到客户端发来的消息:{}",message);
}
/*发送消息*/
public void sendMessage(String message){
for(WebSocket webSocket : webSocketSet){
log.info("websocket 广播消息 message={}",message);
try{
webSocket.session.getBasicRemote().sendText(message);
}catch (Exception e){
log.error(e.getMessage());
}
}
}
}
编写controller
com\imooc\controller\SecKillController.java
package com.imooc.controller;
import com.imooc.service.SecKillService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/skill")
@Slf4j
public class SecKillController {
@Autowired
private SecKillService secKillService;
/**
* 查询秒杀活动特价商品的信息
* @param productId
* @return
*/
@GetMapping("/query/{productId}")
public String query(@PathVariable String productId)throws Exception
{
return secKillService.querySecKillProductInfo(productId);
}
/**
* 秒杀,没有抢到获得"哎呦喂,xxxxx",抢到了会返回剩余的库存量
* @param productId
* @return
* @throws Exception
*/
@GetMapping("/order/{productId}")
public String skill(@PathVariable String productId)throws Exception
{
log.info("@skill request, productId:" + productId);
secKillService.orderProductMockDiffUser(productId);
return secKillService.querySecKillProductInfo(productId);
}
}
可以用压测工具压测:http://127.0.0.1/sell/skill/query/123456