秒杀系统-service
在Dao层我们只完成了针对表的相关操作,包括写了接口方法和映射文件中的sql语句,并没有编写逻辑的代码,例如对多个Dao层方法的拼接,当我们用户成功秒杀商品时我们需要进行商品的减库存操作(调用SeckillDao接口)和增加用户明细(调用SuccessKilledDao接口),这些逻辑我们都需要在Service层完成。Dao层只进行数据的访问操作,接下来我们便进行Service层代码的编写。秒杀Service接口设计如下: (1)创建service包用于存放我们的Service接口和其实现类。 (2)创建exception包用于存放service层出现的异常,例如重复秒杀商品异常、秒杀已关闭等异常。 (3)创建dto包作为传输层, 用于完成web和service层的数据传递。 (4)创建entity包用于业务数据的封装。 service包需要的相关类名和函数名如表6-10所示。
SeckillService.java: public interface SeckillService { List<Seckill> getSeckillList(); Seckill getById(long seckillId); Exposer exportSeckillUrl(long seckillId); SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException,RepeatKillException,SeckillCloseException; SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5); } SeckillServiceImpl.java: public class SeckillServiceImpl implements SeckillService { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private SeckillDao seckillDao; @Autowired private SuccessKilledDao successKilledDao; @Autowired private RedisDao redisDao; private final String salt = "sadkfjalsdjfalksj23423^&*^&%&!EBJKH#e™£4"; @Override public List<Seckill> getSeckillList() { return seckillDao.queryAll(0, 4); } @Override public Seckill getById(long seckillId) { return seckillDao.queryById(seckillId); } @Override public Exposer exportSeckillUrl(long seckillId) { Seckill seckill = redisDao.getSeckill(seckillId); if (seckill == null) { seckill = seckillDao.queryById(seckillId); if (seckill == null) { return new Exposer(false, seckillId); } else { redisDao.putSeckill(seckill); } } 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()); } String md5 = getMD5(seckillId); return new Exposer(true, md5, seckillId); } private String getMD5(long seckillId) { String base = seckillId + "/" + salt; String md5 = DigestUtils.md5DigestAsHex(base.getBytes()); return md5; } @Override @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 insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone); if (insertCount <= 0) { throw new RepeatKillException("seckill repeated"); } else { int updateCount = seckillDao.reduceNumber(seckillId, nowTime); if (updateCount <= 0) { throw new SeckillCloseException("seckill is closed"); } else { SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone); return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKilled); } } } catch (SeckillCloseException e1) { throw e1; } catch (RepeatKillException e2) { throw e2; } catch (Exception e) { logger.error(e.getMessage(), e); throw new SeckillException("seckill inner error:" + e.getMessage()); } } @Override public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) { if (md5 == null || !md5.equals(getMD5(seckillId))) { return new SeckillExecution(seckillId, SeckillStatEnum.DATA_REWRITE); } Date killTime = new Date(); Map<String, Object> map = new HashMap<String, Object>(); map.put("seckillId", seckillId); map.put("phone", userPhone); map.put("killTime", killTime); map.put("result", null); try { seckillDao.killByProcedure(map); int result = MapUtils.getInteger(map, "result", -2); if (result == 1) { SuccessKilled sk = successKilledDao. queryByIdWithSeckill(seckillId, userPhone); return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, sk); } else { return new SeckillExecution(seckillId, SeckillStatEnum.stateOf(result)); } } catch (Exception e) { logger.error(e.getMessage(), e); return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR); } } } 在dto包中创建Exposer.java,用于封装秒杀的地址信息。SeckillExecution.java,用于判断秒杀是否成功,成功就返回秒杀成功的所有信息(包括秒杀的商品id、秒杀成功状态、成功信息、用户明细),失败就抛出一个我们允许的异常(重复秒杀异常、秒杀结束异常)。 Exposer.java: public class Exposer { private boolean exposed; private String md5; private long seckillId; private long now; private long start; private long end; @Override public String toString() { return "Exposer{" + "exposed=" + exposed + ", md5='" + md5 + '\'' + ", seckillId=" + seckillId + ", now=" + now + ", start=" + start + ", end=" + end + '}'; } public Exposer(boolean exposed, String md5, long seckillId) { this.exposed = exposed; this.md5 = md5; this.seckillId = seckillId; } public Exposer(boolean exposed, long seckillId, long now, long start, long end) { this.exposed = exposed; this.seckillId = seckillId; this.now = now; this.start = start; this.end = end; } public Exposer(boolean exposed, long seckillId) { this.exposed = exposed; this.seckillId = seckillId; } 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; } } SeckillExecution.java: public class SeckillExecution { private long seckillId; private int state; private String stateInfo; private SuccessKilled successKilled; @Override public String toString() { return "SeckillExecution{" + "seckillId=" + seckillId + ", state=" + state + ", stateInfo='" + stateInfo + '\'' + ", successKilled=" + successKilled + '}'; } public SeckillExecution(long seckillId, SeckillStatEnum statEnum, SuccessKilled successKilled) { this.seckillId = seckillId; this.state = statEnum.getState(); this.stateInfo = statEnum.getStateInfo(); this.successKilled = successKilled; } public SeckillExecution(long seckillId, SeckillStatEnum statEnum) { this.seckillId = seckillId; this.state = statEnum.getState(); this.stateInfo = statEnum.getStateInfo(); } public long getSeckillId() { return seckillId; } public void setSeckillId(long seckillId) { this.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; } } 然后需要在exception包下创建我们在秒杀业务过程中允许的异常,RepeatKillException.java用于处理重复的秒杀异常;SeckillCloseException.java用于处理秒杀关闭异常;SeckillException.java用于处理秒杀相关业务的异常。 RepeatKillException.java: public class RepeatKillException extends SeckillException { public RepeatKillException(String message) { super(message); } public RepeatKillException(String message, Throwable cause) { super(message, cause); } } SeckillCloseException.java: public class SeckillCloseException extends SeckillException { public SeckillCloseException(String message) { super(message); } public SeckillCloseException(String message, Throwable cause) { super(message, cause); } } SeckillException.java: public class SeckillException extends RuntimeException { public SeckillException(String message) { super(message); } public SeckillException(String message, Throwable cause) { super(message, cause); } } 在实现类SeckillServiceImpl.java中,我们用枚举的方式将异常函数中返回的常量进行封装,在enums包下创建一个枚举类型SeckillStatEnum.java,用于返回state和stateInfo这两个参数的相关数据。 SeckillStatEnum.java: public enum SeckillStatEnum { SUCCESS(1,"秒杀成功"), END(0,"秒杀结束"), REPEAT_KILL(-1,"重复秒杀"), INNER_ERROR(-2,"系统异常"), DATA_REWRITE(-3,"数据篡改"); private int state; private String stateInfo; SeckillStatEnum(int state, String stateInfo) { this.state = state; this.stateInfo = stateInfo; } public int getState() { return state; } public String getStateInfo() { return stateInfo; } public static SeckillStatEnum stateOf(int index) { for (SeckillStatEnum state : values()) { if (state.getState() == index) { return state; } } return null; } } 使用Spring托管Service依赖配置: 在spring包下创建一个spring-service.xml文件,然后采用注解的方式将Service的实现类加入到Spring IOC容器中,然后在Service实现类的方法中,在需要进行事务声明的方法上加上事务的注解,配置如下。 spring-service.xml: <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="org.seckill.service"/> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>