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 !"".equals(command.command.trim())">AND A.COMMAND = #{command.command}</if>
<if test="command.description!=null and !"".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 !"".equals(command.command.trim())">AND A.COMMAND = #{command.command}</if>
<if test="command.description!=null and !"".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 !"".equals(command.trim())">AND A.COMMAND = #{command}</if>
<if test="description!=null and !"".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 !"".equals(command.trim())">AND COMMAND = #{command}</if>
<if test="description!=null and !"".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>-->
<!--<!– JSON View –>-->
<!--<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>
<!--<!–Spring应用上下文,理解层次化的ApplicationContext–>-->
<!--<context-param>-->
<!--<param-name>contextConfigLocation</param-name>-->
<!--<param-value>/WEB-INF/configs/applicationContext.xml</param-value>-->
<!--</context-param>-->
<!--<!–Spring监听–>-->
<!--<listener>-->
<!--<listener-class>-->
<!--org.springframework.web.context.ContextLoaderListener-->
<!--</listener-class>-->
<!--</listener>-->
<!--<!–springmvc过滤器,作为处理乱码等问题的拦截器–>-->
<!--<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>-->
<!--<!–匹配所有请求–>-->
<!--<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 版权协议,转载请保留原文链接与作者。