慕课网:Java高并发秒杀API之Service层
2018-03-18 11:26
357 查看
1)秒杀Service接口设计
SeckillService.javapackage org.seckill.service;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
import java.util.List;
/**
* 业务接口:站在使用者的角度设计接口
* 三个方面:方法定义粒度,参数,返回类型(return 类型/异常)
*/
public interface SeckillService {
/**
* 查询所有秒杀记录
* @return
*/
List<Seckill> getSeckillList();
/**
* 查询单个秒杀记录
* @param seckillId
* @return
*/
Seckill getById(long seckillId);
/**
* 秒杀开启时输出秒杀接口地址,
* 否则输出系统时间和秒杀时间
* @param seckillId
* @return
*/
Exposer exportSeckillUrl(long seckillId);
/**
* 执行秒杀操作
* @param seckillId
* @param userPhone
* @param md5
* @return
*/
SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException,RepeatKillException,SeckillCloseException;
}2)秒杀接口实现
首先定义几个需要用到的异常
SeckillException.javapackage org.seckill.exception;
/**
* 秒杀相关业务异常
*/
public class SeckillException extends RuntimeException{
public SeckillException(String message) {
super(message);
}
public SeckillException(String message, Throwable cause) {
super(message, cause);
}
}SeckillCloseException.javapackage org.seckill.exception;
/**
* 秒杀关闭异常
*/
public class SeckillCloseException extends SeckillException {
public SeckillCloseException(String message) {
super(message);
}
public SeckillCloseException(String message, Throwable cause) {
super(message, cause);
}
}RepeatKillException.javapackage org.seckill.exception;
/**
* 重复秒杀异常(运行期异常)
*/
public class RepeatKillException extends SeckillException {
public RepeatKillException(String message) {
super(message);
}
public RepeatKillException(String message, Throwable cause) {
super(message, cause);
}
}定义一个枚举类型表述常量数据字段
SeckillStatEnum.javapackage org.seckill.enums;
/**
* 使用枚举表述常量数据字段
*/
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;
}
}定义两个数据传输对象(补)
Exposer.javapackage org.seckill.dto;
/**
* 暴露秒杀地址DTO
*/
public class Exposer {
//是否开启秒杀
private boolean exposed;
//加密措施
private String md5;
//秒杀id
private long seckillId;
//当前时间(毫秒)
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;
}
}SeckillExecution.javapackage org.seckill.dto;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStatEnum;
/**
* 封装执行秒杀后结果
*/
public class SeckillExecution {
private long seckillId;
//秒杀执行结果状态
private int state;
//状态表示描述
private String stateInfo;
//秒杀成功对象
private 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;
}
}
秒杀业务实现类
SeckillServiceImpl.javapackage 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.entity.Seckill;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStatEnum;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
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.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import java.util.Date;
import java.util.List;
//@Component @Service @Dao @Controller
@Service
public class SeckillServiceImpl implements SeckillService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
//注入Service依赖
@Autowired //@Resource,@Inject
private SeckillDao seckillDao;
@Autowired
private SuccessKilledDao successKilledDao;
//MD5盐值z字符串,用于混淆MD5
private final String slat = "saao*)^@nsasi)su9)S(f80a0sud<>/)9@#!@";
public List<Seckill> getSeckillList() {
return se
be4a
ckillDao.queryAll(0,4);
}
public Seckill getById(long seckillId) {
return seckillDao.queryById(seckillId);
}
public Exposer exportSeckillUrl(long seckillId) {
Seckill seckill = seckillDao.queryById(seckillId);
if(seckill == null){
return new Exposer(false, seckillId);
}
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 + "/" + slat;
String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
@Transactional
/**
* 使用注解控制事务方法的优点:
* 1:开发团队达成一致约定,明确标注事务方法的编程风格。
* 2:保证事务方法的执行时间尽可能短,不要穿插其他网络操作RPC/HTTP请求或者剥离到事务方法外部。
* 3:不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制。
*/
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 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, 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());
}
}
}
日志配置
logback.xml<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are by default assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>3)基于Spring托管Service实现类并使用声明式事务
spring-service.xml<?xml version="1.0" encoding="UTF-8"?>
<!--suppress SpringFacetInspection -->
<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"> <!-- 扫描service包下所有使用注解的类型 -->
<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>4)Service集成测试
SeckillServiceTest.javapackage org.seckill.service;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
"classpath:spring/spring-dao.xml",
"classpath:spring/spring-service.xml"})
public class SeckillServiceTest {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private SeckillService seckillService;
@Test
public void getSeckillList() throws Exception {
List<Seckill> list = seckillService.getSeckillList();
logger.info("list={}",list);
/**
09:40:26.685 [main] DEBUG org.seckill.dao.SeckillDao.queryAll - ==> Preparing: SELECT * FROM seckill AS s ORDER BY create_time DESC LIMIT ?, ?
09:40:26.749 [main] DEBUG org.seckill.dao.SeckillDao.queryAll - ==> Parameters: 0(Integer), 4(Integer)
09:40:26.785 [main] DEBUG org.seckill.dao.SeckillDao.queryAll - <== Total: 4
09:40:26.796 [main] INFO o.seckill.service.SeckillServiceTest -
list=[
Seckill{seckillId=1000, name='1000元秒杀iphone6', number=100, startTime=Sun May 22 00:00:00 CST 2016, endTime=Mon May 23 00:00:00 CST 2016, createTime=Thu Mar 08 10:50:02 CST 2018},
Seckill{seckillId=1001, name='500元秒杀iPad2', number=200, startTime=Sun May 22 00:00:00 CST 2016, endTime=Mon May 23 00:00:00 CST 2016, createTime=Thu Mar 08 10:50:02 CST 2018},
Seckill{seckillId=1002, name='300元秒杀小米4', number=300, startTime=Sun May 22 00:00:00 CST 2016, endTime=Mon May 23 00:00:00 CST 2016, createTime=Thu Mar 08 10:50:02 CST 2018},
Seckill{seckillId=1003, name='200元秒杀红米note', number=400, startTime=Sun May 22 00:00:00 CST 2016, endTime=Mon May 23 00:00:00 CST 2016, createTime=Thu Mar 08 10:50:02 CST 2018}]
*/
}
@Test
public void getById() throws Exception {
long id = 1000L;
Seckill seckill = seckillService.getById(id);
logger.info("seckill={}",seckill);
/**
09:44:57.942 [main] DEBUG org.seckill.dao.SeckillDao.queryById - ==> Preparing: SELECT * FROM seckill AS s WHERE s.seckill_id = ?
09:44:58.021 [main] DEBUG org.seckill.dao.SeckillDao.queryById - ==> Parameters: 1000(Long)
09:44:58.048 [main] DEBUG org.seckill.dao.SeckillDao.queryById - <== Total: 1
seckill=Seckill{seckillId=1000, name='1000元秒杀iphone6', number=100, startTime=Sun May 22 00:00:00 CST 2016, endTime=Mon May 23 00:00:00 CST 2016, createTime=Thu Mar 08 10:50:02 CST 2018}
*/
}
@Test
public void exportSeckillUrl() throws Exception {
long id = 1000L;
Exposer exposer = seckillService.exportSeckillUrl(id);
logger.info("exposer={}",exposer);
/**
* exposer=Exposer{exposed=false, md5='null', seckillId=1000, now=1521078799782, start=1463846400000, end=1463932800000}
* exposer=Exposer{exposed=true, md5='58d029f9c7d821d5042cf0101018c1fc', seckillId=1000, now=0, start=0, end=0}
*/
}
@Test
public void executeSeckill() throws Exception {
long id = 1000L;
long phone = 13750438714L;
String md5 = "58d029f9c7d821d5042cf0101018c1fc";
SeckillExecution execution = seckillService.executeSeckill(id,phone,md5);
logger.info("result={}",execution);
/**
*org.seckill.exception.RepeatKillException: seckill repeated
*/
}
//集成测试代码完整逻辑,注意可重复执行。
@Test
public void testSeckillLogic() throws Exception{
long id =1001;
Exposer exposer = seckillService.exportSeckillUrl(id);
if (exposer.isExposed()) {
logger.info("exposer={}",exposer);
long phone = 13750438254L;
String md5 = exposer.getMd5();
try {
SeckillExecution execution = seckillService.executeSeckill(id,phone,md5);
logger.info("result={}",execution);
} catch (RepeatKillException e) {
logger.error(e.getMessage());
} catch (SeckillCloseException e){
logger.error(e.getMessage());
}
} else {
//秒杀未开启
//exposer=Exposer{exposed=false, md5='null', seckillId=1001, now=1521080312107, start=1558454400000, end=1558540800000}
logger.warn("exposer={}",exposer);
}
}
}
SeckillService.javapackage org.seckill.service;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
import java.util.List;
/**
* 业务接口:站在使用者的角度设计接口
* 三个方面:方法定义粒度,参数,返回类型(return 类型/异常)
*/
public interface SeckillService {
/**
* 查询所有秒杀记录
* @return
*/
List<Seckill> getSeckillList();
/**
* 查询单个秒杀记录
* @param seckillId
* @return
*/
Seckill getById(long seckillId);
/**
* 秒杀开启时输出秒杀接口地址,
* 否则输出系统时间和秒杀时间
* @param seckillId
* @return
*/
Exposer exportSeckillUrl(long seckillId);
/**
* 执行秒杀操作
* @param seckillId
* @param userPhone
* @param md5
* @return
*/
SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException,RepeatKillException,SeckillCloseException;
}2)秒杀接口实现
首先定义几个需要用到的异常
SeckillException.javapackage org.seckill.exception;
/**
* 秒杀相关业务异常
*/
public class SeckillException extends RuntimeException{
public SeckillException(String message) {
super(message);
}
public SeckillException(String message, Throwable cause) {
super(message, cause);
}
}SeckillCloseException.javapackage org.seckill.exception;
/**
* 秒杀关闭异常
*/
public class SeckillCloseException extends SeckillException {
public SeckillCloseException(String message) {
super(message);
}
public SeckillCloseException(String message, Throwable cause) {
super(message, cause);
}
}RepeatKillException.javapackage org.seckill.exception;
/**
* 重复秒杀异常(运行期异常)
*/
public class RepeatKillException extends SeckillException {
public RepeatKillException(String message) {
super(message);
}
public RepeatKillException(String message, Throwable cause) {
super(message, cause);
}
}定义一个枚举类型表述常量数据字段
SeckillStatEnum.javapackage org.seckill.enums;
/**
* 使用枚举表述常量数据字段
*/
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;
}
}定义两个数据传输对象(补)
Exposer.javapackage org.seckill.dto;
/**
* 暴露秒杀地址DTO
*/
public class Exposer {
//是否开启秒杀
private boolean exposed;
//加密措施
private String md5;
//秒杀id
private long seckillId;
//当前时间(毫秒)
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;
}
}SeckillExecution.javapackage org.seckill.dto;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStatEnum;
/**
* 封装执行秒杀后结果
*/
public class SeckillExecution {
private long seckillId;
//秒杀执行结果状态
private int state;
//状态表示描述
private String stateInfo;
//秒杀成功对象
private 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;
}
}
秒杀业务实现类
SeckillServiceImpl.javapackage 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.entity.Seckill;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStatEnum;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
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.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import java.util.Date;
import java.util.List;
//@Component @Service @Dao @Controller
@Service
public class SeckillServiceImpl implements SeckillService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
//注入Service依赖
@Autowired //@Resource,@Inject
private SeckillDao seckillDao;
@Autowired
private SuccessKilledDao successKilledDao;
//MD5盐值z字符串,用于混淆MD5
private final String slat = "saao*)^@nsasi)su9)S(f80a0sud<>/)9@#!@";
public List<Seckill> getSeckillList() {
return se
be4a
ckillDao.queryAll(0,4);
}
public Seckill getById(long seckillId) {
return seckillDao.queryById(seckillId);
}
public Exposer exportSeckillUrl(long seckillId) {
Seckill seckill = seckillDao.queryById(seckillId);
if(seckill == null){
return new Exposer(false, seckillId);
}
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 + "/" + slat;
String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
@Transactional
/**
* 使用注解控制事务方法的优点:
* 1:开发团队达成一致约定,明确标注事务方法的编程风格。
* 2:保证事务方法的执行时间尽可能短,不要穿插其他网络操作RPC/HTTP请求或者剥离到事务方法外部。
* 3:不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制。
*/
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 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, 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());
}
}
}
日志配置
logback.xml<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are by default assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>3)基于Spring托管Service实现类并使用声明式事务
spring-service.xml<?xml version="1.0" encoding="UTF-8"?>
<!--suppress SpringFacetInspection -->
<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"> <!-- 扫描service包下所有使用注解的类型 -->
<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>4)Service集成测试
SeckillServiceTest.javapackage org.seckill.service;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
"classpath:spring/spring-dao.xml",
"classpath:spring/spring-service.xml"})
public class SeckillServiceTest {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private SeckillService seckillService;
@Test
public void getSeckillList() throws Exception {
List<Seckill> list = seckillService.getSeckillList();
logger.info("list={}",list);
/**
09:40:26.685 [main] DEBUG org.seckill.dao.SeckillDao.queryAll - ==> Preparing: SELECT * FROM seckill AS s ORDER BY create_time DESC LIMIT ?, ?
09:40:26.749 [main] DEBUG org.seckill.dao.SeckillDao.queryAll - ==> Parameters: 0(Integer), 4(Integer)
09:40:26.785 [main] DEBUG org.seckill.dao.SeckillDao.queryAll - <== Total: 4
09:40:26.796 [main] INFO o.seckill.service.SeckillServiceTest -
list=[
Seckill{seckillId=1000, name='1000元秒杀iphone6', number=100, startTime=Sun May 22 00:00:00 CST 2016, endTime=Mon May 23 00:00:00 CST 2016, createTime=Thu Mar 08 10:50:02 CST 2018},
Seckill{seckillId=1001, name='500元秒杀iPad2', number=200, startTime=Sun May 22 00:00:00 CST 2016, endTime=Mon May 23 00:00:00 CST 2016, createTime=Thu Mar 08 10:50:02 CST 2018},
Seckill{seckillId=1002, name='300元秒杀小米4', number=300, startTime=Sun May 22 00:00:00 CST 2016, endTime=Mon May 23 00:00:00 CST 2016, createTime=Thu Mar 08 10:50:02 CST 2018},
Seckill{seckillId=1003, name='200元秒杀红米note', number=400, startTime=Sun May 22 00:00:00 CST 2016, endTime=Mon May 23 00:00:00 CST 2016, createTime=Thu Mar 08 10:50:02 CST 2018}]
*/
}
@Test
public void getById() throws Exception {
long id = 1000L;
Seckill seckill = seckillService.getById(id);
logger.info("seckill={}",seckill);
/**
09:44:57.942 [main] DEBUG org.seckill.dao.SeckillDao.queryById - ==> Preparing: SELECT * FROM seckill AS s WHERE s.seckill_id = ?
09:44:58.021 [main] DEBUG org.seckill.dao.SeckillDao.queryById - ==> Parameters: 1000(Long)
09:44:58.048 [main] DEBUG org.seckill.dao.SeckillDao.queryById - <== Total: 1
seckill=Seckill{seckillId=1000, name='1000元秒杀iphone6', number=100, startTime=Sun May 22 00:00:00 CST 2016, endTime=Mon May 23 00:00:00 CST 2016, createTime=Thu Mar 08 10:50:02 CST 2018}
*/
}
@Test
public void exportSeckillUrl() throws Exception {
long id = 1000L;
Exposer exposer = seckillService.exportSeckillUrl(id);
logger.info("exposer={}",exposer);
/**
* exposer=Exposer{exposed=false, md5='null', seckillId=1000, now=1521078799782, start=1463846400000, end=1463932800000}
* exposer=Exposer{exposed=true, md5='58d029f9c7d821d5042cf0101018c1fc', seckillId=1000, now=0, start=0, end=0}
*/
}
@Test
public void executeSeckill() throws Exception {
long id = 1000L;
long phone = 13750438714L;
String md5 = "58d029f9c7d821d5042cf0101018c1fc";
SeckillExecution execution = seckillService.executeSeckill(id,phone,md5);
logger.info("result={}",execution);
/**
*org.seckill.exception.RepeatKillException: seckill repeated
*/
}
//集成测试代码完整逻辑,注意可重复执行。
@Test
public void testSeckillLogic() throws Exception{
long id =1001;
Exposer exposer = seckillService.exportSeckillUrl(id);
if (exposer.isExposed()) {
logger.info("exposer={}",exposer);
long phone = 13750438254L;
String md5 = exposer.getMd5();
try {
SeckillExecution execution = seckillService.executeSeckill(id,phone,md5);
logger.info("result={}",execution);
} catch (RepeatKillException e) {
logger.error(e.getMessage());
} catch (SeckillCloseException e){
logger.error(e.getMessage());
}
} else {
//秒杀未开启
//exposer=Exposer{exposed=false, md5='null', seckillId=1001, now=1521080312107, start=1558454400000, end=1558540800000}
logger.warn("exposer={}",exposer);
}
}
}
相关文章推荐
- 慕课网:Java高并发秒杀API之业务分析与DAO层(1)--创建项目和依赖
- 慕课网-java高并发秒杀api之web层-总结
- 2017.4.26 慕课网--Java 高并发秒杀API(一)
- 2017.4.26 慕课网--Java 高并发秒杀API(一)
- 2017.4.26 慕课网--Java 高并发秒杀API配置文件(持续更新)
- Java高并发秒杀API之service层实现(二)
- 慕课网-java高并发秒杀api之高并发优化-总结
- 慕课网:Java高并发秒杀API之Web层
- Java高并发秒杀API(二)之Service层
- 慕课网:Java高并发秒杀API之业务分析与DAO层(2)--秒杀业务分析
- 慕课网:Java高并发秒杀API之业务分析与DAO层(3)--DAO层设计与开发
- 2017.4.26 慕课网--Java 高并发秒杀API配置文件(持续更新)
- Java高并发秒杀API之web层实现(三)
- Java高并发秒杀API之业务分析与DAO层
- 三、高并发秒杀API之Service层设计与实现
- SSM实战项目——Java高并发秒杀API
- Java高并发秒杀API -- 业务分析与DAO层
- Java高并发秒杀API(一)之业务分析与DAO层
- #Java 高并发秒杀API 笔记
- Java高并发秒杀API(三)之Web层