Java高并发秒杀API之service层实现(二)

二 service层实现

1.内容

站在使用者的角度设计接口
三个方向 :方法粒度,参数,返回类型

2.代码

SeckillService

package org.seckill.service;

import java.util.List;

import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillException;
import org.seckill.exception.seckillCloseException;
/**
 * 站在使用者角度设计接口
 * 三个方面 方法粒度 参数 返回类型 
 */
public interface SeckillService {
/**
 * 
 * 查询所有秒杀记录
 * @return
 */
    List <Seckill> getSeckillList();
    /**
     * 
     * 查询单个秒杀记录
     * @param seckillId
     * @return
     */
    Seckill getById(long seckillId);
    /**
     * 输出秒杀接口地址 否则输出系统时间和秒杀时间
     * @param seckillId
     */
    Exposer exportSeckillUrl(long seckillId);


    //执行秒杀操作
    SeckillExecution excuteSeckill(long seckillId,long userPhone,String md5) throws RepeatKillException,seckillCloseException,SeckillException; 


    //执行秒杀操作by存储过程
        SeckillExecution excuteSeckillProcedure(long seckillId,long userPhone,String md5) throws RepeatKillException,seckillCloseException,SeckillException; 

}

重复秒杀异常 RepeatKillException

package org.seckill.exception;
//重复秒杀异常(运行期异常)
public class RepeatKillException extends RuntimeException{

    public RepeatKillException(String message, Throwable cause) {
        super(message, cause);
        // TODO Auto-generated constructor stub
    }

    public RepeatKillException(String message) {
        super(message);
        // TODO Auto-generated constructor stub
    }


}

秒杀关闭异常 seckillCloseException

package org.seckill.exception;
/**
 * 
 * 秒杀关闭异常
 * @author LCJA
 *
 */
public class seckillCloseException extends RuntimeException{

    public seckillCloseException(String message, Throwable cause) {
        super(message, cause);
        // TODO Auto-generated constructor stub
    }

    public seckillCloseException(String message) {
        super(message);
        // TODO Auto-generated constructor stub
    }

}

秒杀相关的所有业务异常 SeckillException

package org.seckill.exception;
/**
 * 
 * 秒杀相关的所有业务异常
 * @author LCJA
 *
 */
public class SeckillException extends RuntimeException{

    public SeckillException(String message, Throwable cause) {
        super(message, cause);
        // TODO Auto-generated constructor stub
    }

    public SeckillException(String message) {
        super(message);
        // TODO Auto-generated constructor stub
    }


}

SeckillServiceImpl 实现类

package org.seckill.service.impl;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.MapUtils;
import org.seckill.dao.SeckillDao;
import org.seckill.dao.SuccessKilledDao;
import org.seckill.dao.cache.RedisDao;
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.SeckillException;
import org.seckill.exception.seckillCloseException;
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;
//@component  @service @dao @conroller
@Service
public class SeckillServiceImpl implements SeckillService{
    private Logger logger=LoggerFactory.getLogger(this.getClass());
    // mabaits 使用mapper形式 注入spring容器中
    //注入service依赖
    @Autowired//@resource@inject
    private SeckillDao seckilldao;
    @Autowired
    private SuccessKilledDao successkilleddao;
    @Autowired
    private RedisDao redisdao;

    //用于混淆md5
    private final String slat="abcd";
    public List<Seckill> getSeckillList() {
        // TODO Auto-generated method stub
        return seckilldao.queryAll(0, 4);
    }

    public Seckill getById(long seckillId) {
        // TODO Auto-generated method stub
        return seckilldao.queryById(seckillId);
    }

    public Exposer exportSeckillUrl(long seckillId) {
        //优化点 : 缓存优化
        /**
         * get from cache
         * if null
         * get db
         * else 
         *  put cache 
         *  locgin
         *  可以这样做的原因 秒杀单 一般不做修改
         *  超时维护
         *  一般做集群  
         */
        //访问redis
        Seckill seckill = redisdao.getSeckill(seckillId);
        if(seckill == null){
             seckill=seckilldao.queryById(seckillId);
             System.out.println("seckill----"+seckill);
                if(seckill== null){
                    return new Exposer(false, seckillId);
                }else{
                    //放入redis
                    redisdao.putSeckill(seckill);
                }

        }

        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);
    }
/**
 * 使用注解控制事务有点
 * 开发团队达成一致约定 明确事务方法的编程风格
 * 保证事务方法的执行时间竟可能短不要穿插其他网络操作
 * 不是所有的方法都需要事务 如只有一条修改操作 ,只读操作不需要控制事务
 * 通过异常来告诉声明式事务 回滚还是commit
 */@Transactional
    public SeckillExecution excuteSeckill(long seckillId, long userPhone, String md5)
            throws RepeatKillException, seckillCloseException, SeckillException {
        try{
        if(md5==null||!md5.equals(getMD5(seckillId))){
            throw new SeckillException("seckill data rewrite");
        }
        int insertCount=successkilleddao.insertSuccessKilled(seckillId, userPhone);
        if(insertCount<=0){
            //重复秒杀
            throw new RepeatKillException("seckill repeated");
        }else{
            //执行秒杀逻辑 减库存 +记录购买行为
            //减库存热点商品竞争
            int updateCount = seckilldao.reduceNumber(seckillId, new Date());
            if(updateCount <= 0){
                //没有更新到记录,秒杀结束
                throw new seckillCloseException("seckill losed");
            }else{
                //减库存成功 记录购买行为
                //秒杀成功
                SuccessKilled successKilled=successkilleddao.queryByIdWithSeckill(seckillId, userPhone);
                return new SeckillExecution(seckillId,SeckillStatEnum.SUCCESS,successKilled);       
            }
        }
        }catch(Exception e){
            logger.error(e.getMessage(),e);
            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;
    }

    public SeckillExecution excuteSeckillProcedure(long seckillId, long userPhone, String md5)
            throws RepeatKillException, seckillCloseException, SeckillException {
        if(md5==null||!md5.equals(getMD5(seckillId))){
            return  new SeckillExecution(seckillId,SeckillStatEnum.DATA_REWRITE);
        }
        Date killTime = new Date();
        Map <String, Object>map =new HashMap<String, Object>();
        map.put("seckillId", seckillId);
        map.put("phone", userPhone);
        map.put("killTime", killTime);
        map.put("result", null);
    try {
        seckilldao.killByProcedure(map);
        int result = MapUtils.getInteger(map, "result",-2);
        if(result==1){
            SuccessKilled sk = successkilleddao.queryByIdWithSeckill(seckillId, userPhone);
            return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS,sk);
        }else{
            return new SeckillExecution(seckillId, SeckillStatEnum.stateOf(result));
        }
    } catch (Exception e) {
        logger.error(e.getMessage());
        return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
    }
    }

}

SeckillStatEnum

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

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

    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 SeckillStatEnum stateOf(int index){
        for (SeckillStatEnum state :values()){
            if(state.getState() == index){
                return state;
            }
        }
        return null;
    }

}
3.spring托管service

spring会通过spring工厂创建对象

seckillservice 依赖 SeckillDao和SuccessKillDao,
SeckillDao和SuccessKillDao依赖SqlSessionFactory,
SqlSessionFactory 依赖 数据源..

原因
对象创建统一管理,
规范生命周期管理,
灵活的依赖注入,
一致获取对象。

ioc使用场景

xml注解配置类
1.bean实现类来自第三方类库,如DataSource等;2需要命名空间配置,如:context,aop,mvc等项目中自身开发使用的类,可直接在代码中使用注解 如@Service等需要通过代码控制对象创建逻辑的场景,如自定义修改依赖类库

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:aop="http://www.springframework.org/schema/aop" 
        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"></context:component-scan>

        <!-- 事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入数据库连接池 -->
        <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- 配置注解式声明事务 -->
        <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>
4.spring声明式事务

抛出运行期异常时,会回滚,小心try-catch

这个是 tx:advice+aop命名空间  一次配置永久生效(看起来很美好),本篇没有采用
<!-- 配置事务事务属性 -->
     <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
           <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="append*" propagation="REQUIRED" />
            <tx:method name="insert*" propagation="REQUIRED" />
            <tx:method name="save*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="modify*" propagation="REQUIRED" />
            <tx:method name="edit*" propagation="REQUIRED" />
            <tx:method name="delete*" propagation="REQUIRED" />
            <tx:method name="remove*" propagation="REQUIRED" />
            <tx:method name="get*" propagation="SUPPORTS" />
            <tx:method name="*" propagation="SUPPORTS" />
        </tx:attributes>
    </tx:advice>
    <!-- 配置事务切点,并把切点和事务属性关联起来 -->
    <aop:config>
        <aop:pointcut expression="execution(* com.jinlou.service..*Impl.*(..))" id="txPointcut"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
本篇采用的是注解的形式
  <!-- 配置注解式声明事务 -->
        <tx:annotation-driven transaction-manager="transactionManager"/>

使用注解控制事务的优点;
1.开发团队达成一致约定,明确标注事务方法的编程风格
2.保证事务执行时尽可能短,不要穿插其他网络操作(rpc(远程服务调用),http请求)
3.不是所有的方法都不是需要事务

5.单元测试
package org.seckill.service;

import java.util.List;

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

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
    "classpath:spring/spring-dao.xml",
    "classpath:spring/spring-service.xml"
        })
public class SeckillServiceTest {
    private Logger logger=LoggerFactory.getLogger(this.getClass());
    @Autowired
private SeckillService seckillService;
    @Test
    public void testGetSeckillList() {
        List <Seckill> list=seckillService.getSeckillList();
        logger.info("list={}",list);
    }

    @Test
    public void testGetById() {
        long id=1003;
        Seckill seckill=seckillService.getById(id);
        logger.info("seckill={}seckill",seckill);
    }

    @Test
    public void testExportSeckillUrl() {
        long id=1000;
        Exposer exposer=  seckillService.exportSeckillUrl(id);
        logger.info("exposer={}",exposer);
    }
    //exposed=true, md5=8e7add56132475b9141111ed8e961613, seckillId=1000, now=0, start=0, end=0]

    @Test
    public void testExcuteSeckill() {
        long id=1000;
        long phone=12345679;
        String md5="8e7add56132475b9141111ed8e961613";
        SeckillExecution seckillexcution =seckillService.excuteSeckill(id, phone, md5);
        logger.info("seckillexcution={}",seckillexcution);
    }
    @Test
    public  void executeSeckillProcedure(){
        long seckillId =1000;
        long phone =1313131313;
        Exposer exposer = seckillService.exportSeckillUrl(seckillId);
        if(exposer.isExposed()){
            String md5 = exposer.getMd5();
            SeckillExecution excution = seckillService.excuteSeckillProcedure(seckillId, phone, md5);
            logger.info(excution.getStateInfo());
        }


    }

}

logback.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 默认往控制台打印 -->
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <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>

另外可以打印sql

<bean id="druid-stat-interceptor"
        class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor">
    </bean>
    <bean id="druid-stat-pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"
        scope="prototype">
        <property name="patterns">
            <list>
                <value>com.skwx.service.impl.*</value>
            </list>
        </property>
    </bean>
    <aop:config>
        <aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut" />
    </aop:config>

需要在pom中添加依赖

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.27</version>
    </dependency>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值