接上一节(1)
spring/spring-service.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- 扫描servie包下所有使用注解的类型 -->
<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>
org.seckill.enums.SeckillStateEnum:
package org.seckill.enums;
/**
* 使用枚举表述常量数据字段
* Created by tianjun on 2016/8/4.
*/
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.stateInfo = stateInfo;
this.state = state;
}
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 static SeckillStateEnum stateOf(int index){
for (SeckillStateEnum state : values()){
if(state.getState() == index ){
return state;
}
}
return null;
}
}
org.seckill.exception.SeckillExpection:
package org.seckill.exception;
/**
* 秒杀相关业务异常
* Created by tianjun on 2016/8/4.
*/
public class SeckillExpection extends RuntimeException {
public SeckillExpection(String message) {
super(message);
}
public SeckillExpection(String message, Throwable cause) {
super(message, cause);
}
}
org.seckill.exception.RepeatKillException:
package org.seckill.exception;
/**
* 重复秒杀异常(运行期异常)
* Created by tianjun on 2016/8/4.
*/
public class RepeatKillException extends SeckillExpection {
public RepeatKillException(String message) {
super(message);
}
public RepeatKillException(String message, Throwable cause) {
super(message, cause);
}
}
org.seckill.exception.SeckillCloseException:
package org.seckill.exception;
/**
* 秒杀关闭异常
* Created by tianjun on 2016/8/4.
*/
public class SeckillCloseException extends SeckillExpection {
public SeckillCloseException(String message) {
super(message);
}
public SeckillCloseException(String message, Throwable cause) {
super(message, cause);
}
}
org.seckill.service.SeckillService:
package 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.SeckillExpection;
import java.util.List;
/**
* 业务接口:站在:“使用者”的角度设计接口
* 三个方面:方法的粒度,参数,返回类型(return 类型/异常)
* Created by tianjun on 2016/8/4.
*/
public interface SeckillService {
/**
* 查询所有秒杀记录
* @return
*/
List<Seckill> getSeckillList();
/**
* 查询单个秒杀记录
* @param seckillId
* @return
*/
Seckill getById(long seckillId);
/**
* 秒杀开启时输出秒杀接口地址,否则输出系统时间和秒杀时间
* @param seckillId
*/
Exposer exportSeckillUrl(long seckillId);
/**
* 执行秒杀操作
* @param seckillId
* @param userPhone
* @param md5
*/
SeckillExecution executeSeckill(long seckillId,long userPhone,String md5)
throws SeckillExpection,RepeatKillException,SeckillCloseException;
}
org.seckill.service.impl.SeckillServiceImpl:
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.entity.Seckill;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStateEnum;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillExpection;
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;
/**
*
* Created by tianjun on 2016/8/4.
*/
@Service("seckillService")
public class SeckillServiceImpl implements SeckillService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private SeckillDao seckillDao;
@Autowired
private SuccessKilledDao successKilledDao;
//md5盐值,用来混淆md5
private final String slat = "123*&*^%%^^(-9lsdjfsjflsyiyiiuiuiu";
@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 = 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;
}
@Override
@Transactional
/**
* 使用事务控制事务方法的有点:
* 1:开发团队达成一致约定,明确标注事务方法的编程格式。
* 2:保证事务方法的而执行时间尽可能短,不要穿插其他的网络操作,RPC/HTTP请求或者剥离到事务方法外部。
* 3: 不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制。
*/
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillExpection, RepeatKillException, SeckillCloseException {
if(md5 == null || !md5.equals(getMD5(seckillId))){
throw new SeckillExpection("seckill data rewrite");
}
//执行秒杀逻辑 + 记录购买行为
Date nowTime = new Date();
try {
int updateCount = seckillDao.reduceNumber(seckillId,nowTime);
if(updateCount<=0){
//没有更新到记录
throw new SeckillCloseException("seckill is close");
} else {
//记录购买行为
int insertCount = successKilledDao.insertSuccessKilled(seckillId,userPhone);
//唯一:seckillId,userPhone
if(insertCount<=0){
//重复秒杀
throw new RepeatKillException("seckill repeat");
}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事务回滚只对运行期异常起作用)
throw new SeckillExpection("seckill inner error:"+e.getMessage());
}
}
}
org.seckill.service.impl.SeckillServiceImplTest:
package org.seckill.service.impl;
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.seckill.exception.SeckillExpection;
import org.seckill.service.SeckillService;
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;
import static org.junit.Assert.*;
/**
* 测试类
* Created by tianjun on 2016/8/4.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
"classpath:spring/spring-dao.xml",
"classpath:spring/spring-service.xml"})
public class SeckillServiceImplTest {
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 = 1000L;
Seckill seckill = seckillService.getById(id);
logger.info("seckill={}",seckill);
}
//测试代码完整逻辑,注意可重复执行
@Test
public void testSeckillLogic() throws Exception {
long id = 1001L;
Exposer exposer = seckillService.exportSeckillUrl(id);
if(exposer.isExposed()){
logger.info("exposer={}",exposer);
long phone = 18867102345L;
String md5 = exposer.getMd5();
try {
SeckillExecution seckillExecution = seckillService.executeSeckill(id, phone, md5);
logger.info("result={}",seckillExecution);
} catch (RepeatKillException e) {
logger.error(e.getMessage());
} catch (SeckillCloseException e) {
logger.error(e.getMessage());
}
}else {
//秒杀未开启
logger.warn("exposer={}",exposer);
}
}
}
综上,测试通过,即Service层完成,进入controller层。