美文网首页
高并发秒杀API之Service

高并发秒杀API之Service

作者: 意浅离殇 | 来源:发表于2017-10-15 20:50 被阅读0次

    上篇文章介绍了秒杀的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

    相关文章

      网友评论

          本文标题:高并发秒杀API之Service

          本文链接:https://www.haomeiwen.com/subject/uuthuxtx.html