SSM框架高并发和商品秒杀项目(IDEA)(二)

(二)Java高并发秒杀API之Service层

个人博客:(二)Java高并发秒杀API之Service层
首先在编写Service层代码前,我们应该首先要知道这一层到底时干什么的,这里摘取来自ITEYE一位博主的原话

Service层主要负责业务模块的逻辑应用设计。同样是首先设计接口,再设计其实现的类,接着再Spring的配置文件中配置其实现的关联。这样我们就可以在应用中调用Service接口来进行业务处理。Service层的业务实现,具体要调用到已定义的DAO层的接口,封装Service层的业务逻辑有利于通用的业务逻辑的独立性和重复利用性,程序显得非常简洁。

在项目中要降低耦合的话,分层是一种很好的概念,就是各层各司其职,尽量不做不相干的事,所以Service层的话顾名思义就是业务逻辑,处理程序中的一些业务逻辑,以及调用DAO层的代码,这里我们的DAO层就是连接数据库的那一层,调用关系可以这样表达:
View(页面)>Controller(控制层)>Service(业务逻辑)>Dao(数据访问)>Database(数据库)

  • 首先还是接口的设计,设计Service秒杀商品的接口 SeckillService

首先在org.seckill包下建立service这个包,这个包里面存放Service相关的接口,然后建立SeckillService接口文件,代码如下:


/**
* 业务接口:站在“使用者”角度设计接口
* 三个方面:方法定义粒度,参数,返回类型(return)
*/
public interface SeckillService {

  /**
   * 查询全部的秒杀记录.
   *
   * @return 数据库中所有的秒杀记录
   */
  List<Seckill> getSeckillList();

  /**
   * 查询单个秒杀记录
   *
   * @param seckillId 秒杀记录的ID
   * @return 根据ID查询出来的记录信息
   */
  Seckill getById(long seckillId);

  /**
   * 在秒杀开启时输出秒杀接口的地址,否则输出系统时间跟秒杀时间
   *
   * @param seckillId 秒杀商品Id
   * @return 根据对应的状态返回对应的状态实体
   */
  Exposer exportSeckillUrl(long seckillId);

  /**
   * 执行秒杀操作,有可能是失败的,失败我们就抛出异常
   *
   * @param seckillId 秒杀的商品ID
   * @param userPhone 手机号码
   * @param md5       md5加密值
   * @return 根据不同的结果返回不同的实体信息
   * SeckillException 秒杀出错
   * RepeatKillException 重复秒杀异常
   * SeckillCloseException 秒杀已经关闭异常
   */
  SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException;
}
  

建立后接口之后我们要写实现类了,在写实现类的时候我们肯定会碰到一个这样的问题,你要向前端返回json数据的话,你是返回什么样的数据好?直接返回一个数字状态码或者时文字?这样设计肯定是不好的,所以我们应该向前段返回一个实体信息json,里面包含了一系列的信息,无论是哪种状态都应该可以应对,既然是与数据库字段无关的类,那就不是PO了,所以我们建立一个DTO数据传输类,关于常见的几种对象我的解释如下:

  • PO: 也就是我们在为每一张数据库表写一个实体的类
  • VO, 对某个页面或者展现层所需要的数据,封装成一个实体类
  • BO, 就是业务对象,我也不是很了解
  • DTO, 跟VO的概念有点混淆,也是相当于页面需要的数据封装成一个实体类
  • POJO, 简单的无规则java对象

org.seckill下建立dto包,然后建立Exposer类,这个类是秒杀时数据库那边处理的结果的对象

package org.seckill.dto;

/**
* 暴露秒杀地址DTO
*/
public class Exposer {
  /*是否开启秒杀 */
  private boolean exposed;
  /*  对秒杀地址进行加密措施  */
  private String md5;
  /* id为seckillId的商品秒杀地址   */
  private long seckillId;
  /* 系统当前的时间   */
  private long now;
  /* 秒杀开启的时间   */
  private long start;
  /*  秒杀结束的时间  */
  private long end;

  public Exposer() {
  }

  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;
  }

  @Override
  public String toString() {
      return "Exposer{" +
              "秒杀状态=" + exposed +
              ", md5加密值='" + md5 + '\'' +
              ", 秒杀ID=" + seckillId +
              ", 当前时间=" + now +
              ", 开始时间=" + start +
              ", 结束=" + end +
              '}';
  }
}

然后我们给页面返回的数据应该是更加友好的封装数据,所以我们再在org.seckill.dto包下再建立SeckillExecution用来封装给页面的结果:

/**
 * 封装秒杀执行后结果
 */
public class SeckillExecution {

    private long seckillId;
    /* 执行秒杀结果的状态   */
    private int state;
    /* 状态的明文标示   */
    private String stateInfo;
    /*  当秒杀成功时,需要传递秒杀结果的对象回去  */
    private SuccessKilled successKilled;

    /*  秒杀成功返回的实体  */
    public SeckillExecution(long seckillId, int state, String stateInfo, SuccessKilled successKilled) {
        this.seckillId = seckillId;
        this.state = state;
        this.stateInfo = stateInfo;
        this.successKilled = successKilled;
    }

    /*  秒杀失败返回的实体  */
    public SeckillExecution(long seckillId, int state, String stateInfo) {
        this.seckillId = seckillId;
        this.state = state;
        this.stateInfo = stateInfo;
    }

    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;
    }

    @Override
    public String toString() {
        return "SeckillExecution{" +
                "秒杀的商品ID=" + seckillId +
                ", 秒杀状态=" + state +
                ", 秒杀状态信息='" + stateInfo + '\'' +
                ", 秒杀的商品=" + successKilled +
                '}';
    }
}
定义秒杀中可能会出现的异常
  • 定义一个基础的异常,所有的子异常继承这个异常SeckillException
/**
 * 秒杀基础异常
 */
public class SeckillException extends RuntimeException {
    public SeckillException(String message) {
        super(message);
    }

    public SeckillException(String message, Throwable cause) {
        super(message, cause);
    }
}
  • 首选可能会出现秒杀关闭后被秒杀情况,所以建立秒杀关闭异常SeckillCloseException,需要继承我们一开始写的基础异常
/**
 * 秒杀已经关闭异常(运行时异常),当秒杀结束就会出现这个异常
 */
public class SeckillCloseException extends SeckillException {
    public SeckillCloseException(String message) {
        super(message);
    }

    public SeckillCloseException(String message, Throwable cause) {
        super(message, cause);
    }
}
  • 然后还有可能发生重复秒杀异常RepeatKillException

/**
 * 重复秒杀异常(运行期异常),不需要我们手动去try catch
 */
public class RepeatKillException extends SeckillException {
    public RepeatKillException(String message) {
        super(message);
    }

    public RepeatKillException(String message, Throwable cause) {
        super(message, cause);
    }
}
实现Service接口
@Service
public class SeckillServiceImpl implements SeckillService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());
    //注入service依赖
    @Autowired
    private SeckillDao seckillDao;
    @Autowired
    private SuccessKilledDao successKilledDao;
    /* 加入一个md5盐值字符串,用于混淆*/
    private final String slat = "thisIsASaltValue";

    /**
     * 查询全部的秒杀记录.
     *
     * @return 数据库中所有的秒杀记录
     */
    @Override
    public List<Seckill> getSeckillList() {
        return seckillDao.queryAll(0, 4);
    }

    /**
     * 查询单个秒杀记录
     *
     * @param seckillId 秒杀记录的ID
     * @return 根据ID查询出来的记录信息
     */
    @Override
    public Seckill getById(long seckillId) {
        return seckillDao.queryById(seckillId);
    }

    /**
     * 在秒杀开启时输出秒杀接口的地址,否则输出系统时间跟秒杀地址
     *
     * @param seckillId 秒杀商品Id
     * @return 根据对应的状态返回对应的状态实体
     */
    @Override
    public Exposer exportSeckillUrl(long seckillId) {
        Seckill seckill = seckillDao.queryById(seckillId);
        //为空
        if (seckill == null) {
            return new Exposer(true, 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;//通过加上混淆的md5不能被别人破解
        String md5 = DigestUtils.md5DigestAsHex(base.getBytes());//加密
        return md5;
    }

    /**
     * 执行秒杀操作,失败的,失败我们就抛出异常
     *
     * @param seckillId 秒杀的商品ID
     * @param userPhone 手机号码
     * @param md5       md5加密值
     * @return 根据不同的结果返回不同的实体信息
     */
    @Override
    @Transactional
    /**
     * 使用注解控制事务方法的优点:
     * 1.开发团队达成一致约定,明确标注事务方法的编程风格
     * 2.保证事务方法的执行时间尽可能短,不要穿插其他的网络操作,RPC/HTTP请求或者剥离到事务方法外部
     * 3.不是所有的方法都需要事务。如只有一条修改操作,只读操作不需要事务控制
     */
    public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException {
        try {
            if (md5 == null || !md5.equals(getMD5(seckillId))) {
                logger.error("秒杀数据被篡改");
                throw new SeckillException("seckill data rewrite");
            }
            //执行秒杀逻辑:减库存+记录购买行为
            Date nowTime = new Date();
            //减库存
            int updateCount = seckillDao.reduceNumber(seckillId, nowTime);
            if (updateCount <= 0) {
                logger.warn("没有更新数据库记录,说明秒杀结束");
                throw new SeckillCloseException("seckill is closed");
            } else {
                //记录购买行为
                int inserCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
                //inserCount为0时冲突,重复秒杀
                if (inserCount <= 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);
            //把编译期异常转换为运行时异常
            //rollback回滚
            throw new SeckillException("seckill inner error : " + e.getMessage());
        }
    }
}

在这里我们捕获了运行时异常,这样做的原因就是Spring的事物默认就是发生了RuntimeException才会回滚,可以检测出来的异常是不会导致事物的回滚的,这样的目的就是你明知道这里会发生异常,所以你一定要进行处理.如果只是为了让编译通过的话,那捕获异常也没多意思,所以这里要注意事物的回滚.

然后我们还发现这里存在硬编码的现象,就是返回各种字符常量,例如秒杀成功,秒杀失败等等,这些字符串时可以被重复使用的,而且这样维护起来也不方便,要到处去类里面寻找这样的字符串,所有我们使用枚举类来管理这样状态,在con.suny包下建立enum包,专门放置枚举类,然后再建立SeckillStatEnum枚举类:

/**
 * 常量枚举类
 */
public enum SeckillStatEnum {
    SUCCESS(1, "秒杀成功"),
    END(0, "秒杀结束"),
    REPEAT_KILL(-1, "重复秒杀"),
    INNER_ERROR(-2, "系统异常"),
    DATE_REWRITE(-3, "数据篡改");

    private int state;
    private String info;

    SeckillStatEnum() {
    }

    SeckillStatEnum(int state, String info) {
        this.state = state;
        this.info = info;
    }

    public int getState() {
        return state;
    }

    public String getInfo() {
        return info;
    }

    public static SeckillStatEnum stateOf(int index) {
        for (SeckillStatEnum statEnum : values()) {
            if (statEnum.getState() == index) {
                return statEnum;
            }
        }
        return null;
    }
}

既然把这些改成了枚举,那么在SeckillServiceImpl类中的executeSeckill方法中成功秒杀的返回值就应该修改为

return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKilled);

改了这里以后会发现会报错,因为在实体类那边构造函数可不是这样的,然后修改SeckillExecution类的构造函数,把statestateInfo的值设置从构造函数里面的SeckillStatEnum中取出值来设置:


/*  秒杀成功返回的实体  */
    public SeckillExecution(long seckillId, SeckillStatEnum statEnum, SuccessKilled successKilled) {
        this.seckillId = seckillId;
        this.state = statEnum.getState();
        this.stateInfo = statEnum.getInfo();
        this.successKilled = successKilled;
    }

    /*  秒杀失败返回的实体  */
    public SeckillExecution(long seckillId, SeckillStatEnum statEnum) {
        this.seckillId = seckillId;
        this.state = statEnum.getState();
        this.stateInfo = statEnum.getInfo();
    }

业务对象依赖图

[外链图片转存失败(img-uBjnAVPc-1566365498400)(https://raw.githubusercontent.com/DiCaprio17/DiCaprio17.github.io/master/img/2019-08-20-SSM框架高并发和商品秒杀项目(IDEA)-1.png)]

为什么要用IOC

[外链图片转存失败(img-XFS5bzsG-1566365498401)(https://raw.githubusercontent.com/DiCaprio17/DiCaprio17.github.io/master/img/2019-08-20-SSM框架高并发和商品秒杀项目(IDEA)-2.png)]
[外链图片转存失败(img-pn1f8wio-1566365498402)(https://raw.githubusercontent.com/DiCaprio17/DiCaprio17.github.io/master/img/2019-08-20-SSM框架高并发和商品秒杀项目(IDEA)-3.png)]
[外链图片转存失败(img-0v3MeNaM-1566365498406)(https://raw.githubusercontent.com/DiCaprio17/DiCaprio17.github.io/master/img/2019-08-20-SSM框架高并发和商品秒杀项目(IDEA)-4.png)]

下一步肯定要注入Service了

首先在resources/spring下建立spring-service.xml文件,用来配置Service层的相关代码:

<?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">

    <!--配置自动扫描service包下的注解,在这里配置了自动扫描后,org.seckill.service包下所有带有@Service注解的类都会被加入Spring容器中-->
    <context:component-scan base-package="org.seckill.service"/>

    <!--配置事务管理器,这里时使用基于注解的事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据库连接池-->
        <!--此时dataSource存在于mapper下的DAO的xml文件中,引入-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--开启基于注解的声明式事务
        默认使用注解来管理事务行为-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

在这里开启了基于注解的事物,常见的事物操作有以下几种方法

  • 在Spring早期版本中是使用ProxyFactoryBean+XMl方式来配置事物.
  • 在Spring配置文件使用tx:advice+aop命名空间,好处就是一次配置永久生效,你无须去关心中间出的问题,不过出错了你很难找出来在哪里出了问题
  • 注解@Transactional的方式,注解可以在方法定义,接口定义,类定义,public方法上,但是不能注解在private,final,static等方法上,因为Spring的事物管理默认是使用Cglib动态代理的:
    • private方法因为访问权限限制,无法被子类覆盖
    • final方法无法被子类覆盖
    • static时类级别的方法,无法被子类覆盖
    • protected方法可以被子类覆盖,因此可以被动态字节码增强
不能被Spring AOP事物增强的方法
序号动态代理策略不能被事物增强的方法
1基于接口的动态代理出了public以外的所有方法,并且 public static 的方法也不能被增强
2基于Cglib的动态代理private,static,final的方法

然后你要在Service类上添加注解@Service,不用在接口上添加注解:

@Service
public class SeckillServiceImpl implements SeckillService 

既然已经开启了基于注解的事物,那我们就去需要被事物的方法上加个注解@Transactional吧:

@Transactional
    @Override
    public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException 

[外链图片转存失败(img-unAsamLQ-1566365498406)(https://raw.githubusercontent.com/DiCaprio17/DiCaprio17.github.io/master/img/2019-08-20-SSM框架高并发和商品秒杀项目(IDEA)-5.png)]
[外链图片转存失败(img-unwRhGSe-1566365498407)(https://raw.githubusercontent.com/DiCaprio17/DiCaprio17.github.io/master/img/2019-08-20-SSM框架高并发和商品秒杀项目(IDEA)-6.png)]
加入日志工具logback.xml
[外链图片转存失败(img-2ngMIVK8-1566365498408)(https://raw.githubusercontent.com/DiCaprio17/DiCaprio17.github.io/master/img/2019-08-20-SSM框架高并发和商品秒杀项目(IDEA)-7.png)]

<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="false">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT"/>
    </root>

</configuration>
Service层的测试

写测试类,我这里的测试类名为SeckillServiceImplTest:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring/spring-dao.xml", "classpath:spring/spring-service.xml"})
public class SeckillServiceImplTest {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private SeckillService seckillService;

    @Test
    public void getSeckillList() throws Exception {
        List<Seckill> seckillList = seckillService.getSeckillList();
        logger.info("list={}", seckillList);
        System.out.println(seckillList.toString());
        /**
         * 2019-08-20 17:23:11.216 [main] INFO  org.seckill.service.impl.SeckillServiceImplTest - list=[Seckill{seckillId=1000, name='1000元秒杀iPhoneX', number=100, startTime=Wed May 22 00:00:00 CST 2019, endTime=Thu May 23 00:00:00 CST 2019, createTime=Sun Aug 18 13:38:11 CST 2019}, Seckill{seckillId=1001, name='500元秒杀iPad2', number=200, startTime=Wed May 22 00:00:00 CST 2019, endTime=Thu May 23 00:00:00 CST 2019, createTime=Sun Aug 18 13:38:11 CST 2019}, Seckill{seckillId=1002, name='300元秒杀小米7', number=300, startTime=Wed May 22 00:00:00 CST 2019, endTime=Thu May 23 00:00:00 CST 2019, createTime=Sun Aug 18 13:38:11 CST 2019}, Seckill{seckillId=1003, name='200元秒杀红米note', number=400, startTime=Wed May 22 00:00:00 CST 2019, endTime=Thu May 23 00:00:00 CST 2019, createTime=Sun Aug 18 13:38:11 CST 2019}]
         * [Seckill{seckillId=1000, name='1000元秒杀iPhoneX', number=100, startTime=Wed May 22 00:00:00 CST 2019, endTime=Thu May 23 00:00:00 CST 2019, createTime=Sun Aug 18 13:38:11 CST 2019}, Seckill{seckillId=1001, name='500元秒杀iPad2', number=200, startTime=Wed May 22 00:00:00 CST 2019, endTime=Thu May 23 00:00:00 CST 2019, createTime=Sun Aug 18 13:38:11 CST 2019}, Seckill{seckillId=1002, name='300元秒杀小米7', number=300, startTime=Wed May 22 00:00:00 CST 2019, endTime=Thu May 23 00:00:00 CST 2019, createTime=Sun Aug 18 13:38:11 CST 2019}, Seckill{seckillId=1003, name='200元秒杀红米note', number=400, startTime=Wed May 22 00:00:00 CST 2019, endTime=Thu May 23 00:00:00 CST 2019, createTime=Sun Aug 18 13:38:11 CST 2019}]
         */
    }

    @Test
    public void getById() throws Exception {
        long seckillId = 1000;
        Seckill seckill = seckillService.getById(seckillId);
        logger.info("seckill={}", seckill);
        /**
         * 2019-08-20 17:24:44.717 [main] INFO  org.seckill.service.impl.SeckillServiceImplTest - seckill=Seckill{seckillId=1000, name='1000元秒杀iPhoneX', number=100, startTime=Wed May 22 00:00:00 CST 2019, endTime=Thu May 23 00:00:00 CST 2019, createTime=Sun Aug 18 13:38:11 CST 2019}
         */
    }

    @Test
    public void exportSeckillUrl() throws Exception {
        long seckillId = 1000;
        Exposer exposer = seckillService.exportSeckillUrl(seckillId);
        logger.info("exposer={}", exposer);
        /**
         * 2019-08-20 17:26:04.390 [main] INFO  org.seckill.service.impl.SeckillServiceImplTest - exposer=Exposer{秒杀状态=false, md5加密值='null', 秒杀ID=1000, 当前时间=1566293164389, 开始时间=1558454400000, 结束=1558540800000}
         * 秒杀状态=false, md5加密值='null'是因为createTime没有在startTime-endTime之间
         * 修改之后
         * 2019-08-20 17:39:34.947 [main] INFO  org.seckill.service.impl.SeckillServiceImplTest - exposer=Exposer{秒杀状态=true, md5加密值='3781463499435ea6016e4163568492c2', 秒杀ID=1000, 当前时间=0, 开始时间=0, 结束=0}
         */
    }

    @Test
    public void executeSeckill() throws Exception {
        long seckillId = 1000;
        Exposer exposer = seckillService.exportSeckillUrl(seckillId);
        if (exposer.isExposed()) {
            long userPhone = 12222222222L;
            String md5 = "3781463499435ea6016e4163568492c2";//使用上面的md5
            try {
                SeckillExecution seckillExecution = seckillService.executeSeckill(seckillId, userPhone, md5);
                logger.info("result={}", seckillExecution);
            } catch (SeckillCloseException | RepeatKillException e) {
                e.printStackTrace();
            }
        } else {
            logger.warn("秒杀未开启");
        }
        /**
         * 2019-08-20 17:41:57.712 [main] INFO  org.seckill.service.impl.SeckillServiceImplTest - result=SeckillExecution{秒杀的商品ID=1000, 秒杀状态=1, 秒杀状态信息='秒杀成功', 秒杀的商品=SuccessKilled{seckilled=0, userPhone=12222222222, state=0, createTime=Tue Aug 20 17:41:57 CST 2019}}
         */
    }

    @Test
    public void executeSeckillProcedureTest() {
        long seckillId = 1001;
        long phone = 1368011101;
        Exposer exposer = seckillService.exportSeckillUrl(seckillId);
        if (exposer.isExposed()) {
            String md5 = exposer.getMd5();
            SeckillExecution execution = seckillService.executeSeckill(seckillId, phone, md5);
            System.out.println(execution.getStateInfo());
            //秒杀成功
        }
    }
}

测试的话如果每个方法测试都通过就说明通过,如果报错了话就仔细看下哪一步错了检查下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SSM框架(Spring+SpringMVC+MyBatis)是一种常用的Java企业级框架,它能够将各模块进行解耦,并使用注入和依赖注入等技术实现松耦合的开发。 RESTful是一种软件架构风格,它通过基于HTTP的标准请求方法(GET、POST、PUT、DELETE等)来进行资源的增删改查操作。它具有简洁、易于理解和扩展性好的特点。 Spring Boot是一种基于Spring框架的快速开发框架,它封装了很多开发常用的依赖和配置,极大地简化了Spring应用的配置过程。 而在利用Idea开发SSM框架的RESTful风格的Spring Boot项目时,可以按照以下步骤进行: 1. 首先,创建一个空的Spring Boot项目,可以在Idea使用Spring Initializer快速生成。 2. 在pom.xml文件添加所依赖的SSM框架、连接数据库的相关依赖以及其他需要的依赖。 3. 在src/main/java目录下创建主启动类,并添加相关注解,如@SpringBootApplication等。同时,创建并配置application.properties或application.yml文件来设置项目的相关配置信息。 4. 创建用于处理网页请求的Controller、用于数据传输的DTO类、实体类等。 5. 利用MyBatis创建DAO和Mapper接口,同时编写相应的Mapper.xml文件,使用注解或者XML方式配置SQL语句。 6. 创建Service的接口和实现类,用于进行业务处理,并调用DAO的方法来访问数据。 7. 在Controller进行相关URL的映射和请求处理,实现RESTful风格的API接口,并调用Service的方法。 8. 对项目进行测试和调试,可以使用Postman等工具进行接口的测试,验证项目的正确性。 总的来说,利用SSM框架和RESTful风格的开发,结合Spring Boot和Idea等工具,可以快速搭建和开发Java项目,提高开发效率和可维护性。同时,也能为项目提供稳定的运行环境和良好的用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值