高并發秒殺API之Service

上篇文章介紹了秒殺的dao 這邊將介紹秒殺的業務邏輯代碼。主要有統一異常的控制,統一的枚舉表示秒殺的狀態,秒殺的業務邏輯,通用返回。
首先定義一個枚舉,表示秒殺的狀態

一.枚舉定義

public enum SeckillStatEnum {
    SUCCESS(1,"秒殺成功"),
    END(0,"秒殺結束"),
    REPEAT_KILL(-1,"重復秒殺"),
    INNER_KILL(-2,"系統異常"),
    DATA_REWRITE(-3,"數據篡改"),
    ;
    
    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
    }
    public String getStateInfo() {
        return stateInfo;
    }
    public void setStateInfo(String stateInfo) {
        this.stateInfo = stateInfo;
    }
    private int state;
    private String  stateInfo;
    private SeckillStatEnum(int state, String stateInfo) {
        this.state = state;
        this.stateInfo = stateInfo;
    }
    public static SeckillStatEnum stateInfo(int index) {
        for(SeckillStatEnum state :values()) {
            if(state.getState()==index) {
                return state;
            }
        }
        return null;
    }
}

接下來設置三個異常 主要是重復秒殺異常,秒殺結束異常,以及秒殺異常。
二.異常定義
1.秒殺異常

public class SeckillException extends RuntimeException{
    public SeckillException(String message) {
        super(message);
    }
    public SeckillException(String message,Throwable cause) {
        super(message,cause);
    }
}

2.重復秒殺異常

public class RepeatKillException extends SeckillException{
    public RepeatKillException(String message) {
        super(message);
    }
    public RepeatKillException(String message,Throwable cause) {
        super(message,cause);
    }
}

3.秒殺結束異常

public class SeckillCloseException  extends SeckillException{
    public SeckillCloseException(String message) {
        super(message);
    }
    public SeckillCloseException(String message,Throwable cause) {
        super(message,cause);
    }
}

三.返回值定義

本項目需要兩種返回類型,一種是秒殺接口暴露,另一種是秒殺結果。
1.秒殺接口暴露的返回值


public class Exposer {
    //是否開其秒殺
    private boolean exposed;
    
    private String md5;
    private long seckillId;
    //當前時間
    private long now;
    //開啟時間
    private long start;
    //結束時間
    private long end;
    public Exposer(boolean exposed, String md5, long seckillId) {
        super();
        this.exposed = exposed;
        this.md5 = md5;
        this.seckillId = seckillId;
    }
    public Exposer(boolean exposed, long seckillId) {
        super();
        this.exposed = exposed;
        this.seckillId = seckillId;
    }
    public Exposer(boolean exposed, long seckillId,long now, long start, long end) {
        super();
        this.exposed = exposed;
        this.now = now;
        this.seckillId=seckillId;
        this.start = start;
        this.end = end;
    }
    public boolean isExposed() {
        return exposed;
    }
    public void setExposed(boolean exposed) {
        this.exposed = exposed;
    }
    public String getMd5() {
        return md5;
    }
    public void setMd5(String md5) {
        this.md5 = md5;
    }
    public long getSeckillId() {
        return seckillId;
    }
    public void setSeckillId(long seckillId) {
        this.seckillId = seckillId;
    }
    public long getNow() {
        return now;
    }
    public void setNow(long now) {
        this.now = now;
    }
    public long getStart() {
        return start;
    }
    public void setStart(long start) {
        this.start = start;
    }
    public long getEnd() {
        return end;
    }
    public void setEnd(long end) {
        this.end = end;
    }
    

}

2.秒殺結果

public class SeckillExecution {
    private long SeckillId;
    //秒殺狀態
    private int state;
    private  String stateInfo;
    private SuccessKilled successKilled;
    
    public SeckillExecution(long seckillId, SeckillStatEnum statEnum) {
        super();
        SeckillId = seckillId;
        this.state = statEnum.getState();
        this.stateInfo = statEnum.getStateInfo();
    }
    public SeckillExecution(long seckillId, SeckillStatEnum statEnum, SuccessKilled successKilled) {
        super();
        SeckillId = seckillId;
        this.state = statEnum.getState();
        this.stateInfo = statEnum.getStateInfo();
        this.successKilled = successKilled;
    }
    public long getSeckillId() {
        return SeckillId;
    }
    public void setSeckillId(long seckillId) {
        SeckillId = seckillId;
    }
    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
    }
    public String getStateInfo() {
        return stateInfo;
    }
    public void setStateInfo(String stateInfo) {
        this.stateInfo = stateInfo;
    }
    public SuccessKilled getSuccessKilled() {
        return successKilled;
    }
    public void setSuccessKilled(SuccessKilled successKilled) {
        this.successKilled = successKilled;
    }
    
}

四.秒殺service

1.秒殺service接口主要有四個方法,秒殺列表查詢,單個商品信息查詢,秒殺地址暴露,秒殺執行。

public interface SeckillService {
    /**
     * 查詢所有秒殺的記錄
     * @return 
     */
    public List<Seckill> getScekillList();
    /**
     * 查詢耽擱秒殺
     */
    public Seckill getSeckillById(long seckillId);
    /**
     * 秒殺借口 秒殺開啟時輸出地址否則輸出系統時間和接口時間
     */
    public Exposer  exportSeckillUrl(long seckillId);
    /**
     * 執行秒殺
     */
    SeckillExecution  executeSeckill(long seckillId,long userPhone ,String md5)
    throws SeckillException,RepeatKillException,SeckillCloseException;
}

2.接口實現

package com.wen.seckill.service.impl;

import java.util.Date;
import java.util.List;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import com.wen.seckill.dao.SeckillDao;
import com.wen.seckill.dao.SuccessKilledDao;
import com.wen.seckill.dto.Exposer;
import com.wen.seckill.dto.SeckillExecution;
import com.wen.seckill.enums.SeckillStatEnum;
import com.wen.seckill.exception.RepeatKillException;
import com.wen.seckill.exception.SeckillCloseException;
import com.wen.seckill.exception.SeckillException;
import com.wen.seckill.model.Seckill;
import com.wen.seckill.model.SuccessKilled;
import com.wen.seckill.service.SeckillService;
@Service("seckillService")
public class SeckillServiceImpl implements  SeckillService{
    private Logger logger = (Logger) LoggerFactory.getLogger(SeckillServiceImpl.class);
    @Resource(name="seckillDao")
    private SeckillDao seckillDao;
    @Resource(name="successKilledDao")
    private SuccessKilledDao successKilledDao;
    public final String slat="dfaf23asascxcaser23ads";
    /**
     * 查詢所有秒殺的記錄
     * @return 
     */
    public List<Seckill> getSeckillList(){
        return seckillDao.queryAll();
    }
    /**
     * 查詢耽擱秒殺
     */
    public Seckill getSeckillById(long seckillId) {
        return seckillDao.queryById(seckillId);
    }
    /**
     * 秒殺借口 秒殺開啟時輸出地址否則輸出系統時間和接口時間
     */
    public Exposer  exportSeckillUrl(long seckillId) {
        Seckill seckill=seckillDao.queryById(seckillId);
        if(seckill==null) {
            return new Exposer(false,seckillId);
        }else {
            Date startTime=seckill.getStartTime();
            Date endTime=seckill.getEndTime();
            Date nowTime=new Date();
            if(nowTime.getTime()<startTime.getTime()||nowTime.getTime()>endTime.getTime()) {
                return new Exposer(false,seckillId,nowTime.getTime(),startTime.getTime(),endTime.getTime());
            }else {
                String md5=getMD5(seckillId);
                return new Exposer(true,md5,seckillId);
            } 
        }
            
    }
    /**
     * 執行秒殺
     */
    @Transactional
    public SeckillExecution  executeSeckill(long seckillId,long userPhone ,String md5)
    throws SeckillException,RepeatKillException,SeckillCloseException{
        if(md5==null||!md5.equals(getMD5(seckillId))) {
            throw new SeckillException("seckill data rewrite");
        }
        //執行秒殺
        Date nowTime=new Date();
        try {
            int updateCount=seckillDao.reduceNumber(seckillId, nowTime);
            if(updateCount<=0) {
                //沒有更新dao
                //throw new SeckillCloseException("seckill id closed");
                return new SeckillExecution(seckillId,SeckillStatEnum.FAIL_Kill);
            }else {
                //減庫存成功
                //記錄購買行為
                int insertCount=successKilledDao.insertSuccessKilled(seckillId, userPhone);
                if(insertCount<=0) {
                    //重復秒殺
                    //throw new RepeatKillException("seckill id repeated");
                    return new SeckillExecution(seckillId,SeckillStatEnum.REPEAT_KILL);
                }else {
                    
                    SuccessKilled success=successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
                    return new SeckillExecution(seckillId,SeckillStatEnum.SUCCESS,success);
                }
            }   
        }catch(Exception e) {
            e.printStackTrace();
            throw new SeckillException("seckill inner error"+e.getMessage());
        }
        
    }
    private String getMD5(long seckillId) {
        String base=seckillId+"/"+slat;
        String md5=DigestUtils.md5DigestAsHex(base.getBytes());
        return md5;
    }
}

由于需要執行減庫存以及插入秒殺記錄的造作需要事務這里我們用注解的方式來控制事務。注解事務主要有如下的優點。
(1).開發團隊達成一致約定,明確標注事務的編程風格。
(2).保證事務的執行時間盡可能的短,不要穿插其他網絡操作如RPC/HTTP請求或者剝離到事務的外圍。
(3).不是所有的方法都需要事務,只有一條記錄要修改,只讀操作不需要事務。

五.單元測試

編寫單元測試的方法測試所寫的service 類


public class SeckillServiceImplTest extends BaseTest {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private SeckillService seckillService;

    @Test
    public void testGetSeckillList() throws Exception {
        List<Seckill> list = seckillService.getSeckillList();
        logger.info("list={}", list);
    }

    @Test
    public void testGetById() throws Exception {
        long id = 1000;
        Seckill seckill = seckillService.getSeckillById(id);
        logger.info("seckill={}", seckill);
    }

    // 測試代碼完整邏輯,注意可重復執行
    @Test
    public void testSeckillLogic() throws Exception {
        long id = 1002;
        Exposer exposer = seckillService.exportSeckillUrl(id);
        if (exposer.isExposed()) {
            logger.info("exposer={}", exposer);
            long phone = 13631231234L;
            String md5 = exposer.getMd5();
            try {
                SeckillExecution execution = seckillService.executeSeckill(id, phone, md5);
                logger.info("execution={}", execution);
            } catch (RepeatKillException e) {
                logger.error(e.getMessage());
            } catch (SeckillCloseException e) {
                logger.error(e.getMessage());
            }catch (Exception e) {
                e.printStackTrace();
                logger.error(e.getMessage());
            }
        } else {
            // 秒殺未開啟
            logger.error("exposer={}", exposer);
        }
    }

    @Test
    public void testExecuteSeckillProcedure() throws Exception {
        long seckillId = 1003;
        long phone = 13631231234L;
        Exposer exposer = seckillService.exportSeckillUrl(seckillId);
        if (exposer.isExposed()) {
            String md5 = exposer.getMd5();
            SeckillExecution execution = seckillService.executeSeckill(seckillId, phone, md5);
            logger.info(execution.getStateInfo());
        }
    }

}

文章地址 :http://www.haha174.top/article/details/251844
源碼地址 :https://github.com/haha174/seckill.git
教程地址 :http://www.imooc.com/learn/631

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容