1.需求分析
1.需求
所謂“秒殺”,就是網絡賣家發佈一些超低價格的商品,所有買家在同一時間網上搶購的一種銷售方式。通俗一點講就是網絡商家爲促銷等目的組織的網上限時搶購活動。由於商品價格低廉,往往一上架就被搶購一空,有時只用一秒鐘。
秒殺商品通常有兩種限制:庫存限制、時間限制。
需求:
- 商家提交秒殺商品申請,錄入秒殺商品數據,主要包括:商品標題、原價、秒殺價、商品圖片、介紹等信息
- 運營商審覈秒殺申請
- 秒殺頻道首頁列出秒殺商品(進行中的)點擊秒殺商品圖片跳轉到秒殺商品詳細頁。
- 商品詳細頁顯示秒殺商品信息,點擊立即搶購實現秒殺下單,下單時扣減庫存。當庫存爲0或不在活動期範圍內時無法秒殺。
- 秒殺下單成功,直接跳轉到支付頁面(微信掃碼),支付成功,跳轉到成功頁,填寫收貨地址、電話、收件人等信息,完成訂單。
- 當用戶秒殺下單5分鐘內未支付,取消預訂單,調用微信支付的關閉訂單接口,恢復庫存
2.實現思路
秒殺技術實現核心思想是運用緩存減少數據庫瞬間的訪問壓力!讀取商品詳細信息時運用緩存,當用戶點擊搶購時減少緩存中的庫存數量,當庫存數爲0時或活動期結束時,同步到數據庫。 產生的秒殺預訂單也不會立刻寫到數據庫中,而是先寫到緩存,當用戶付款成功後再寫入數據庫。
秒殺頻道首頁
1.秒殺頻道首頁,顯示正在秒殺的商品(已經開始,未結束的商品)
serviceImpl
@Override
public List<TbSeckillGoods> findList() {
//獲取秒殺商品列表
List<TbSeckillGoods> seckillGoodsList = redisTemplate.boundHashOps("seckillGoods").values();
if(seckillGoodsList==null || seckillGoodsList.size()==0){
TbSeckillGoodsExample example=new TbSeckillGoodsExample();
Criteria criteria = example.createCriteria();
criteria.andStatusEqualTo("1");//審覈通過
criteria.andStockCountGreaterThan(0);//剩餘庫存大於0
criteria.andStartTimeLessThanOrEqualTo(new Date());//開始時間小於等於當前時間
criteria.andEndTimeGreaterThan(new Date());//結束時間大於當前時間
seckillGoodsList= seckillGoodsMapper.selectByExample(example);
//將商品列表裝入緩存
System.out.println("將秒殺商品列表裝入緩存");
for(TbSeckillGoods seckillGoods:seckillGoodsList){
redisTemplate.boundHashOps("seckillGoods").put(seckillGoods.getId(), seckillGoods);
}
}
return seckillGoodsList;
}
2.秒殺商品詳情頁
serviceImpl
@Override
public TbSeckillGoods findOneFromRedis(Long id) {
return (TbSeckillGoods)redisTemplate.boundHashOps("seckillGoods").get(id);
}
秒殺下單
serviceImpl
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private IdWorker idWorker;
@Override
public void submitOrder(Long seckillId, String userId) {
//從緩存中查詢秒殺商品
TbSeckillGoods seckillGoods =(TbSeckillGoods) redisTemplate.boundHashOps("seckillGoods").get(seckillId);
if(seckillGoods==null){
throw new RuntimeException("商品不存在");
}
if(seckillGoods.getStockCount()<=0){
throw new RuntimeException("商品已搶購一空");
}
//扣減(redis)庫存
seckillGoods.setStockCount(seckillGoods.getStockCount()-1);
redisTemplate.boundHashOps("seckillGoods").put(seckillId, seckillGoods);//放回緩存
if(seckillGoods.getStockCount()==0){//如果已經被秒光
seckillGoodsMapper.updateByPrimaryKey(seckillGoods);//同步到數據庫
redisTemplate.boundHashOps("seckillGoods").delete(seckillId);
}
//保存(redis)訂單
long orderId = idWorker.nextId();
TbSeckillOrder seckillOrder=new TbSeckillOrder();
seckillOrder.setId(orderId);
seckillOrder.setCreateTime(new Date());
seckillOrder.setMoney(seckillGoods.getCostPrice());//秒殺價格
seckillOrder.setSeckillId(seckillId);
seckillOrder.setSellerId(seckillGoods.getSellerId());
seckillOrder.setUserId(userId);//設置用戶ID
seckillOrder.setStatus("0");//狀態
redisTemplate.boundHashOps("seckillOrder").put(userId, seckillOrder);
}
秒殺支付
用戶成功下單後,跳轉到支付頁面。支付頁顯示微信支付二維碼。用戶完成支付後,保存訂單到數據庫。
1.生成二維碼serviceimpl
@RequestMapping("/createNative")
public Map createNative(){
//獲取當前用戶
String userId=SecurityContextHolder.getContext().getAuthentication().getName();
//到redis查詢秒殺訂單
TbSeckillOrder seckillOrder = seckillOrderService.searchOrderFromRedisByUserId(userId);
//判斷秒殺訂單存在
if(seckillOrder!=null){
long fen= (long)(seckillOrder.getMoney().doubleValue()*100);//金額(分)
return weixinPayService.createNative(seckillOrder.getId()+"",+fen+"");
}else{
return new HashMap();
}
}
2.支付成功保存訂單serviceImpl
@Override
public void saveOrderFromRedisToDb(String userId, Long orderId, String transactionId) {
System.out.println("saveOrderFromRedisToDb:"+userId);
//根據用戶ID查詢日誌
TbSeckillOrder seckillOrder = (TbSeckillOrder) redisTemplate.boundHashOps("seckillOrder").get(userId);
if(seckillOrder==null){
throw new RuntimeException("訂單不存在");
}
//如果與傳遞過來的訂單號不符
if(seckillOrder.getId().longValue()!=orderId.longValue()){
throw new RuntimeException("訂單不相符");
}
seckillOrder.setTransactionId(transactionId);//交易流水號
seckillOrder.setPayTime(new Date());//支付時間
seckillOrder.setStatus("1");//狀態
seckillOrderMapper.insert(seckillOrder);//保存到數據庫
redisTemplate.boundHashOps("seckillOrder").delete(userId);//從redis中清除
}
3.支付狀態查詢Controller
@RequestMapping("/queryPayStatus")
public Result queryPayStatus(String out_trade_no){
//獲取當前用戶
String userId=SecurityContextHolder.getContext().getAuthentication().getName();
Result result=null;
int x=0;
while(true){
//調用查詢接口
Map<String,String> map = weixinPayService.queryPayStatus(out_trade_no);
if(map==null){//出錯
result=new Result(false, "支付出錯");
break;
}
if(map.get("trade_state").equals("SUCCESS")){//如果成功
result=new Result(true, "支付成功");
seckillOrderService.saveOrderFromRedisToDb(userId, Long.valueOf(out_trade_no), map.get("transaction_id"));
break;
}
try {
Thread.sleep(3000);//間隔三秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//不讓循環無休止地運行定義變量,如果超過了這個值則退出循環,設置時間爲1分鐘
x++;
if(x>20){
result=new Result(false, "二維碼超時");
//1.調用微信的關閉訂單接口(學員實現)
Map<String,String> payresult = weixinPayService.closePay(out_trade_no);
if( !"SUCCESS".equals(payresult.get("result_code")) ){//如果返回結果是正常關閉
if("ORDERPAID".equals(payresult.get("err_code"))){
result=new Result(true, "支付成功");
seckillOrderService.saveOrderFromRedisToDb(userId, Long.valueOf(out_trade_no), map.get("transaction_id"));
}
}
if(result.isSuccess()==false){
System.out.println("超時,取消訂單");
//2.調用刪除
seckillOrderService.deleteOrderFromRedis(userId, Long.valueOf(out_trade_no));
}
break;
}
}
return result;
}
訂單超時處理
1.當用戶下單後5分鐘尚未付款應該釋放訂單,增加庫存serviceImpl
@Override
public void deleteOrderFromRedis(String userId, Long orderId) {
//根據用戶ID查詢日誌
TbSeckillOrder seckillOrder = (TbSeckillOrder) redisTemplate.boundHashOps("seckillOrder").get(userId);
if(seckillOrder!=null &&
seckillOrder.getId().longValue()== orderId.longValue() ){
redisTemplate.boundHashOps("seckillOrder").delete(userId);//刪除緩存中的訂單
//恢復庫存
//1.從緩存中提取秒殺商品
TbSeckillGoods seckillGoods=(TbSeckillGoods)redisTemplate.boundHashOps("seckillGoods").get(seckillOrder.getSeckillId());
if(seckillGoods!=null){
seckillGoods.setStockCount(seckillGoods.getStockCount()+1);
redisTemplate.boundHashOps("seckillGoods").put(seckillOrder.getSeckillId(), seckillGoods);//存入緩存
}
}
}
2.關閉微信訂單serviceImpl
public Map closePay(String out_trade_no) {
Map param=new HashMap();
param.put("appid", appid);//公衆賬號ID
param.put("mch_id", partner);//商戶號
param.put("out_trade_no", out_trade_no);//訂單號
param.put("nonce_str", WXPayUtil.generateNonceStr());//隨機字符串
String url="https://api.mch.weixin.qq.com/pay/closeorder";
try {
String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);
HttpClient client=new HttpClient(url);
client.setHttps(true);
client.setXmlParam(xmlParam);
client.post();
String result = client.getContent();
Map<String, String> map = WXPayUtil.xmlToMap(result);
System.out.println(map);
return map;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}