zoukankan      html  css  js  c++  java
  • 高并发秒杀系统--Service接口设计与实现

    [DAO编写之后的总结]

    DAO层    -->    接口设计 + SQL编写

    DAO拼接等逻辑    -->    统一在Service层完成

    [Service层的接口设计]

    1.接口设计原则:站在'使用者'的角度设计接口

    2.方法定义粒度:从'使用者'的行为角度来思考-- 减库存+插入购买明细 --> 执行秒杀

    3.参数:越简练越直接

    4.返回类型:return 类型/Exception 返回类型要友好,使用DTO

    package org.azcode.service;
    
    import org.azcode.dto.Exposer;
    import org.azcode.dto.SeckillExecution;
    import org.azcode.entity.Seckill;
    import org.azcode.exception.RepeatKillException;
    import org.azcode.exception.SeckillCloseException;
    import org.azcode.exception.SeckillException;
    
    import java.util.List;
    
    /**
     * Created by azcod on 2017/4/15.
     */
    public interface SeckillService {
    
        /**
         * 查询所有的秒杀记录
         * @return
         */
        List<Seckill> getSeckillList();
    
        /**
         * 根据id查询单个秒杀记录
         * @param seckillId
         * @return
         */
        Seckill getById(long seckillId);
    
        /**
         * 秒杀开启时输出秒杀接口地址,
         * 否则输出系统时间和秒杀时间
         * @param seckillId
         * @return Exposer
         */
        Exposer exportSeckillUrl(long seckillId);
    
        /**
         * 执行秒杀操作
         * @param seckillId
         * @param userPhone
         * @param md5
         * @return SeckillExecution
         */
        SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
            throws SeckillException,RepeatKillException,SeckillCloseException;
    }

    [Service接口实现编码]

    1.防止数据篡改的措施,使用MD5给数据加密

    2.具体业务逻辑的编码,对异常的处理

    3.异常的多重catch,将编译期异常转换为运行期异常

    4.使用枚举表述常量数据字典

    package org.azcode.service.impl;
    
    import org.azcode.dao.SeckillDao;
    import org.azcode.dao.SuccessKilledDao;
    import org.azcode.dto.Exposer;
    import org.azcode.dto.SeckillExecution;
    import org.azcode.entity.Seckill;
    import org.azcode.entity.SuccessKilled;
    import org.azcode.enums.SeckillStateEnum;
    import org.azcode.exception.RepeatKillException;
    import org.azcode.exception.SeckillCloseException;
    import org.azcode.exception.SeckillException;
    import org.azcode.service.SeckillService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.util.DigestUtils;
    
    import java.util.Date;
    import java.util.List;
    
    /**
     * Created by azcod on 2017/4/15.
     */
    public class SeckillServiceImpl implements SeckillService {
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
        private SeckillDao seckillDao;
    
        private SuccessKilledDao successKilledDao;
    
        //MD5盐值字符串,用于混淆MD5
        private final String slat = "r#^*T*(&F)HHU!&@T#456465";
    
        public List<Seckill> getSeckillList() {
            return seckillDao.queryAll(0, 4);
        }
    
        public Seckill getById(long seckillId) {
            return seckillDao.queryById(seckillId);
        }
    
        public Exposer exportSeckillUrl(long seckillId) {
            Seckill seckill = seckillDao.queryById(seckillId);
            //step1:判断秒杀产品是否存在
            if (seckill == null) {
                return new Exposer(false, seckillId);
            }
            //step2:判断秒杀是否开启
            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());
            }
            //MD5:一种转换特定字符串的过程,不可逆
            String md5 = getMD5(seckillId);
            return new Exposer(true, md5, seckillId);
        }
    
        public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
                throws SeckillException, RepeatKillException, SeckillCloseException {
            //step1:校验是否篡改秒杀数据
            if (md5 == null || !md5.equals(getMD5(seckillId))) {
                throw new SeckillException("seckill data rewrite");
            }
            //step2:秒杀逻辑-减库存+插入购买明细
            Date nowTime = new Date();
            // 异常捕获的IDEA快捷键:包选代码块后->ctrl+alt+t
            try {
                //减库存
                int updateCount = seckillDao.reduceNumber(seckillId, nowTime);
                if (updateCount <= 0) {
                    //秒杀结束
                    throw new SeckillCloseException("seckill is closed");
                } else {
                    //插入购买明细
                    int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
                    if (insertCount <= 0) {
                        //重复秒杀
                        throw new RepeatKillException("seckill repeated");
                    } else {
                        //秒杀成功
                        SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
                        return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, successKilled);
                    }
                }
            } catch (SeckillCloseException e1) {
                throw e1;
            } catch (RepeatKillException e2) {
                throw e2;
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                //所有编译期异常转换为运行期异常,Spring声明式事务只支持运行期异常的回滚
                //转换的目的是为了出现编译期异常时,rollback回滚
                throw new SeckillException("seckill inner error" + e.getMessage());
            }
        }
    
        /**
         * 生成MD5的通用方法
         *
         * @param seckillId
         * @return
         */
        private String getMD5(long seckillId) {
            String base = seckillId + slat;
            String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
            return md5;
        }
    }
    package org.azcode.enums;
    
    /**
     * 使用枚举表述常量数据字典
     * Created by azcod on 2017/4/15.
     */
    public enum SeckillStateEnum {
        SUCCESS(1, "秒杀成功"),
        END(0, "秒杀结束"),
        REPEAT_KILL(-1, "重复秒杀"),
        INNER_ERROR(-2, "系统异常"),
        DATA_REWRITE(-3, "数据篡改");
    
        private int state;
    
        private String stateInfo;
    
        SeckillStateEnum(int state, String stateInfo) {
            this.state = state;
            this.stateInfo = stateInfo;
        }
    
        public int getState() {
            return state;
        }
    
        public String getStateInfo() {
            return stateInfo;
        }
    
        public static SeckillStateEnum stateOf(int index) {
            for (SeckillStateEnum stateEnum : values()) {
                if (stateEnum.getState() == index) {
                    return stateEnum;
                }
            }
            return null;
        }
    }
  • 相关阅读:
    MTK 官方 openwrt SDK 使用
    PF_RING packet overwrites
    pycares cffi
    libevent evbuffer bug
    浮点转字符串性能比较
    重写 libev 的 EV_WIN32_HANDLE_TO_FD
    thrift TNonblockingServer 使用
    accel-pptp 部署
    boost::asio 使用 libcurl
    蜂鸟A20开发板刷 cubietruck 的 SD 卡固件
  • 原文地址:https://www.cnblogs.com/azcode/p/6714393.html
Copyright © 2011-2022 走看看