高并发秒杀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阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容