上篇文章介紹了秒殺的dao 這邊將介紹秒殺的業(yè)務(wù)邏輯代碼扇商。主要有統(tǒng)一異常的控制毒姨,統(tǒng)一的枚舉表示秒殺的狀態(tài),秒殺的業(yè)務(wù)邏輯溶浴,通用返回乍迄。
首先定義一個枚舉,表示秒殺的狀態(tài)
一.枚舉定義
public enum SeckillStatEnum {
SUCCESS(1,"秒殺成功"),
END(0,"秒殺結(jié)束"),
REPEAT_KILL(-1,"重復(fù)秒殺"),
INNER_KILL(-2,"系統(tǒng)異常"),
DATA_REWRITE(-3,"數(shù)據(jù)篡改"),
;
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;
}
}
接下來設(shè)置三個異常 主要是重復(fù)秒殺異常士败,秒殺結(jié)束異常闯两,以及秒殺異常。
二.異常定義
1.秒殺異常
public class SeckillException extends RuntimeException{
public SeckillException(String message) {
super(message);
}
public SeckillException(String message,Throwable cause) {
super(message,cause);
}
}
2.重復(fù)秒殺異常
public class RepeatKillException extends SeckillException{
public RepeatKillException(String message) {
super(message);
}
public RepeatKillException(String message,Throwable cause) {
super(message,cause);
}
}
3.秒殺結(jié)束異常
public class SeckillCloseException extends SeckillException{
public SeckillCloseException(String message) {
super(message);
}
public SeckillCloseException(String message,Throwable cause) {
super(message,cause);
}
}
三.返回值定義
本項目需要兩種返回類型谅将,一種是秒殺接口暴露漾狼,另一種是秒殺結(jié)果。
1.秒殺接口暴露的返回值
public class Exposer {
//是否開其秒殺
private boolean exposed;
private String md5;
private long seckillId;
//當(dāng)前時間
private long now;
//開啟時間
private long start;
//結(jié)束時間
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.秒殺結(jié)果
public class SeckillExecution {
private long SeckillId;
//秒殺狀態(tài)
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接口主要有四個方法饥臂,秒殺列表查詢逊躁,單個商品信息查詢,秒殺地址暴露擅笔,秒殺執(zhí)行志衣。
public interface SeckillService {
/**
* 查詢所有秒殺的記錄
* @return
*/
public List<Seckill> getScekillList();
/**
* 查詢耽擱秒殺
*/
public Seckill getSeckillById(long seckillId);
/**
* 秒殺借口 秒殺開啟時輸出地址否則輸出系統(tǒng)時間和接口時間
*/
public Exposer exportSeckillUrl(long seckillId);
/**
* 執(zhí)行秒殺
*/
SeckillExecution executeSeckill(long seckillId,long userPhone ,String md5)
throws SeckillException,RepeatKillException,SeckillCloseException;
}
2.接口實現(xiàn)
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);
}
/**
* 秒殺借口 秒殺開啟時輸出地址否則輸出系統(tǒng)時間和接口時間
*/
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);
}
}
}
/**
* 執(zhí)行秒殺
*/
@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");
}
//執(zhí)行秒殺
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) {
//重復(fù)秒殺
//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;
}
}
由于需要執(zhí)行減庫存以及插入秒殺記錄的造作需要事務(wù)這里我們用注解的方式來控制事務(wù)。注解事務(wù)主要有如下的優(yōu)點猛们。
(1).開發(fā)團隊達成一致約定念脯,明確標(biāo)注事務(wù)的編程風(fēng)格。
(2).保證事務(wù)的執(zhí)行時間盡可能的短弯淘,不要穿插其他網(wǎng)絡(luò)操作如RPC/HTTP請求或者剝離到事務(wù)的外圍绿店。
(3).不是所有的方法都需要事務(wù),只有一條記錄要修改庐橙,只讀操作不需要事務(wù)假勿。
五.單元測試
編寫單元測試的方法測試所寫的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);
}
// 測試代碼完整邏輯,注意可重復(fù)執(zhí)行
@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