美文网首页
seckill项目创建流程---WEB层

seckill项目创建流程---WEB层

作者: 24_yu | 来源:发表于2017-12-14 00:34 被阅读0次

1、首先引入springMVC

~、修改web.xml,引入springMVC的DispatcherServlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         id="WebApp_ID" version="3.0">
    <display-name>imoocDemo1</display-name>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>

    <!-- 修改servlet版本为3.1 -->
    <!-- 配置DispatcherServlet -->
    <servlet>
        <servlet-name>seckill-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 配置springMVC需要接在的配置文件
            spring-dao.xml,spring-service.xml,spring-web.xml
            Mybatis -》spring ->springMVC
         -->
        <!-- 此处如果不配置的话,spring会默认的去WEB-INF下去查找默认规范的配置文件 -->
        <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>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

2、编写Controller

Controller // 类似与@Service和@Component
@RequestMapping("/seckill") // url:/模块/资源/{id}/细分
public class SeckillController {

    //  private org.slf4j.Logger logger = LoggerFactory.getLogger(this.getClass());
    public SeckillController() {

    }

    @Autowired
    private SeckillService seckillService;

    /**
     * 进入秒杀列表
     *
     * @param model 模型数据,存放秒杀商品的信息
     * @return 秒杀列表详情页面
     */
    @RequestMapping(value = "/{showPage}/list", method = RequestMethod.GET)
    public String list(@PathVariable("showPage") int showPage, Model model) {

        System.out.println("经过controler来到list界面");

        List<Seckill> seckillList = seckillService.getSeckillList(showPage);
        int pageCount = seckillService.getPageCount();
        model.addAttribute("list", seckillList);
        model.addAttribute("pageCount", pageCount);

        return "list"; // WEN-INF/list.jsp

    }

    @RequestMapping(value = "/{showPage}/listPage", method = RequestMethod.GET)
    @ResponseBody
    public List<Seckill> pageList(@PathVariable("showPage") int showPage, Model model) {

        List<Seckill> seckillList = seckillService.getSeckillList(showPage);
        Iterator iterator = seckillList.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        System.out.println("=======================");
        model.addAttribute("list", seckillList);
        return seckillList; // WEN-INF/list.jsp

    }

    /**
     * 根据id搜索商品,展示商品
     *
     * @param seckillId
     * @param model
     * @return
     */
    @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);
        return "detail";
    }

    /**
     * 暴露秒杀的接口的方法
     *
     * @param seckillId
     * @return 根据用户秒杀的商品id进行业务逻辑判断,返回不同的json实体结果
     */
    @RequestMapping(value = "/{seckillId}/exposer", method = RequestMethod.POST)
    @ResponseBody // 告诉springmvc,返回的类型作为ajax输出
    public SeckillResult<Exposer> exposer(@PathVariable("seckillId") Long seckillId) {
        SeckillResult<Exposer> result;

        try {
            Exposer exposer = seckillService.exportSeckillUrl(seckillId);
            result = new SeckillResult<Exposer>(true, exposer);
        } catch (Exception e) {

//          logger.error(e.getMessage(), e);
            result = new SeckillResult<Exposer>(false, e.getMessage());
        }
        return result;
    }

    /**
     * 用户执行秒杀,在页面点击相关的秒杀连接,进入后获取对应的参数进行判断 返回响应的json实体结果,前端在进行处理
     *
     * @param seckillId 秒杀的商品,对应的秒杀时的id
     * @param md5       一个被盐值加密过的md5加密值
     * @param userPhone 参与秒杀用户的手机号码,当做账号密码使用
     * @return 参与秒杀的结果,为json数据
     */
    @RequestMapping(value = "/{seckillId}/{md5}/execution", method = RequestMethod.POST)
    @ResponseBody
    public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId") long seckillId,
                                                   @PathVariable("md5") String md5, @CookieValue(value = "userPhone", required = false) Long userPhone) {

        // 如果用户的手机号码为空,说明用户没有完成注册
        if (userPhone == null) {
            return new SeckillResult<>(false, "没有注册");
        }
        try {
            // 这里换成储存过程
            //SeckillExecution execution = seckillService.executeSeckill(seckillId, userPhone, md5);
            SeckillExecution execution = seckillService.executeSeckillProcedure(seckillId, userPhone, md5);
            return new SeckillResult<>(true, execution);
        } catch (RepeaKillException e1) {
            // 重复秒杀
            SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.REPEAT_KILL);
            return new SeckillResult<>(false, execution);
        } catch (SeckillCloseException e2) {
            // 秒杀关闭
            SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.END);
            return new SeckillResult<>(false, execution);
        } catch (SeckillException e) {
            // 不能判断的异常
            SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
            return new SeckillResult<>(false, execution);
        }
    }

    @RequestMapping(value = "/time/now", method = RequestMethod.GET)
    @ResponseBody
    public SeckillResult<Date> time() {
        Date date = new Date();
        return new SeckillResult<>(true, date);
    }
}

3、建立一个全局ajax请求返回类,返回json数据

com.seckill.dto.SeckillResult
package com.seckill.dto;

/**
 * 封装所有的ajax请求返回类型,方便返回json
 *
 * @author hyh47
 *
 */
public class SeckillResult<T> {
    
    //执行的结果
    private boolean success;
    //泛型数据
    private T data;
    //字符串类型的具体error
    private String error;

    public SeckillResult(boolean success, T data) {
        super();
        this.success = success;
        this.data = data;
    }

    public SeckillResult(boolean success, String error) {
        super();
        this.success = success;
        this.error = error;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getError() {
        return error;
    }

    public void setError(String error) {
        this.error = error;
    }

    @Override
    public String toString() {
        return "SeckillResult [success=" + success + ", data=" + data + ", error=" + error + "]";
    }

}

4、前台页面的编写

image.png

对于jQuery和Bootstrap的引入,可以使用本地路径方式和CDN链接两种引入方式

首先需要一个列表页面

<%@page import="com.fasterxml.jackson.annotation.JsonInclude.Include" %>
<%@ page language="java" import="java.util.*" pageEncoding="utf-8" %>
<%@include file="common/tag.jsp" %>

<html lang="zh-CN">
<head>
    <%@include file="common/head.jsp" %>
    <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
    <title>秒杀列表页面</title>
    <script type="text/javascript" src="<%=path%>/js/jquery-1.11.1.min.js"></script>
    <script type="text/javascript" src="<%=path%>/js/jquery.paginate.js"></script>
    <script type="text/javascript" src="<%=path%>/js/bootstrap.min.js"></script>

    <style>
        .demo {
            width: 580px;
            padding: 10px;
            margin: 10px auto;
            border: 1px solid #fff;
            background-color: #f7f7f7;
        }
    </style>
</head>
<body>
<div class="panel panel-default">
    <div class="panel-heading text-center">
        <h2>秒杀列表</h2>
    </div>
    <div class="panel-body">
        <table class="table table-striped">
            <thead>
            <tr>
                <th>名称</th>
                <th>库存</th>
                <th>开始时间</th>
                <th>结束时间</th>
                <th>创建时间</th>
                <th>详情页</th>
            </tr>
            </thead>
            <tbody>
            <div id="listTable">
                <c:forEach items="${list}" var="sk" varStatus="skStatus">
                    <tr>
                        <td id="name${skStatus.index}">${sk.name}</td>
                        <td id="number${skStatus.index}">${sk.number}</td>
                        <td id="startTime${skStatus.index}"><fmt:formatDate value="${sk.startTime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
                        <td id="endTime${skStatus.index}"><fmt:formatDate value="${sk.endTime }" pattern="yyyy-MM-dd HH:mm:ss"/></td>
                        <td id="createTime${skStatus.index}"><fmt:formatDate value="${sk.createTime }" pattern="yyyy-MM-dd HH:mm:ss"/></td>
                        <td><a id="seckillHref" class="btn btn-info" href="/seckill/${sk.seckillId}/detail" target="_blank">link</a></td>
                    </tr>
                </c:forEach>
            </div>
            </tbody>
        </table>
        <div id="paginationdemo" class="demo">
            <div id="demo4"></div>
        </div>
    </div>
</div>
<script type="text/javascript">

    $(function () {

        $("#demo4").paginate({
            count: ${pageCount},
            start: 1,
            display: 5,
            border: false,
            text_color: '#79B5E3',
            background_color: 'none',
            text_hover_color: '#2573AF',
            background_hover_color: 'none',
            images: false,
            mouse: 'press',
            onChange: function (page) {
                //$('._current','#paginationdemo').removeClass('_current').hide();
                //$('#p'+page).addClass('_current').show();
                $('._current', '#paginationdemo').text("page" + page);

                //此处使用ajax.post方法进行访问
                $.get("/seckill/" + page + "/listPage", {}, function (result) {
                    var temp;
                    for (var i = 4; i >= 0; i--) {
                        temp = result[i];
                        if (temp != null) {
                            var startTime = new Date(temp['startTime']);
                            var endTime = new Date(temp['endTime']);
                            var createTime = new Date(temp['createTime']);
                            var seckillId = temp['seckillId'];
                            console.log(seckillId);
                            $('#name' + i).text(temp['name']);
                            $('#number' + i).text(temp['number']);
                            $('#startTime' + i).text(startTime.toLocaleString());
                            $('#endTime' + i).text(endTime.toLocaleString());
                            $('#createTime' + i).text(createTime.toLocaleString());
                            document.getElementById("seckillHref").setAttribute("href", "/seckill/" + seckillId + "/detail");
                        } else {
                            $('#name' + i).text(null);
                            $('#number' + i).text(null);
                            $('#startTime' + i).text(null);
                            $('#endTime' + i).text(null);
                            $('#createTime' + i).text(null);
                        }
                    }
                });
            }
        });
    });
</script>
</body>
</html>

此页面中使用了jquery.paginate.js的分页插件http://www.jq22.com/jquery-info34
然后在前面部分对于一些通用的JSP设置和静态文件(CSS等)的导入,采用了JSP的静态导入:
<%@include file="common/tag.jsp" %>、<%@include file="common/head.jsp" %>

而相应的jsp如下:
tag.jsp

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

head.jsp:

<%
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
            + path + "/";
%>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 检测设备终端的宽度和高度 -->
<meta charset="utf-8">

<link rel="stylesheet" href="<%=path%>/css/bootstrap.min.css"
      type="text/css">
<link rel="stylesheet" href="<%=path%>/css/bootstrap-theme.min.css"
      type="text/css">
<link rel="stylesheet" href="<%=path%>/css/style.css" type="text/css">
点击商品详情后,需要的是商品秒杀详情页面:

detail.jsp:

<%@page import="com.fasterxml.jackson.annotation.JsonInclude.Include" %>
<%@ page language="java" import="java.util.*" pageEncoding="utf-8" %>
<%@include file="common/tag.jsp" %>

<html lang="zh-CN">
<head>
    <%@include file="common/head.jsp" %>
    <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
    <title>秒杀详情页面</title>
    <script type="text/javascript" src="<%=path%>/js/jquery-1.11.1.min.js"></script>
    <script type="text/javascript" src="<%=path%>/js/bootstrap.min.js"></script>
    <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="https://cdn.bootcss.com/bootstrap-modal/2.2.6/css/bootstrap-modal-bs3patch.min.css"></script>
    </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="userPhoneModal" 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>

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

        <div class="modal-footer">
            <span id="userPhoneMessage" class="glyphicon"></span>
            <button type="button" id="userPhoneBtn" class="btn btn-success">
                <span class="glyphicon glyphicon-phone"></span> 提交
            </button>
        </div>
    </div>
</div>
</body>
<script type="text/javascript" src="<%=path%>/js/seckill.js"></script>

<script type="text/javascript">
    //采用jquery自动加载$(function(){});
    //初始化init,init内部存在着秒杀的逻辑
    $(function () {
        //详情页初始化
        seckill.detail.init({
            //采用jstl获取需要的数据,作为init函数的参数
            //在函数内部获取参数的形式为: param['seckillId']
            seckillId: ${seckill.seckillId},
            startTime: ${seckill.startTime.time},//毫秒
            endTime: ${seckill.endTime.time}

        });
    });

</script>

</html>
detail.jsp中再执行过程中相应的一些js逻辑:
//存放主要交互逻辑js代码

/**
 * 使用模块化javascript
 */
var seckill = {
    //封装秒杀相关ajax的url
    URL: {
        now: function () {
            return "/seckill/time/now";
        },
        exposer: function (seckillId) {
            return "/seckill/" + seckillId + "/exposer";
        },
        execution: function (seckillId, md5) {
            return "/seckill/" + seckillId + "/" + md5 + "/execution";
        }

    },
    validatePhone: function (phone) {
        if (phone && phone.length == 11 && !isNaN(phone)) {
            return true;
        } else {
            return false;
        }
    },
    //详情页秒杀逻辑
    detail: {
        //详情页初始化
        init: function (params) {
            //手机验证和登录,计时交互
            //规划系列交互流程
            //在cookies中查找手机号
            var userPhone = $.cookie("userPhone");

            if (!seckill.validatePhone(userPhone)) {
                console.log("为填写手机号码");
                var userPhoneModal = $("#userPhoneModal");
                /*
                modal的选项:
                backdrop(boolean、或者String'static')指定一个静态场景,
                当用户点击模态框外部时不会关闭模态框
                keyboard(boolean):当按下escape键时关闭模态框,设置为false时则按键失效
                show(boolean):当初始化时显示模态框
                 */
                userPhoneModal.modal({
                    show: true, //显示弹出层
                    backdrop: 'static', //精致位置关闭
                    keyboard: false //关闭键盘事件
                });
                /*
                $.cookie(名称,值,[option])
                 [option]参数说明:
                 expires:有限日期,可以是一个整数或一个日期(单位:天).默认关闭浏览器则丢失
                 path:cookie值保存的路径,默认与创建页路径一致
                  domin:cookie域名属性,默认与创建页域名一样。这个地方要相当注意,跨域的概念,如果要主域名二级域名有效则要设置  ".xxx.com"
                   secrue:一个布尔值,表示传输cookie值时,是否需要一个安全协议。
                 */
                $("#userPhoneBtn").click(function () {
                    console.log("提交手机号码按钮被点击");
                    var inputPhone = $("#userPhoneKey").val(); //获得输入的手机号码
                    console.log("inputPhone" + inputPhone);
                    if (seckill.validatePhone(inputPhone)) {
                        //把手机号码写入cookie
                        $.cookie('userPhone', inputPhone, {
                            //expires : 7,//有效日期,默认为关闭浏览器则失效
                            path: '/seckill'
                        })
                        window.location.reload();
                    } else {
                        $("#userPhoneMessage").hide().html("<label class='label label-danger'>手机号码错误</label>").show(1000);
                    }
                });

            } else {
                console.log("在cookie中找到了手机号码");
                //已经登录了就开始进行计时交互
                var startTime = params['startTime'];
                var endTime = params['endTime'];
                var seckillId = params['seckillId'];
                console.log("开始秒杀时间:" + startTime + "  时间格式:" + startTime.time);
                console.log("结束秒杀时间:" + endTime);
                $.get(seckill.URL.now(), function (result) {
                    if (result && result['success']) {
                        var nowTime = result['data'];
                        console.log("服务器当前时间:" + nowTime);
                        seckill.countdown(seckillId, nowTime, startTime, endTime);
                    } else {
                        console.log("结果:" + result);
                    }
                });
            }
            ;
        }
    },
    handlerSeckill: function (seckillId, mode) {
        //获得秒杀地址
        mode.hide().html('<button class="btn btn-primary btn-lg" id="killBtn">开始秒杀</button>');
        console.debug("开始进行秒杀地址获取");
        $.post(seckill.URL.exposer(seckillId), {}, function (result) {
            //在回调函数中,执行交互流程

            if (result && result['success']) {
                var exposer = result['data'];
                if (exposer['exposed']) {
                    console.log("有秒杀地址接口");
                    //开启秒杀,获取秒杀地址
                    var md5 = exposer['md5'];
                    var killUrl = seckill.URL.execution(seckillId, md5);
                    console.log("秒杀的地址为:" + killUrl);
                    //绑定一次点击事件,使用one,防止用户连续点击按钮,连续发送按钮请求
                    $("#killBtn").one('click', function () {
                        console.log("开始进行秒杀,按钮被禁用");
                        //执行秒杀请求,先禁用按钮
                        $(this).addClass("disabled");
                        //发送秒杀请求
                        $.post(killUrl, {}, function (result) {
                            console.info(result);
                            var killResult = result['data'];
                            console.log(killResult['seckillId']);
                            var state = killResult['state'];
                            var stateInfo = killResult['stateInfo'];
                            console.log("秒杀状态:" + stateInfo);
                            //显示秒杀结果
                            mode.html('<span class="label label-success">' + stateInfo + '</span>');
                        });
                    });
                    mode.show();
                } else {
                    console.warn("还没有暴露秒杀地址接口,无法进行秒杀");
                    //未开启秒杀
                    var now = exposer['now']; //
                    var start = exposer['start'];
                    var end = exposer['end'];
                    //重新计算计时逻辑
                    seckill.countdown(seckillId, now, start, end);
                }
            } else {
                console.log("result:" + result);
            }
        });

    },
    //倒计时交互
    countdown: function (seckillId, nowTime, startTime, endTime) {
        console.log("秒杀的商品ID:" + seckillId + ",服务器当前时间:" + nowTime + ",开始秒杀的时间:" + startTime + ",结束秒杀的时间" + endTime);
        var seckillBox = $("#seckill-box");
        //获取时间戳进行实践的比较
        nowTime = new Date(nowTime).valueOf();
        startTime = new Date(startTime).valueOf();
        endTime = new Date(endTime).valueOf();
        console.log("转换后的Date类型当前时间戳" + nowTime);
        console.log("转换后的Date类型开始时间戳" + startTime);
        console.log("转换后的Date类型结束时间戳" + endTime);
        if (nowTime < endTime && nowTime > startTime) {
            console.log("秒杀可以开始,时间条件符合");
            seckill.handlerSeckill(seckillId, seckillBox);
        }
        else if (nowTime > endTime) {
            console.log("秒杀时间已经结束");
            seckillBox.html("秒杀结束");
        } else if (nowTime < startTime) {
            console.log("秒杀还没有开始");
            //秒杀未开启,计时事件绑定
            var killTime = new Date(startTime + 1000);
            console.log(killTime);
            console.log("开始计时效果");
            seckillBox.countdown(killTime, function (event) {
                //事件格式
                var format = event.strftime('秒杀倒计时:%D天  %H时 %M分 %S秒');
                console.log(format);
                seckillBox.html(format);
                /*时间完成后回调事件*/
            }).on('finish.countdown', function () {
                console.log("准备执行回调,获取秒杀地址,执行秒杀");
                console.log("倒计时结束");
                seckill.handlerSeckill(seckillId, seckillBox);
            });
        }
    }
}
采用restful格式的url,以及js代码编程风格,模块化js

5、在idea中启动tomcat进行测试

相关文章

网友评论

      本文标题:seckill项目创建流程---WEB层

      本文链接:https://www.haomeiwen.com/subject/gwytwxtx.html