Service Layer of Secondary Killing System Based on SSM

Keywords: Java Spring xml Junit

Preface

This blog is the DAO layer of the second kill system based on SSM. If you don't know the whole process, please watch the last one first.

Introduction to Service Layer

In the previous DAO layer, the interface and related SQL are written to separate the code from the SQL and facilitate future review. When we need to control some processes or logic of the DAO layer, such as when we need many DAO layer methods to combine to complete a thing, then the work of the Service layer will be done. The logical operations such as splicing DAO methods are done in the Service layer.

Design of Service Layer Structure

Create service packages to store relevant code, and create exceptions packages to store exceptions needed by service interfaces, such as duplicate secondkill and secondkill that have closed these exceptions. In creating dto package for data transmission, it is mainly used for data transmission between web layer and service layer.

Creating SeckillService Interface

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

import java.util.List;

public interface SeckillService {

    /**
     * Query all pending merchandise records
     */
    List<Seckill> getSeckillList();

    /**
     * Query for a single pending merchandise record
     */
    Seckill getById(long seckillId);

    /**
     * Exposure of secondkill address
     */
    Exposer exportSeckillUrl(long seckillId);

    /**
     * Perform a second kill operation
     * Declarative transactions through spring
     */
    SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)throws SeckillException,RepeatKillException,SeckillCloseException;
}

Packaging class Exposer

exportSeckillUrl(long seckillId) method in the interface is used to output the interface address when the second kill is opened, otherwise the system time and the second kill time are output. Create an Exposer class in the dto package to expose the secondkill address as an encapsulation of data returned by the exportSeckillUrl () method.

package org.seckill.dto;

/**
 * Exposure of secondkill address
 */
public class Exposer {

    //Whether to turn on secondkill
    private boolean exposed;

    //Encryption measures
    private String md5;

    //Commodity id
    private long seckillId;

    //System current time (milliseconds)
    private long now;

    //Opening time
    private long start;

    //End time
    private long end;

    @Override
    public String toString() {
        return "Exposer{" +
                "exposed=" + exposed +
                ", md5='" + md5 + '\'' +
                ", seckillId=" + seckillId +
                ", now=" + now +
                ", start=" + start +
                ", end=" + end +
                '}';
    }

    public Exposer(boolean exposed, String md5, long seckillId) {
        this.exposed = exposed;
        this.md5 = md5;
        this.seckillId = seckillId;
    }

    //Initialization when time does not match
    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;
    }

    //There is no such commodity.
    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;
    }
}

Create exception classes

When exceptions are executed, you need to know the exceptions it may output, such as repetitive exceptions when executing a second kill, so you also need to mark the possible run-time exceptions after the executeSeckill method, so that spring transactions can roll back these run-time exceptions.

Second Kill Related Business Abnormalities

package org.seckill.exception;

/**
 * Second Kill Related Business Abnormalities
 */
public class SeckillException extends RuntimeException{

    public SeckillException(String message) {
        super(message);
    }

    public SeckillException(String message, Throwable cause) {
        super(message, cause);
    }
}

Repetitive secondkill anomaly

package org.seckill.exception;

/**
 * Repeated secondkill exception (run-time exception)
 */
public class RepeatKillException extends SeckillException{

    public RepeatKillException(String message) {
        super(message);
    }

    public RepeatKillException(String message, Throwable cause) {
        super(message, cause);
    }
}

Secondkill shutdown exception

Time is up or stocks are empty, but still to get the secondkill address

package org.seckill.exception;

/**
 * Secondkill shutdown exception
 */
public class SeckillCloseException extends SeckillException{
    public SeckillCloseException(String message) {
        super(message);
    }

    public SeckillCloseException(String message, Throwable cause) {
        super(message, cause);
    }
}

Encapsulation class SeckillExecution

Create enumeration classes to represent constant data fields

Enumeration is used to encapsulate state and stateInfo.

package org.seckill.enums;

public enum SeckillStatEnum {
    SUCCESS(1,"Spike kill"),
    END(0,"End of spike"),
    REPEAT_KILL(-1,"Repeat spike"),
    INNER_ERROR(-2,"System exception"),
    DATA_REWRITE(-3,"Data tampering");

    private int state;

    private String stateInfo;

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

ExcuteSeckill (long seckill Id, long user Phone, String md5) method is used to perform the second kill operation. The process of this method is equivalent to Seckill Id (corresponding to a commodity) and userPhone (equivalent to a user) to perform the second kill. To perform the second kill, the Exposer exposed interface address must be called first, and MD5 in Exposer is passed to the executeSeckill method. For validation with the internal MD5 rules, if the two MD5 values are different, it means that the user's url has been tampered with, and it can not be killed in seconds. In order to encapsulate the return data of the execution of the second kill method, the class SeckillExecution is created in dto to encapsulate the results of the execution of the second kill. The data needed in the result of the second kill execution is the attributes of this class. Property needs to be able to tell the user whether the second kill is successful, if successful, return the SuccessKilled object containing the commodity property, and if failed, return the information.

package org.seckill.dto;

import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStatEnum;

/**
 * Encapsulation of the results of the execution of the second kill
 */
public class SeckillExecution {

    private long seckillId;

    //Second Kill Execution Result Status
    private int state;

    //Status identifier
    private String stateInfo;

    //Successful target of second kill
    private SuccessKilled successKilled;

    @Override
    public String toString() {
        return "SeckillExecution{" +
                "seckillId=" + seckillId +
                ", state=" + state +
                ", stateInfo='" + stateInfo + '\'' +
                ", successKilled=" + successKilled +
                '}';
    }

    //Successful second killing, using enumerations to represent state and stateInfo
    public SeckillExecution(long seckillId, SeckillStatEnum statEnum, SuccessKilled successKilled) {
        this.seckillId = seckillId;
        this.state = statEnum.getState();
        this.stateInfo = statEnum.getStateInfo();
        this.successKilled = successKilled;
    }

    //Spike kill
    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;
    }
}

Interface Implementation Class SeckillService Impl

  • Advantages of using annotations to control transactions

Firstly, it clearly annotates the programming style of transaction methods; secondly, it ensures that the execution time of transaction methods is as short as possible. In order to ensure the performance, it is necessary to avoid interposing other network operations such as RPC/HTTP, and secondly, rollBack when the method is catch ed during the operation period. It needs to be clear that not all methods need transactions, such as only one modification operation or read-only operation. Transaction control.

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

    <!-- scanning service All types of annotations used under the package -->
    <context:component-scan base-package="org.seckill.service"/>

    <!-- Configuring Transaction Manager -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- Injecting database connection pool -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- Configuring annotation-based declarative transactions,Default use of annotations to manage transaction behavior-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

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



@Service
public class SeckillServiceImpl implements SeckillService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private SeckillDao seckillDao;

    @Autowired
    private SuccessKilledDao successKilledDao;


    //md5 salt value string for confusing md5
    private final String salt = "djfsdognogdjfsdfdj**(*&fdjksdf";

    @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()
                || endTime.getTime() < nowTime.getTime()) {
            return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), endTime.getTime());
        }
        //The process of transforming a particular string is irreversible
        String md5 = getMD5(seckillId);
        return new Exposer(true, md5, seckillId);
    }

    private String getMD5(long seckillId) {
        String base = seckillId + "/" + salt;
        String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
        return md5;
    }

    @Override
    @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");
        }
        //Execute the second kill logic: reduce inventory + record purchase behavior Success Killed
        Date nowTime = new Date();
        //try catch prevents other exceptions: insert timeouts or database connection breaks
        try {
            //Reduce stock
            int updateCount = seckillDao.reduceNumber(seckillId, nowTime);
            if (updateCount <= 0) {
                //No update to the record, the second kill is over (stock is not available or time is not within the scope)
                throw new SeckillCloseException("seckill is closed");
            } else {
                //Successful inventory reduction and record purchasing behavior
                //If the insertion is successful, the number of rows affected is a value greater than 0.
                int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
                //seckillId,userPhone decides the only
                if (insertCount <= 0) {
                    //Repeated secondkill, affecting the number of rows is not positive
                    throw new RepeatKillException("seckill repeated");
                } else {
                    //Spike kill
                    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);
            //All compilation exceptions, converted to run-time exceptions
            throw new SeckillException("seckill inner error:" + e.getMessage());
        }
    }
}

Create test classes

  • Define logback configuration file for output logs

Create logback.xml in the resources directory

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

When testing the getSeckillList() method, the output Closing non-transactional SqlSession indicates that it is no longer under transaction control because it is not under spring's Transactional annotation.

When the exportSeckillUrl() method is executed, md5 is NULL if the current time is not within the corresponding test commodity secondkill time range.

Before testing the executeSeckill() method, first copy the md5 value returned by the previous exportSeckillUrl () method, and then use it as the execution parameter of this method. However, it should be noted that if the product has this phone second kill in the Success Killed table, it can not be repeated second kill. Junit error reporting, you can add catch to this test and Junit will not be thrown, JUnit will not report error. But because repetitive second kills don't commt.
When the method is successfully executed, it shows that JDBC Connection will be managed by Spring, that is, the method is managed by spring.

In fact, the exportSeckillUrl () method and the executeSeckill () method should be together before they are complete, so they should be linked together in series to create a test method testSeckillLogic() to aggregate the two methods. if (exposer.isExposed()) is used to judge whether the product is open seconds killing, if opened, you can get the md5 of the product, otherwise the seconds killing is not open.

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

import static org.junit.Assert.*;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
        "classpath:spring/spring-dao.xml",
        "classpath:spring/spring-service.xml"})
public class SeckillServiceTest {

    //slf4j is the interface and logback is the implementation
    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);
    }

    @Test
    public void getById() throws Exception {

        long id = 1000;
        Seckill seckill = seckillService.getById(id);
        logger.info("seckill={}",seckill);
    }

    @Test
    public void exportSeckillUrl() throws Exception {

        long id = 1000;
        Exposer exposer = seckillService.exportSeckillUrl(id);
        logger.info("exposer={}",exposer);
    }

    @Test
    public void executeSeckill() throws Exception {

        long id = 1000;
        long phone = 18787125628L;
        String md5 = "66160fbb9096b40283a30043603d17e7";
        SeckillExecution seckillExecution = seckillService.executeSeckill(id, phone, md5);
        logger.info("ScekillExecution={}",seckillExecution);
    }

    @Test
    public void testSeckillLogic() throws Exception {

        long id = 1001;
        Exposer exposer = seckillService.exportSeckillUrl(id);
        if (exposer.isExposed()) {
            logger.info("exposer={}", exposer);
            long phone = 18787125629L;
            String md5 = exposer.getMd5();
            try {
                SeckillExecution seckillExecution = seckillService.executeSeckill(id, phone, md5);
                logger.info("ScekillExecution={}", seckillExecution);
            } catch (RepeatKillException e) {
                logger.error(e.getMessage());
            } catch (SeckillCloseException e1) {
                logger.error(e1.getMessage());
            }
        } else {
            //Second Kill Not Opened
            logger.info("exposer={}",exposer);
        }
    }
}

The next article goes on to build the Web layer: Implementation of Web Layer of Secondary Killing System Based on SSM

Posted by kinaski on Sat, 20 Apr 2019 20:06:34 -0700