高并发秒杀系统--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;
    }
}

 

posted @ 2017-04-15 15:40  AZcode  阅读(1270)  评论(0编辑  收藏  举报