ssm框架maven配置

此文档总结了在maven中组建ssm框架的相关配置

0.创建好maven后首先需要改web.xml的表头

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1"
         metadata-complete="true">
  <!--注意这里要把maven新建项目时自动生成的web.xml头部替换成上面的头部,修改servlet版本,这里为3.1,这样就能自动支持spring EL-->
</web-app>

0.5 maven目录结构

src
  main
    java
    resources
    sql
    webapps

  test

1.pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.braincao</groupId>
  <artifactId>seckill</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>seckill Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>

    <junit.version>4.11</junit.version>
    <slf4j-api.version>1.7.12</slf4j-api.version>
    <logback-core.version>1.1.1</logback-core.version>
    <mysql-connector-java.version>5.1.35</mysql-connector-java.version>
    <c3p0.version>0.9.1.2</c3p0.version>
    <mybatis.version>3.3.0</mybatis.version>
    <mybatis-spring.version>1.2.3</mybatis-spring.version>
    <standard.version>1.1.2</standard.version>
    <jstl.version>1.2</jstl.version>
    <jackson-databind.version>2.5.4</jackson-databind.version>
    <javax.servlet-api.version>3.1.0</javax.servlet-api.version>
    <spring-core.version>4.1.7.RELEASE</spring-core.version>
  </properties>

  <dependencies>
    <!-- 单元测试:使用junit4,支持注解 -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>

    <!-- 1.日志
      java日志:slf4j,log4j,logback,common-logging
      slf4j: 是规范/接口
      日志实现:log4j,logback,common-logging
      本案例使用:slf4j+logback -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>${slf4j-api.version}</version>
    </dependency>

    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-core</artifactId>
      <version>${logback-core.version}</version>
    </dependency>

    <!--实现slf4j接口并整合-->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>${logback-core.version}</version>
    </dependency>

    <!--2.数据库相关依赖-->
    <!--mysql driver-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>${mysql-connector-java.version}</version>
      <scope>runtime</scope>
    </dependency>

    <!--c3p0连接池-->
    <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>${c3p0.version}</version>
    </dependency>

    <!--DAO框架:mybatis依赖-->
    <!--mybatis-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>${mybatis.version}</version>
    </dependency>

    <!--mybatis自身实现的spring整合依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>${mybatis-spring.version}</version>
    </dependency>

    <!--3.Servlet web相关依赖-->
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>${standard.version}</version>
    </dependency>

    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>${jstl.version}</version>
    </dependency>

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson-databind.version}</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>${javax.servlet-api.version}</version>
    </dependency>

    <!--4.Spring依赖-->
    <!--1)Spring核心依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring-core.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring-core.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring-core.version}</version>
    </dependency>

    <!--2)Spring dao层依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring-core.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>${spring-core.version}</version>
    </dependency>

    <!--3)Spring web依赖: Spring Web+Spring MVC-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring-core.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring-core.version}</version>
    </dependency>

    <!--4)Spring test依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring-core.version}</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>seckill</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.7.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.20.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

2.mybatis-config.xml

表头可以在mybatis中文官网中找到

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <!--配置全局属性-->
    <settings>
        <!--默认为false,设为true后我们使用jdbc的getGeneratedKeys可以获取数据库的自增主键值-->
        <setting name="useGeneratedKeys" value="true"/>

        <!--默认为true,使用列别名替换列名
            select name as title from table-->
        <setting name="useColumnLabel" value="true"/>

        <!--开启驼峰命名转换:table(create_time) -> entity(createTime)-->
        <setting name="mapUnderscoreCamelCase" value="true"/>

    </settings>

    <!--其他的mybatis配置如连接数据库等都在mybatis与spring整合中一起配置-->
</configuration>

3.配置完mybatis-config后写mapper.xml

这里放3个示例mapper.xml,同样表头可以在mybatis中文官网示例中找到.

mapper.xml目的:为DAO接口方法提供sql语句配置

mapper.xml中参数多个的时候可以不写,dao层用的时候可以通过@param(“xxx”)来自动识别

mapper.xml中返回值如果是列表,写列表里面的类型即可

mapper.xml中类型不用写包名,配置时配置就行

是XML转义用的

示例1:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.braincao.dao.SeckillDao">
    <!--目的:为DAO接口方法提供sql语句配置-->
    <!--参数多个的时候可以不写;返回值如果是列表,写列表里面的类型即可-->
    <!--<![CDATA[<=]]>是XML转义用的-->

    <update id="reduceNumber">
        UPDATE
          seckill
        SET
          number = number - 1
        WHERE
          seckill_id = #{seckillId}
          and start_time <![CDATA[<=]]> #{createTime}
          and end_time >= #{createTime}
          and number > 0
    </update>

    <select id="queryById" parameterType="long" resultType="Seckill">
        SELECT seckillId,name,number,start_time,end_time,create_time
        FROM seckill
        WHERE seckillId = #{seckillId}
    </select>

    <select id="queryAll" resultType="Seckill">
        SELECT seckillId,name,number,start_time,end_time,create_time
        FROM seckill
        ORDER BY create_time DESC
        limit #{offset}, #{limit}
    </select>
</mapper>

示例2:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.braincao.dao.SeckillDao">
    <!--目的:为DAO接口方法提供sql语句配置-->
    <!--参数多个的时候可以不写,dao层用的时候可以通过@param("xxx")来自动识别;返回值如果是列表,写列表里面的类型即可;类型不用写包名,配置时配置就行-->
    <!--<![CDATA[<=]]>是XML转义用的-->

    <insert id="insertSuccessKilled">
        <!--INSERT ignore INTO:主键冲突时不报错,而是返回0-->
        INSERT ignore INTO success_killed(seckill_id, user_phone)
        VALUES (#{seckillId}, #{userPhone})
    </insert>

    <!--根据seckillId查询SuccessKilled实体(也携带Seckill秒杀商品对象实体)-->
    <!--mybatis把结果映射到SuccessKilled,同时映射属性seckill属性-->
    <!--由此可以看出,mybatis可以很自由的控制sql-->
    <select id="queryByIdWithSeckill" resultType="SuccessKilled">
        SELECT
          sk.seckillId,
          sk.user_phone,
          sk.state,
          sk.create_time,
          s.seckillId "seckill.seckill_id",
          s.name "seckill.name",
          s.number "seckill.number",
          s.start_time "seckill.start_time",
          s.end_time "seckill.end_time",
          s.create_time "seckill.create_time"
        FROM success_killed sk INNER JOIN seckill s
        ON sk.seckill_id = s.seckill_id
        WHERE sk.seckillId = #{seckillId}
    </select>
</mapper>

示例3:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 为这个mapper指定一个唯一的namespace,namespace的值习惯上设置成包名加sql映射文件名,这样就能够保证namespace的值是唯一的-->
<mapper namespace="com.braincao.dao.ICommandDao">

    <!--确定映射关系,此命名空间下,id=CommandResult的resultMap所映射实体类是Command-->
    <resultMap type="com.braincao.bean.Command" id="CommandResult">
        <!--左边数据库名,右边model实体名-->
        <id column="A_ID" jdbcType="INTEGER" property="id"/>
        <result column="COMMAND" jdbcType="VARCHAR" property="command"/>
        <result column="DESCRIPTION" jdbcType="VARCHAR" property="description"/>
        <!--一对多的映射关系:主表包含字表的集合; 如果是多对一的话同样的,只是collection换成了association-->
        <collection resultMap="CommandContentMapper.CommandContentResult" property="commandContentList"/>
    </resultMap>

    <!--分页功能:后台list.jsp根据查询条件查询消息列表:parameterType表示传入的参数类型-->
    <select id="queryListCommand" parameterType="java.util.Map" resultMap="CommandResult">
        SELECT A.ID A_ID,A.COMMAND,A.DESCRIPTION,B.CONTENT FROM COMMAND A LEFT JOIN COMMAND_CONTENT B
        ON A.ID=B.COMMAND_ID
        <where>
            <if test="command.command!=null and !&quot;&quot;.equals(command.command.trim())">AND A.COMMAND = #{command.command}</if>
            <if test="command.description!=null and !&quot;&quot;.equals(command.description.trim())">AND A.DESCRIPTION LIKE '%' #{command.description} '%'</if>
        </where>
        ORDER BY A.ID LIMIT #{page.dbIndex}, #{page.dbNumber}
    </select>

    <!--拦截器实现分页功能:后台list.jsp根据查询条件查询消息列表:parameterType表示传入的参数类型-->
    <!--看源码后发现parameterType会自动先统一转成小写,并且map、list等直接写也行不用写包名,所以下面的MAp实验是成功的-->
    <select id="queryListCommandByPage" parameterType="MAp" resultMap="CommandResult">
        SELECT A.ID A_ID,A.COMMAND,A.DESCRIPTION,B.CONTENT FROM COMMAND A LEFT JOIN COMMAND_CONTENT B
        ON A.ID=B.COMMAND_ID
        <where>
            <if test="command.command!=null and !&quot;&quot;.equals(command.command.trim())">AND A.COMMAND = #{command.command}</if>
            <if test="command.description!=null and !&quot;&quot;.equals(command.description.trim())">AND A.DESCRIPTION LIKE '%' #{command.description} '%'</if>
        </where>
        ORDER BY A.ID
    </select>

    <!--前台initTalk.jsp根据查询条件查询消息列表:parameterType表示传入的参数类型-->
    <select id="queryListContent" parameterType="com.braincao.bean.Command" resultMap="CommandResult">
        SELECT A.ID A_ID,A.COMMAND,A.DESCRIPTION,B.CONTENT FROM COMMAND A LEFT JOIN COMMAND_CONTENT B
        ON A.ID=B.COMMAND_ID
        <where>
            <if test="command!=null and !&quot;&quot;.equals(command.trim())">AND A.COMMAND = #{command}</if>
            <if test="description!=null and !&quot;&quot;.equals(description.trim())">AND A.DESCRIPTION LIKE '%' #{description} '%'</if>
        </where>
    </select>

    <!--根据条件查询消息列表的总条数-->
    <select id="count" parameterType="com.braincao.bean.Command" resultType="int">
        SELECT COUNT(*) FROM COMMAND
        <where>
            <if test="command!=null and !&quot;&quot;.equals(command.trim())">AND COMMAND = #{command}</if>
            <if test="description!=null and !&quot;&quot;.equals(description.trim())">AND DESCRIPTION LIKE '%' #{description} '%'</if>
        </where>
    </select>

    <!--新增一条指令-->
    <insert id="addCommand" parameterType="com.braincao.bean.Command">
        INSERT INTO COMMAND (COMMAND,DESCRIPTION) VALUES(#{command},#{description})
    </insert>

    <!--修改指令及描述-->
    <update id="updateCommand" parameterType="com.braincao.bean.Command">
        UPDATE COMMAND SET COMMAND=#{command},DESCRIPTION=#{description} WHERE ID=#{id}
    </update>

    <!--根据id删除指定的一条指令-->
    <delete id="deleteOne" parameterType="int">
        DELETE FROM COMMAND WHERE ID = #{_parameter}
    </delete>

    <!--根据id批量删除消息-->
    <delete id="deleteBatch" parameterType="java.util.List">
        DELETE FROM COMMAND WHERE ID in
        (
        <foreach collection="list" item="item" separator=",">
            #{item}
        </foreach>
        )
    </delete>
</mapper>

4.spring-dao.xml

放在resources目录下,配置整合spring-mybatis:

<?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"
       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">
    <!--配置整合spring-mybatis过程,classpath就是java与resources下面的-->

    <!--1. 配置数据库相关参数 引入外部jdbc.properties属性文件,properties属性:${url}-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--2. 数据库连接池,这里为c3p0-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--配置连接池属性-->
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>

        <!--c3p0连接池私有属性,一般不配置,由于本案例高并发因此个性化配置-->
        <property name="maxPoolSize" value="30"/>
        <property name="minPoolSize" value="10"/>

        <!--关闭连接后不自动commit,默认为false-->
        <property name="autoCommitOnClose" value="false"/>

        <!--获取连接超时时间-->
        <property name="checkoutTimeout" value="1000"/>

        <!--当获取连接失败重试次数-->
        <property name="acquireRetryAttempts" value="2"/>
    </bean>

    <!--3. 配置sqlSessionFactory对象-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--注入数据库连接池-->
        <property name="dataSource" ref="dataSource"/>

        <!--配置mybatis全局配置文件:mybatis-config.xml-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>

        <!--扫描entity包,方便在mapper.xml中直接使用类名-->
        <property name="typeAliasesPackage" value="com.braincao.entity"/>

        <!--扫描sql配置文件:mapper需要的xml文件,这样不用在mybatis-config.xml中一个一个添加mapper.xml-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!--4. 配置扫描DAO接口包,动态实现DAO接口,注入到spring容器中-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--注入sqlSessionFactory-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

        <!--给出需要扫描的DAO接口包-->
        <property name="basePackage" value="com.braincao.dao"/>
    </bean>
</beans>

5.Service层接口与实现

service层接口:

package com.braincao.service;

import com.braincao.dto.Exposer;
import com.braincao.dto.SeckillExecution;
import com.braincao.entity.Seckill;
import com.braincao.exception.RepeatKillException;
import com.braincao.exception.SeckillCloseException;
import com.braincao.exception.SeckillException;

import java.util.List;

/**
 * 业务接口:Service层接口应站在使用者角度设计接口
 *
 */
public interface SeckillService {

    /**
     * 查询所有秒杀商品
     * @param void
     * @return List<Seckill>
     */
    List<Seckill> getSeckillList();

    /**
     * 根据id查询一个秒杀商品
     * @param seckillId
     * @return Seckill
     */
    Seckill getById(long seckillId);

    /**
     * 用户根据id查询该秒杀商品的秒杀接口地址时:
     * 秒杀开启时输出秒杀接口地址,未开启时输出系统时间+秒杀开启时间
     * @param seckillId
     * @return  秒杀接口地址。用dto传输层的实体来封装秒杀接口地址,dto方便web层拿到秒杀暴露接口相关数据
     */
    Exposer exportSeckillUrl(long seckillId);

    /**
     * 执行秒杀操作
     * @param seckillId,userPhone,md5
     *        md5用来验证是否是同一用户操作,如果md5变了,说明用户被篡改
     * @return dto传输层的实体来封装秒杀操作的返回实体,包含成功、失败
     * 当秒杀失败时输出自定义运行期异常RepeatKillException
     */
    SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
        throws SeckillException, SeckillCloseException, RepeatKillException;

}

service层实现:

package com.braincao.service.impl;

import com.braincao.dao.SeckillDao;
import com.braincao.dao.SuccessKilledDao;
import com.braincao.dto.Exposer;
import com.braincao.dto.SeckillExecution;
import com.braincao.entity.Seckill;
import com.braincao.entity.SuccessKilled;
import com.braincao.enums.SeckillStateEnum;
import com.braincao.exception.RepeatKillException;
import com.braincao.exception.SeckillCloseException;
import com.braincao.exception.SeckillException;
import com.braincao.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;

/**
 * @FileName: SeckillServiceImpl
 * @Author: braincao
 * @Date: 2018/11/27 15:40
 * @Description: Service层接口的实现
 * md5:对任意长度字符串返回一个特定长度的加密编码,不可逆
 * 所有编译期异常转化为运行期异常,这样spring声明式事务会帮我们做roll back
 */
//@Component / @Service / @Dao / @Controller
@Service
public class SeckillServiceImpl implements SeckillService {

    //slf4j规范接口的日志
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    //spring注入Service依赖:spring注入的DAO层对象 @Autowired / @Resource / @Inject
    @Autowired
    private SeckillDao seckillDao;

    //spring注入的DAO层对象
    @Autowired
    private SuccessKilledDao successKilledDao;

    //盐值字符串,用于混淆md5。越复杂越好,用户猜不到
    private final String slat = "asdc#$!EFSD$#%$GWVDSQ#!$#%$#T~~@#$^GV";

    //对seckillId生成md5的过程
    private String getMD5(long seckillId){
        String base = seckillId + "/" + slat;
        String md5 = DigestUtils.md5DigestAsHex(base.getBytes());//用base二进制生成md5
        return md5;
    }

    //查询所有秒杀商品
    @Override
    public List<Seckill> getSeckillList() {
        return seckillDao.queryAll(0,4);
    }

    //根据id查询一个秒杀商品
    @Override
    public Seckill getById(long seckillId) {
        return seckillDao.queryById(seckillId);
    }

    //用户根据id查询该秒杀商品的秒杀接口地址时:
    //秒杀开启时输出秒杀接口地址,未开启时输出系统时间+秒杀开启时间
    @Override
    public Exposer exportSeckillUrl(long seckillId) {
        Seckill seckill = seckillDao.queryById(seckillId);//拿到目标的秒杀商品
        //1.如果目标商品为null
        if(seckill==null){
            return new Exposer(false, seckillId);
        }
        Date startTime = seckill.getStartTime();
        Date endTime = seckill.getEndTime();
        Date date = new Date(); //系统的当前时间

        //2.如果秒杀未开始或已经结束
        if(date.getTime()<startTime.getTime() || date.getTime()>endTime.getTime()){
            return new Exposer(false,seckillId,date.getTime(),startTime.getTime(),endTime.getTime());
        }
        //3.如果秒杀开始,暴露接口地址
        String md5 = getMD5(seckillId);
        return new Exposer(true, md5, seckillId);
    }

    //执行秒杀操作,使用注解来声明式事务
    @Override
    @Transactional
    public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, SeckillCloseException, RepeatKillException {
        //md5验证,如果不对则抛出异常,秒杀失败,防止用户私自篡改
        if(md5==null || !md5.equals(getMD5(seckillId))){
            throw new SeckillException("seckill data rewrite");
        }

        //执行秒杀逻辑:减库存 + 记录购买行为,对里面抛出的异常进行try/catch并记录到日志,汇总后向外只抛一个总异常就好
        Date date = new Date();
        try{
            //减库存
            int updateCount = seckillDao.reduceNumber(seckillId, date);
            if(updateCount<=0){//减库存失败,秒杀结束
                throw new SeckillCloseException("seckill is closed");
            }else{
                //减库存成功,记录购买行为
                int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
                if(insertCount<=0){//重复秒杀
                    throw new RepeatKillException("Repeat seckill");
                }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声明式事务会帮我们做roll back
            throw new SeckillException("seckill inner error: " + e.getMessage());
        }
    }
}

6.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/mvc/spring-tx.xsd">

    <!--扫描service包下所有使用注解的类型 @Component @Service @Dao @Controller @自定义-->
    <context:component-scan base-package="com.braincao.service"/>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据库连接池-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

</beans>

7.logback.xml

resources下的logback.xml,是实现slf4j接口规范的日志管理。配置示例可以从logback官网上找到。

<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="true">

    <!--打印格式:默认往ConsoleAppender控制台打印,打印格式如下配置-->
    <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>

    <!--打印级别,默认为debug-->
    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

8.单元测试

DAO层–SeckillDaoTest.java:

package com.braincao.dao;
import com.braincao.entity.Seckill;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import static org.junit.Assert.*;


//配置spring和junit整合(@RunWith),这样junit启动时会加载springIOC容器,拿到相应的bean
@RunWith(SpringJUnit4ClassRunner.class)
//告诉junit spring的配置文件(这里测试dao,是spring-dao.xml)
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class SeckillDaoTest {

    //用resource注解来注入seckillDao依赖
    @Resource
    private SeckillDao seckillDao;

    @Test
    public void queryById() {
        long id = 1000;
        Seckill seckill = seckillDao.queryById(id);
        System.out.println(seckill.getName());
        System.out.println(seckill);
    }

    @Test
    public void queryAll() {
        List<Seckill> seckills = seckillDao.queryAll(0,100);
        //这里注意:java没有保存形参的记录,多个参数时会将offset->arg0,
        // 因此在dao层接口中参数前需要加@Param("offset"),否则会报错
        for(Seckill seckill: seckills){
            System.out.println(seckill);
        }
    }

    @Test
    public void reduceNumber() {
        Date createTime = new Date();
        int updateCount = seckillDao.reduceNumber(1000L,createTime);
        System.out.println(updateCount);
    }

service层(带logger输出控制台)---SeckillServiceTest.java:

package com.braincao.service;

import com.braincao.dto.Exposer;
import com.braincao.dto.SeckillExecution;
import com.braincao.entity.Seckill;
import com.braincao.exception.RepeatKillException;
import com.braincao.exception.SeckillCloseException;
import org.junit.Test;
import org.junit.runner.RunWith;
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;

//配置spring和junit整合(@RunWith),这样junit启动时会加载springIOC容器,拿到相应的bean
@RunWith(SpringJUnit4ClassRunner.class)
//告诉junit spring的配置文件(这里测试dao,是spring-dao.xml)
@ContextConfiguration({"classpath:spring/spring-dao.xml",
                        "classpath:spring/spring-service.xml"})
public class SeckillServiceTest {

    //日志,因为测试service方法时存在秒杀失败抛出异常,所以把这些输出放在日志中
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    //用@Autowired注解来注入seckillService依赖
    @Autowired
    private SeckillService seckillService;

    //查询所有秒杀商品
    @Test
    public void getSeckillList() {
        List<Seckill> seckillList = seckillService.getSeckillList();
        logger.info("list={}", seckillList);//{}是占位符,把后面的object放到占位符中
    }

    //根据id查询一个秒杀商品
    @Test
    public void getById() {
        long id = 1000;
        Seckill seckill = seckillService.getById(id);
        logger.info("seckill={}", seckill);
    }


    //集成测试代码完整逻辑,将接口地址暴露和执行秒杀操作两个一起集成测试,
    // 这样md5值就中间可用,可重复执行测试
    @Test
    public void testSeckillLogic() throws Exception {
        long id = 1000;
        Exposer exposer = seckillService.exportSeckillUrl(id);
        if(exposer.isExposed()){
            logger.info("exposer={}", exposer);
            long userPhone = 15652965935L;
            String md5 = exposer.getMd5();
            try{
                SeckillExecution seckillExecution = seckillService.executeSeckill(id, userPhone, md5);
                logger.info("seckillResult={}", seckillExecution);
            }catch (RepeatKillException e){
                logger.error(e.getMessage());
            }catch (SeckillCloseException e){
                logger.error(e.getMessage());
            }
        }
        else{
            //秒杀未开启
            logger.warn("exposer={}", exposer);
        }
    }
}

9.另一种手写的单元测试

UintTestBase:

import org.junit.After;
import org.junit.Before;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.util.StringUtils;

public class UnitTestBase {

    private ClassPathXmlApplicationContext context;
    private String stringXmlPath;

    public UnitTestBase() {
    }

    public UnitTestBase(String stringXmlPath) {
        this.stringXmlPath = stringXmlPath;
    }

    @Before
    public void before() {

        if (StringUtils.isEmpty(this.stringXmlPath)) {
            stringXmlPath = "classPath*:spring-*.xml";
        }
        try {//context就是一个bean容器
            context = new ClassPathXmlApplicationContext(stringXmlPath.split("[,\\s]+"));
            context.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @After
    public void after() {
        context.destroy();
    }

    @SuppressWarnings("unchecked")
    protected <T extends Object> T getBean(String beanId) {
        return (T) context.getBean(beanId);
    }

    protected <T extends Object> T getBean(Class<T> clazz) {
        return (T) context.getBean(clazz);
    }
}

AppTest:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.BlockJUnit4ClassRunner;
import sevice.AccountService;

@RunWith(BlockJUnit4ClassRunner.class)
public class AppTest extends UnitTestBase
{

    public AppTest(){
        super("classpath:applicationContext.xml");
    }

    @Test
    public void testJdbcDaoSupport(){
        AccountService accountService = super.getBean("accountService");
        accountService.transfer("aaa", "bbb", 50);
    }
}

##10.Spring-MVC配置

SpringMVC是基于DispatcherServlet的,DispatcherServlet是继承自HttpServlet的,HttpServlet是在web.xml文件中声明的。关于Spring-MVC我们需要配置两个东西。

1.resources包下的spring-web.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:mvc="http://www.springframework.org/schema/mvc"
       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/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--配置整合springMVC过程,classpath就是java与resources下面的-->

    <!--1.开启springMVC注解模式-->
    <!--简化配置:
        1)自动注册DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter
        2)提供一系列:数据绑定,数字和日期的format,@NumberFormat,@DataTimeFormat,
        xml,json默认读写支持。
    -->
    <mvc:annotation-driven/>

    <!--2.静态资源默认sevlet配置
            1)加入对静态资源的处理:js,gif,png
            2)允许使用"/"做整体映射
    -->
    <mvc:default-servlet-handler/>

    <!-- 3.配置ViewResolver,这里配置jsp格式的。
          对转向页面的路径解析,spring mvc的这个ViewResolver就是将相同的后端数据给前端不同的呈现(jsp格式数据\JSON格式数据等)。
        可以用多个ViewResolver,使用order属性排序,InternalResourceViewResolver放在最后
    -->
    <!--json格式的,但这里先不用-->
    <!--<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">-->
        <!--<property name="order" value="1" />-->
        <!--<property name="mediaTypes">-->
            <!--<map>-->
                <!--<entry key="json" value="application/json" />-->
                <!--<entry key="xml" value="application/xml" />-->
                <!--<entry key="htm" value="text/html" />-->
            <!--</map>-->
        <!--</property>-->

        <!--<property name="defaultViews">-->
            <!--<list>-->
                <!--&lt;!&ndash; JSON View &ndash;&gt;-->
                <!--<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"></bean>-->
            <!--</list>-->
        <!--</property>-->
        <!--<property name="ignoreAcceptHeader" value="true" />-->
    <!--</bean>-->

    <!--jsp格式的,InternalResourceViewResolver放在最后-->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/jsps/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!--4.扫描web相关的bean,就是自己开发的Controller-->
    <context:component-scan base-package="com.braincao.web"/>

</beans>

2.web.xml

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1"
         metadata-complete="true">
  <!--注意这里要把maven新建项目时自动生成的web.xml头部替换成上面的头部,修改servlet版本为3.1,这样就能自动支持spring EL-->

  <!--修改-->
  <display-name>Spring MVC Demo Study</display-name>

  <!--Spring MVC核心: 配置DispatcherServlet,不同的拦截需求可以有多个DispatcherServlet-->
  <servlet>
    <servlet-name>seckill-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 可以像如下自定义servlet.xml配置文件的位置和名称,默认为WEB-INF目录下,名称为[<servlet-name>]-servlet.xml,如spring-servlet.xml-->
    <!--配置springMVC需要加载的配置文件
        spring-dao.xml,spring-service.xml,spring-web.xml
        mybatis->spring->springMVC
    -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring/spring-*.xml</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>seckill-dispatcher</servlet-name>
    <!--默认匹配所有请求,并映射到此dispatcher-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <!--&lt;!&ndash;Spring应用上下文,理解层次化的ApplicationContext&ndash;&gt;-->
  <!--<context-param>-->
    <!--<param-name>contextConfigLocation</param-name>-->
    <!--<param-value>/WEB-INF/configs/applicationContext.xml</param-value>-->
  <!--</context-param>-->

  <!--&lt;!&ndash;Spring监听&ndash;&gt;-->
  <!--<listener>-->
    <!--<listener-class>-->
      <!--org.springframework.web.context.ContextLoaderListener-->
    <!--</listener-class>-->
  <!--</listener>-->


  <!--&lt;!&ndash;springmvc过滤器,作为处理乱码等问题的拦截器&ndash;&gt;-->
  <!--<filter>-->
    <!--<filter-name>encodingFilter</filter-name>-->
    <!--<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>-->
    <!--<init-param>-->
      <!--<param-name>encoding</param-name>-->
      <!--<param-value>utf8</param-value>-->
    <!--</init-param>-->
  <!--</filter>-->

  <!--<filter-mapping>-->
    <!--<filter-name>encodingFilter</filter-name>-->
    <!--&lt;!&ndash;匹配所有请求&ndash;&gt;-->
    <!--<url-pattern>*</url-pattern>-->
  <!--</filter-mapping>-->

</web-app>

11.Controller层

实现Restful接口的Controller层:接受请求与参数,跳转页面的控制,并且返回参数都是已经dto传输层封装好的类型

SeckillController.java:

package com.braincao.web;

import com.braincao.dto.Exposer;
import com.braincao.dto.SeckillExecution;
import com.braincao.dto.SeckillResult;
import com.braincao.entity.Seckill;
import com.braincao.enums.SeckillStateEnum;
import com.braincao.exception.RepeatKillException;
import com.braincao.exception.SeckillCloseException;
import com.braincao.service.SeckillService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.Date;
import java.util.List;

/**
 * @FileName: SeckillController
 * @Author: braincao
 * @Date: 2018/11/28 20:16
 * @Description: 实现Restful接口的Controller层:接受请求与参数,跳转页面的控制,并且返回参数都是已经dto传输层封装好的类型
 */
@Controller
//url: 模块/资源/{id}/细分
@RequestMapping("/seckill")
public class SeckillController {

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

    @Autowired
    private SeckillService seckillService;

    //获取所有秒杀商品列表页
    @RequestMapping(value="/list", method = RequestMethod.GET)
    public String list(Model model){
        List<Seckill> seckillList = seckillService.getSeckillList();
        model.addAttribute("list", seckillList);
        //jsp页面 + model数据 = ModelAndView

        //跳转到/WEB-INF/jsps/list.jsp
        return "list";
    }

    //获取单个秒杀商品的详情页
    @RequestMapping(value="/{seckillId}/detail", method = RequestMethod.GET)
    public String detail(@PathVariable("seckillId") Long seckillId, Model model){
        if(seckillId==null){
            return "redirect:/seckill/list"; //重定向
        }
        Seckill seckill = seckillService.getById(seckillId);
        if(seckill==null){
            return "forward:/seckill/list";
        }
        model.addAttribute("seckill", seckill);
        //jsp页面 + model数据 = ModelAndView

        //跳转到/WEB-INF/jsps/detail.jsp
        return "detail";
    }

    //获取秒杀地址。接收ajaxPOST请求,返回json格式。produces解决乱码问题
    @RequestMapping(value = "/{seckillId}/exposer",
            method = RequestMethod.POST,
            produces = {"application/json;charset=UTF-8"})
    @ResponseBody
    public SeckillResult<Exposer> exposer(@PathVariable("seckillId")long seckillId){
        SeckillResult<Exposer> result;
        try{
            Exposer exposer = seckillService.exportSeckillUrl(seckillId);
            result = new SeckillResult<>(true, exposer);
        }catch (Exception e){
            logger.error(e.getMessage());
            result = new SeckillResult<>(false,e.getMessage());
        }

        return result;
    }

    //执行秒杀的ajax请求,返回json。CookieValue是通过cookie获取手机号,如果不存在也不报错,交给程序try/catch
    @RequestMapping(value = "/{seckillId}/{md5}/execution",
            method = RequestMethod.POST,
            produces = {"application/json;charset=UTF-8"})
    @ResponseBody
    public SeckillResult<SeckillExecution> exposer(
            @PathVariable("seckillId")Long seckillId,
            @PathVariable("md5") String md5,
            @CookieValue(value = "killPhone", required = false)Long userPhone){

        if(userPhone==null){
            return new SeckillResult<SeckillExecution>(false,"用户未注册");
        }
        try{
            SeckillExecution seckillExecution = seckillService.executeSeckill(seckillId,userPhone,md5);
            return new SeckillResult<SeckillExecution>(true,seckillExecution);
        }catch (RepeatKillException e) {
            SeckillExecution seckillExecution = new SeckillExecution(seckillId, SeckillStateEnum.REPEAT_KILL);
            return new SeckillResult<SeckillExecution>(true,seckillExecution);
        }catch (SeckillCloseException e){
            SeckillExecution seckillExecution = new SeckillExecution(seckillId, SeckillStateEnum.END);
            return new SeckillResult<SeckillExecution>(true,seckillExecution);
        }catch (Exception e){
            logger.error(e.getMessage(),e);
            SeckillExecution seckillExecution = new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
            return new SeckillResult<SeckillExecution>(true,seckillExecution);
        }
    }

    //前端获取系统时间,ajax请求
    @RequestMapping(value = "/time/now", method = RequestMethod.GET)
    @ResponseBody
    public SeckillResult<Long> time(){
        Date now = new Date();
        return new SeckillResult<Long>(true, now.getTime());
    }
}

12.jsp主页面 + .js

detail.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@include file="common/taglib.jsp"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%
    String path = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<html>
<head>
    <title>秒杀详情页</title>

    <!--静态包含jsp,将所引的jsp部分会合并过来-->
    <%@include file="common/head.jsp"%>
</head>

<body>

    <div class="container">
        <div class="panel panel-default text-center">
            <div class="panel-heading">
                <h1>${seckill.name}</h1>
            </div>
            <div class="panel-body">
                <h2 class="text-danger">
                    <!--显示time图标-->
                    <span class="glyphicon glyphicon-time"></span>
                    <!--展示倒计时-->
                    <span class="glyphicon" id="seckill-box"></span>
                </h2>
            </div>
        </div>
    </div>

    <!--登录弹出层,输入电话-->
    <div id="killPhoneModal" class="modal fade">
        <div class="modal-dialog">
            <div class="modal-content">

                <div class="modal-header">
                    <h3 class="modal-title text-center">
                        <span class="glyphicon glyphicon-phone"></span>
                        秒杀前请输入用户手机号
                    </h3>
                </div>

                <div class="modal-body">
                    <div class="row">
                        <div class="col-xs-8 col-xs-offset-2">
                            <input type="text" name="killPhone" autofocus class="form-control" id="killPhoneKey" placeholder="填手机号"/>
                        </div>
                    </div>
                </div>

                <div class="modal-footer">
                    <!--验证信息-->
                    <span id="killPhoneMessage" class="glyphicon"></span>
                    <button type="button" id="killPhoneButton" class="btn btn-success">
                        <span class="glyphicon glyphicon-phone"></span>
                        Submit
                    </button>
                </div>

            </div>
        </div>
    </div>

</body>
<!--引入的插件用cdn链接:cookie插件、倒计时插件-->
<script type="text/javascript" src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery.countdown/2.2.0/jquery.countdown.min.js"></script>

<script type="text/javascript" src="<%=path%>/resources/js/seckill.js"></script>
<script type="text/javascript">
    $(function(){
        //使用EL表达式传入参数,初始化页面
        seckill.detail.init({
            seckillId: ${seckill.seckillId},
            startTime: ${seckill.startTime.time}, //startTime是date类型,转成毫秒
            endTime: ${seckill.endTime.time}
        });
    });
</script>
</html>

seckill.js:

//存放主要交互逻辑js代码
//javascript模块化封装,不要写成一坨
var seckill = {
    //封装所有秒杀相关的url
    URL:{
        now:function(){
            return "/seckill/time/now"
        },

        exposer:function(seckillId){
            return "/seckill/" + seckillId + "/exposer";
        },

        execution:function(seckillId, md5){
            return "/seckill/" + seckillId + "/" + md5 + "/execution";
        }
    },

    //秒杀开始时的秒杀逻辑:获取秒杀地址,控制显示逻辑,执行秒杀
    handlerSeckill:function(seckillId, node){
        node.hide().html("<button class='btn btn-primary btn-lg' id='killBtn'>开始秒杀</button>");//隐藏按钮
        //通过ajaxPOST请求获取秒杀接口地址
        $.post(seckill.URL.exposer(seckillId), {}, function(result){
            if(result && result["success"]){
                var exposer = result["data"];
                if(exposer["exposed"]){
                    //开启秒杀
                    //获取秒杀地址
                    var md5 = exposer["md5"];
                    var killUrl = seckill.URL.execution(seckillId, md5);//拿到服务器返回的秒杀接口地址
                    console.log("killUrl: " + killUrl);//输出到控制台
                    //用one绑定而不是click的好处就是:只绑定一次点击事件
                    // 防止一个用户不停的点秒杀,都发送服务端造成崩
                    $("#killBtn").one("click",function(){
                        //执行秒杀请求
                        //1.点击后按钮变灰色,禁用按钮
                        $(this).addClass("disabled");
                        //2.发送ajaxPOST请求,执行秒杀
                        $.post(killUrl,{},function(result){
                            if(result && result["success"]){
                                var killResult = result["data"];
                                var state = killResult["state"];
                                var stateInfo = killResult["stateInfo"];
                                //3.显示秒杀结果
                                node.html("<span class='label label-success'>" + stateInfo + "</span>");
                            }
                        });
                    });
                    node.show();
                }
            }
            else{
                console.log("result: " + result);
            }
        });
    },

    //验证手机号
    validatePhone:function(phone){
      if(phone && phone.length===11 && !isNaN(phone)){
          return true;
      }else{
          return false;
      }
    },

    //倒计时的时间判断
    countdown: function(seckillId, nowTime, startTime, endTime){
        var seckillBox = $("#seckill-box");
        //时间判断
        if(nowTime>endTime){
            //秒杀结束
            seckillBox.html("秒杀结束!");
        }
        else if(nowTime<startTime){
            //秒杀未开始,计时事件绑定,用countdown插件倒计时
            var killTime = new Date(startTime+1000);
            seckillBox.countdown(killTime,function(event){//countdown插件的函数
                //时间格式
                var format = event.strftime("秒杀倒计时:%D天 %H时 %M分 %S秒");
                seckillBox.html(format);
                /*倒计时结束后回调事件*/
            }).on("finish.countdown", function(){
                //获取秒杀地址,控制显示逻辑,执行秒杀
                seckill.handlerSeckill(seckillId, seckillBox);
            });
        }
        else{
            //秒杀开始
            seckill.handlerSeckill(seckillId, seckillBox);
        }
    },

    //详情页秒杀逻辑
    detail:{
        //详情页初始化
        init:function(params){
            //详情页初始化:手机验证和登录 + 计时交互
            //在cookie中查找手机号
            var killPhone = $.cookie("killPhone");
            //1.验证手机号
            if(!seckill.validatePhone(killPhone)){
                //手机号不存在,显示弹出层,开始绑定phone
                var killPhoneModal = $("#killPhoneModal");
                killPhoneModal.modal({
                    show:true,
                    backdrop:"static", //禁止位置关闭:点别的地方关不掉
                    keyboard:false //关闭键盘事件:按esc关不掉
                });
                //监听回车事件:回车也同样进行下面的click
                document.onkeydown = function(e){
                    if(e.keyCode === 13){
                        $("#killPhoneButton").click();
                    }
                };
                $("#killPhoneButton").click(function(){
                    var inputPhone = $("#killPhoneKey").val();
                    console.log("inputPhone" + inputPhone);
                    if(seckill.validatePhone(inputPhone)){
                        //手机号写入cookie,名字为killPhone,值为用户输入的正确手机号,有效期为7天,只在/seckill路径下有效
                        $.cookie("killPhone", inputPhone, {expires:7,path:"/seckill"});
                        //刷新页面
                        window.location.reload();
                    }
                    else{//手机号填写错误,这个过程先hide再show,这样用户看不到中间渲染的过程,更好的体验
                        $("#killPhoneMessage").hide().html("<lable class='label label-danger'>手机号填写错误!</lable>").show(300);
                    }
                })
            }
            //2.已经登录,开始计时交互
            //通过ajaxGET请求获取服务器时间/time/now,ajax请求返回的数据是json格式的result
            var seckillId = params["seckillId"];
            var startTime = params["startTime"];
            var endTime = params["endTime"];
            $.get(seckill.URL.now(), {}, function(result){
                if(result && result["success"]){
                    var nowTime = result["data"];//拿到服务器时间
                    //时间判断,计时交互
                    seckill.countdown(seckillId,nowTime,startTime, endTime);
                }
                else{
                    console.log("result: " + result);
                }
            });
        }
    }
};

附上jsp主页面依赖的其他jsp:

head.jsp:

<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">

<!--移动设备自适应-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<!--响应式图像:可以让图像按比例缩放,不超过其父元素的尺寸-->
<!--img src="..." class="img-responsive" alt="响应式图像"-->

<script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>

<script type="text/javascript" src="<%=path%>/resources/js/bootstrap-table.js"></script>
<link rel="stylesheet" href="<%=path%>/resources/css/bootstrap-table.css"/>

taglib.jsp:

<!--引入jstl标签库-->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

欢迎转载,欢迎错误指正与技术交流,欢迎交友谈心

文章标题:ssm框架maven配置

文章字数:1.8k

本文作者:Brain Cao

发布时间:2018-03-21, 16:49:52

最后更新:2020-03-12, 14:34:42

原始链接:https://braincao.cn/2018/03/21/java-ssm-maven-build/

版权声明:本文为博主原创文章,遵循 BY-NC-SA 4.0 版权协议,转载请保留原文链接与作者。

目录
×

喜欢请收藏,疼爱就打赏