高并發(fā)秒殺API之Service

上篇文章介紹了秒殺的dao 這邊將介紹秒殺的業(yè)務(wù)邏輯代碼扇商。主要有統(tǒng)一異常的控制毒姨,統(tǒng)一的枚舉表示秒殺的狀態(tài),秒殺的業(yè)務(wù)邏輯溶浴,通用返回乍迄。
首先定義一個枚舉,表示秒殺的狀態(tài)

一.枚舉定義

public enum SeckillStatEnum {
    SUCCESS(1,"秒殺成功"),
    END(0,"秒殺結(jié)束"),
    REPEAT_KILL(-1,"重復(fù)秒殺"),
    INNER_KILL(-2,"系統(tǒng)異常"),
    DATA_REWRITE(-3,"數(shù)據(jù)篡改"),
    ;
    
    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;
    }
}

接下來設(shè)置三個異常 主要是重復(fù)秒殺異常士败,秒殺結(jié)束異常闯两,以及秒殺異常。
二.異常定義
1.秒殺異常

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

2.重復(fù)秒殺異常

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

3.秒殺結(jié)束異常

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

三.返回值定義

本項目需要兩種返回類型谅将,一種是秒殺接口暴露漾狼,另一種是秒殺結(jié)果。
1.秒殺接口暴露的返回值


public class Exposer {
    //是否開其秒殺
    private boolean exposed;
    
    private String md5;
    private long seckillId;
    //當(dāng)前時間
    private long now;
    //開啟時間
    private long start;
    //結(jié)束時間
    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.秒殺結(jié)果

public class SeckillExecution {
    private long SeckillId;
    //秒殺狀態(tài)
    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接口主要有四個方法饥臂,秒殺列表查詢逊躁,單個商品信息查詢,秒殺地址暴露擅笔,秒殺執(zhí)行志衣。

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

2.接口實現(xiàn)

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);
    }
    /**
     * 秒殺借口 秒殺開啟時輸出地址否則輸出系統(tǒng)時間和接口時間
     */
    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);
            } 
        }
            
    }
    /**
     * 執(zhí)行秒殺
     */
    @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");
        }
        //執(zhí)行秒殺
        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) {
                    //重復(fù)秒殺
                    //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;
    }
}

由于需要執(zhí)行減庫存以及插入秒殺記錄的造作需要事務(wù)這里我們用注解的方式來控制事務(wù)。注解事務(wù)主要有如下的優(yōu)點猛们。
(1).開發(fā)團隊達成一致約定念脯,明確標(biāo)注事務(wù)的編程風(fēng)格。
(2).保證事務(wù)的執(zhí)行時間盡可能的短弯淘,不要穿插其他網(wǎng)絡(luò)操作如RPC/HTTP請求或者剝離到事務(wù)的外圍绿店。
(3).不是所有的方法都需要事務(wù),只有一條記錄要修改庐橙,只讀操作不需要事務(wù)假勿。

五.單元測試

編寫單元測試的方法測試所寫的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);
    }

    // 測試代碼完整邏輯,注意可重復(fù)執(zhí)行
    @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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末态鳖,一起剝皮案震驚了整個濱河市转培,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌浆竭,老刑警劉巖浸须,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異邦泄,居然都是意外死亡删窒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門顺囊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肌索,“玉大人,你說我怎么就攤上這事特碳〕涎牵” “怎么了晕换?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長亡电。 經(jīng)常有香客問我届巩,道長,這世上最難降的妖魔是什么份乒? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任恕汇,我火速辦了婚禮,結(jié)果婚禮上或辖,老公的妹妹穿的比我還像新娘瘾英。我一直安慰自己,他們只是感情好颂暇,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布缺谴。 她就那樣靜靜地躺著,像睡著了一般耳鸯。 火紅的嫁衣襯著肌膚如雪湿蛔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天县爬,我揣著相機與錄音阳啥,去河邊找鬼。 笑死财喳,一個胖子當(dāng)著我的面吹牛察迟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播耳高,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼扎瓶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泌枪?” 一聲冷哼從身側(cè)響起概荷,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碌燕,沒想到半個月后乍赫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡陆蟆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了惋增。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叠殷。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖诈皿,靈堂內(nèi)的尸體忽然破棺而出林束,到底是詐尸還是另有隱情像棘,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布壶冒,位于F島的核電站缕题,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏胖腾。R本人自食惡果不足惜烟零,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望咸作。 院中可真熱鬧锨阿,春花似錦、人聲如沸记罚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桐智。三九已至末早,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間说庭,已是汗流浹背然磷。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留口渔,地道東北人样屠。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像缺脉,于是被迫代替她去往敵國和親痪欲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容