开发业务接口,一定要站在用户的角度去考虑问题。对于一个秒杀系统,我们需要以下几个接口:查询所有秒杀记录,查询单条秒杀记录,判断是否符合秒杀条件,以及秒杀逻辑执行。
package org.seckill.service;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
import org.seckill.pojo.Seckill;
import java.util.List;
// 业务接口
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;
}
执行秒杀操作是,定义了三个异常:重复秒杀异常,秒杀关闭异常以及一个父类的异常,即出去前两个异常意外的所有异常都归为这个异常。后两个方法的返回值是两个dto层的类。dto层是一个很类似于pojo的层。pojo层只关心如何对数据库的表进行映射,即如何存储数据表中的数据,不进行任何的交互操作。而dto层里的类还需要负责service层的数据交互任务。首先,定义了一个dto类Exposer。该类的作用是存储秒杀的相关信息。
package org.seckill.dto;
// 暴露秒杀地址dtp
public class Exposer {
// 是否可以开启秒杀
private boolean exposed;
// 地址加密
private String md5;
// 秒杀商品id
private long seckillId;
// 系统当前时间(mm)
private long now;
// 秒杀开启时间
private long start;
// 秒杀结束时间
private long 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;
}
}
另一个dto类是SeckillExecution。它存放着秒杀成功后系统记录的信息。
package org.seckill.dto;
import org.seckill.enums.SeckillStateEnum;
import org.seckill.pojo.SuccessKilled;
// 封装秒杀执行后结果
public class SeckillExecution {
// 秒杀商品id
private long seckillId;
// 秒杀结果状态
private int state;
// 秒杀结果状态描述
private String stateInfo;
// 秒杀成功返回秒杀成功对象
private SuccessKilled successKilled;
public SeckillExecution(long seckillId, SeckillStateEnum stateEnum, SuccessKilled successKilled) {
this.seckillId = seckillId;
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
this.successKilled = successKilled;
}
public SeckillExecution(long seckillId, SeckillStateEnum stateEnum) {
this.seckillId = seckillId;
this.state = stateEnum.getState();
this.stateInfo = stateEnum.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;
}
}
这里是用了枚举类SeckillStateEnum来存放常量数据字典:
package org.seckill.enums;
// 使用枚举表示常量数据字典
public enum SeckillStateEnum {
CLOSE(0, "秒杀结束"),
DATA_REWRITE(-3, "数据篡改"),
INNER_ERROR(-2, "系统异常"),
REPEAT(-1, "重复秒杀"),
SUCCESS(1, "秒杀成功");
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 state : SeckillStateEnum.values()) {
if (state.getState() == index) {
return state;
}
}
return null;
}
}
下面是接口的实现:
package org.seckill.service.impl;
import org.seckill.dao.SeckillDao;
import org.seckill.dao.SuccessKilledDao;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.enums.SeckillStateEnum;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
import org.seckill.pojo.Seckill;
import org.seckill.pojo.SuccessKilled;
import org.seckill.service.SeckillService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.util.Date;
import java.util.List;
@Service
public class SeckillServiceImpl implements SeckillService {
@Autowired
private SeckillDao seckillDao;
@Autowired
private SuccessKilledDao successKilledDao;
private Logger logger = LoggerFactory.getLogger(this.getClass());
// md5盐值字符串
private final String slat = "s#(&BFdjhsd(*@hbe289fNI*A@E0";
public List<Seckill> getSeckillList() {
return seckillDao.queryAll(0, 100);
}
public Seckill getById(long seckillId) {
return seckillDao.queryById(seckillId);
}
private String getMd5(long seckillId) {
String base = seckillId + "/" + slat;
String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
public Exposer exportSeckillUrl(long seckillId) {
Seckill seckill = seckillDao.queryById(seckillId);
if (seckill==null) {
return new Exposer(false, seckillId);
}
Date nowTime = new Date();
if (nowTime.getTime()<seckill.getStartTime().getTime() ||
nowTime.getTime()>seckill.getEndTime().getTime()) {
return new Exposer(false, seckillId, nowTime.getTime(),
seckill.getStartTime().getTime(), seckill.getEndTime().getTime());
}
String md5 = getMd5(seckillId);
return new Exposer(true, md5, seckillId);
}
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();
int updateCount = seckillDao.reduceNumber(seckillId, nowTime);
try {
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 e) {
throw e;
} catch (RepeatKillException e) {
throw e;
} catch (SeckillException e) {
logger.error(e.getMessage(), e);
throw new SeckillException("seckill inner error: "+e.getMessage());
}
}
}