高并發(fā)秒殺API(五)

前言

本篇將完成前端頁面的設(shè)計(jì)與開發(fā),包括:

  • 使用Bootstrap開發(fā)頁面結(jié)構(gòu)
  • 交互邏輯編程

一、使用Bootstrap開發(fā)頁面結(jié)構(gòu)

在設(shè)計(jì)SeckillController中我們已經(jīng)設(shè)置了jsp文件的路徑,在/WEB-INF/新建一個(gè)jsp目錄,在該目錄下新建list.jsp和detail.jsp

使用Bootstrap的模板鸟废,這個(gè)模板基本上是固定的

<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
   <head>
      <title>Bootstrap 模板</title>
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <!-- 引入 Bootstrap -->
      <link  rel="stylesheet">
 
      <!-- HTML5 Shim 和 Respond.js 用于讓 IE8 支持 HTML5元素和媒體查詢 -->
      <!-- 注意: 如果通過 file://  引入 Respond.js 文件,則該文件無法起效果 -->
      <!--[if lt IE 9]>
         <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
         <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
      <![endif]-->
   </head>
   <body>
      <h1>Hello, world!</h1>
 
      <!-- jQuery (Bootstrap 的 JavaScript 插件需要引入 jQuery) -->
      <script src="https://code.jquery.com/jquery.js"></script>
      <!-- 包括所有已編譯的插件 -->
      <script src="js/bootstrap.min.js"></script>
   </body>
</html>

1姑荷、list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<!-- 引入jstl -->
<%@ include file="common/tag.jsp" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
   <head>
      <title>秒殺列表頁</title>
      <%@ include file="common/head.jsp" %>
   </head>
   <body>
   </body>
   
<!-- jQuery文件盒延。務(wù)必在bootstrap.min.js 之前引入 -->
<script src="http://cdn.static.runoob.com/libs/jquery/2.1.1/jquery.min.js"></script>
     
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</html>

在最上面的jsp內(nèi)置對(duì)象page中的contentType修改為UTF-8缩擂,這個(gè)模板已經(jīng)引入了一些文件包含了 jquery.js、bootstrap.min.js 和 bootstrap.min.css 文件添寺,用于讓一個(gè)常規(guī)的 HTML 文件變?yōu)槭褂昧薆ootstrap的模板

最下面有兩個(gè)script標(biāo)簽胯盯,通過CDN加載一些Bootstrap資源,** JavaScript有一個(gè)先后引入規(guī)則计露,jQuery作為Bootstrap的底層依賴博脑,要先于Bootstrap聲明 **,這兩個(gè)script標(biāo)簽在上面介紹的網(wǎng)站上都有

這里有些通用的標(biāo)簽以及要引入的文件都單獨(dú)提取出來薄坏,不用把這些相同的代碼都寫在每一個(gè)頁面中

在jsp目錄下新建一個(gè)common目錄趋厉,專門存放通用的jsp文件

新建一個(gè)tag.jsp,用于引入jstl胶坠,如果以后還要引入別的標(biāo)簽君账,再添加

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

新建一個(gè)head.jsp,head標(biāo)簽中的內(nèi)容所有頁面基本都一樣

<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link  rel="stylesheet">
 
<!-- HTML5 Shim 和 Respond.js 用于讓 IE8 支持 HTML5元素和媒體查詢 -->
<!-- 注意: 如果通過 file://  引入 Respond.js 文件沈善,則該文件無法起效果 -->
<!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
    <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->

然后使用jsp的內(nèi)置對(duì)象include乡数,靜態(tài)引入head.jsp,** 靜態(tài)包含 是會(huì)把引入的文件合并過來 闻牡,也就是head.jsp中的內(nèi)容會(huì)放到外層list.jsp中作為一個(gè)Servlet輸出净赴,如果是 動(dòng)態(tài)包含 的話,那么head.jsp會(huì)作為一個(gè) 獨(dú)立的jsp罩润,先轉(zhuǎn)換為Servlet **玖翅,轉(zhuǎn)換后的結(jié)果再和list.jsp合并

接著開始編寫lsit.jsp的細(xì)節(jié)部分

list.jsp

panel-defaulttext-center都是使用Bootstrap提供的樣式

在panel-body中使用表格割以,通過jstl提供的方法來顯示要展示的秒殺商品

<thead>
    <tr>
        <th>名稱</th>
        <th>庫存</th>
        <th>開始時(shí)間</th>
        <th>結(jié)束時(shí)間</th>
        <th>創(chuàng)建時(shí)間</th>
        <th>詳情頁</th>
    </tr>
</thead>
<tbody>
    <c:forEach var="sk" items="${list}">
     <tr>
        <td>${sk.name}</td>
        <td>${sk.number}</td>
        <td>
            <fmt:formatDate value="${sk.startTime}" pattern="yyyy-MM-dd HH:mm:ss"/>             
        </td>
        <td>
            <fmt:formatDate value="${sk.endTime}" pattern="yyyy-MM-dd HH:mm:ss"/>               
        </td>
        <td>
            <fmt:formatDate value="${sk.createTime}" pattern="yyyy-MM-dd HH:mm:ss"/>                
        </td>
        <td>
            <a class="btn btn-info" href="/seckill/${sk.seckillId}/detail" target="_blank">link</a>             
        </td>
     </tr>
    </c:forEach>
</tbody>

首先使用jstl的c:forEach標(biāo)簽金度,用來迭代從SeckillController中的list方法傳過來的"list",這個(gè)list是存放秒殺的商品严沥,屬性var代表當(dāng)前項(xiàng)目的變量名猜极,items表示進(jìn)行循環(huán)的項(xiàng)目

一個(gè)tr標(biāo)簽是一行,每個(gè)td標(biāo)簽是一列消玄,數(shù)據(jù)庫有多少個(gè)秒殺商品這個(gè)表格就有多少行

@RequestMapping(value = "/list", method = RequestMethod.GET)
public String list(Model model){
        
    //獲取列表頁
    List<Seckill> list = seckillService.getSeckillList();
    model.addAttribute("list", list);
    return "list";
        
}

從SeckillController的list方法返回的是字符串跟伏,但是之前說過,Spring MVC會(huì)拼接成一個(gè)URL地址翩瓜,返回的數(shù)據(jù)是個(gè)泛型受扳,類型是Seckill

public class Seckill {
    
    private long seckillId;
    
    private String name;
    
    private int number;
    
    private Date startTime;
    
    private Date endTime;
    
    private Date createTime;
}

這是Seckill定義的屬性,所以在list.jsp頁面中通過sk.name來調(diào)用相關(guān)的參數(shù)

日期類型的輸出默認(rèn)是直接調(diào)用日期類型的toString奥溺,這不符合我們的規(guī)范辞色,所以使用jstl的fmt:formatDate標(biāo)簽來格式化輸出的時(shí)間

最后一列給一個(gè)超鏈接,用于鏈接這個(gè)秒殺商品的詳情頁,可以把這個(gè)超鏈接做成一個(gè)按鈕相满,使用的也是Bootstrap的CSS

2层亿、detail.jsp

detail.jsp

這是detail.jsp的一個(gè)大的框架,先是由兩個(gè)div組成立美,一個(gè)用于顯示日期或者文本的一個(gè)顯示面板匿又,在顯示面板中做一個(gè)埋點(diǎn),因?yàn)檫@個(gè)面板在之后的交互邏輯編碼中建蹄,在不同時(shí)間顯示的是不同的內(nèi)容

<h1>${seckill.name }</h1>

這里可以直接這樣寫的原因是:

model.addAttribute("seckill", seckill);//SeckillController中的detail方法

另一個(gè)div就是登錄彈出層碌更,在進(jìn)入詳情頁的時(shí)候,會(huì)通過Cookie判斷用戶時(shí)候登錄洞慎,沒有登錄的用戶的頁面會(huì)顯示這個(gè)登錄彈出層痛单,提示用戶登錄

detail.jsp中的登錄彈出層

首先在最外圍的div中進(jìn)行埋點(diǎn)

<div id="killPhoneModal" class="modal fade">

因?yàn)檫@個(gè)登錄彈出層不是每次用戶到詳情頁都要出現(xiàn),只有驗(yàn)證Cookie中沒有用戶登錄信息才會(huì)出現(xiàn)劲腿,所以在這里埋點(diǎn)旭绒,如果Cookie中有用戶的信息,在交互邏輯中我們會(huì)控制這個(gè)div不出現(xiàn)

登錄彈出層實(shí)際是一個(gè)模態(tài)框焦人,在頁面顯示的時(shí)候主要由三個(gè)部分:

  • modal-header:顯示一些文本
  • modal-body:用戶輸入登錄信息
  • modal-footer:登錄按鈕
<div class="modal-header">
    <h3 class="modal-title text-center">
        <span class="glyphicon glyphicon-phone"></span>秒殺電話:
    </h3>               
</div>

在modal-header中有個(gè)span面板用于顯示一些文本和圖標(biāo)

<div class="modal-body">
    <div class="row">
        <div class="col-xs-8 col-xs-offset-2">
            <input type="text" name="killPhone" id="killPhoneKey" 
                            placeholder="填寫手機(jī)號(hào)^o^" class="form-control">
        </div>
    </div>
</div>

在modal-body中有一個(gè)輸入框挥吵,這里需要在輸入框中進(jìn)行埋點(diǎn),之后的交互邏輯要通過這個(gè)埋點(diǎn)來獲取用戶輸入的信息

<div class="modal-footer">
    <!-- 驗(yàn)證信息 -->
    <span id="killPhoneMessage" class="glyphicon"></span>
    <button type="button" id="killPhoneBtn" class="btn btn-success">
        <span class="glyphicon glyphicon-phone"></span>
    </button>
</div>

在modal-footer中由兩部分組成:

  • span:顯示錯(cuò)誤信息
  • button:登錄按鈕

在button中也需要埋點(diǎn)花椭,用于綁定點(diǎn)擊事件

body標(biāo)簽中的內(nèi)容完成了忽匈,下面也要通過CDN引入一些文件

<!-- jQuery文件。務(wù)必在bootstrap.min.js 之前引入 -->
<script src="http://cdn.static.runoob.com/libs/jquery/2.1.1/jquery.min.js"></script>
     
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/js/bootstrap.min.js"></script>

<!-- 使用CDN獲取公共js  -->
<!-- jQuery cookie操作插件 -->
<script src="http://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.js"></script>
<!-- jQuery countDown倒計(jì)時(shí)插件 -->
<script src="http://cdn.bootcss.com/jquery.countdown/2.2.0/jquery.countdown.min.js"></script>

jquery文件和bootstrap.min.js之前在list.jsp也引入了

對(duì)Cookie的操作使用jQuery Cookie插件矿辽,倒計(jì)時(shí)使用jQuery的countDown插件

2丹允、交互邏輯

1、交互流程

前端頁面交互流程

當(dāng)用戶點(diǎn)擊某一個(gè)秒殺商品的按鈕的時(shí)候袋倔,會(huì)進(jìn)入到相應(yīng)的詳情頁嫌松,這個(gè)詳情頁會(huì)判斷用戶是否登錄過,如果登錄過就展示詳情頁頁面奕污,如果沒有登錄過,就彈出登錄彈出層液走,在用戶正確填寫登錄信息后就可以進(jìn)入詳情頁

詳情頁流程
  • 獲取標(biāo)準(zhǔn)系統(tǒng)時(shí)間碳默,因?yàn)橛脩艨赡芴幵诓煌臅r(shí)區(qū),用戶終端的時(shí)間也不可能完全一致缘眶,所以要統(tǒng)一地采用一個(gè)標(biāo)準(zhǔn)時(shí)間嘱根,也就是服務(wù)器時(shí)間

  • 通過秒殺商品的開始時(shí)間和結(jié)束時(shí)間來做出不同的判斷:

    • 系統(tǒng)時(shí)間大于結(jié)束時(shí)間:秒殺活動(dòng)已結(jié)束,在detail.jsp的顯示面板顯示“秒殺結(jié)束”字樣
    • 系統(tǒng)時(shí)間小于開始時(shí)間:秒殺活動(dòng)未開始巷懈,在detail.jsp的顯示面板顯示倒計(jì)時(shí)该抒,使用的是jQuery的countDown插件,倒計(jì)時(shí)完成后顶燕,會(huì)出現(xiàn)秒殺按鈕凑保,用戶可以執(zhí)行秒殺操作
    • 系統(tǒng)時(shí)間介于開始時(shí)間和結(jié)束時(shí)間之間:秒殺活動(dòng)正在進(jìn)行冈爹,直接出現(xiàn)秒殺按鈕,用戶可以執(zhí)行秒殺操作

2欧引、頁面展示

列表頁
登錄彈出層
可以秒殺
秒殺結(jié)束
秒殺未開始

3频伤、交互邏輯編程

在src/main/webapp目錄下新建一個(gè)resources文件夾,再在其中新建一個(gè)script文件夾芝此,用于存放腳本文件

創(chuàng)建一個(gè)seckill.js


seckill.js

這是最后完成的總覽憋肖,接著一步步來,整個(gè)seckil這樣寫的原因是模擬高級(jí)語言分包的概念婚苹,使JavaScript模塊化岸更,這樣當(dāng)調(diào)用一個(gè)方法可以用seckill.detail.init(params)的形式

在詳情頁初始化中,首先要做的就是獲取killPhone節(jié)點(diǎn)膊升,這個(gè)killPhone節(jié)點(diǎn)不是程序中具體的標(biāo)簽怎炊,而是Cookie中的用于標(biāo)識(shí)用戶信息的數(shù)據(jù),用戶的信息都放在Cookie中名為killPhone的節(jié)點(diǎn)

//在cookie中查找手機(jī)號(hào)
var killPhone = $.cookie('killPhone');
//驗(yàn)證手機(jī)號(hào)
if(!seckill.validatePhone(killPhone)){
    var killPhoneModal = $('#killPhoneModal');
    killPhoneModal.modal({
        show : true,//顯示登錄彈出層
        backdrop : 'static',//禁止位置關(guān)閉
        keyboard : false//關(guān)閉鍵盤事件
    });
    $('#killPhoneBtn').click(function(){
        var inputPhone = $('#killPhoneKey').val();
        if(seckill.validatePhone(inputPhone)){
            $.cookie('killPhone', inputPhone, {expires:7, path:'/seckill'});//手機(jī)號(hào)寫入cookie
            window.location.reload();//刷新頁面
        }else{
            $('#killPhoneMessage').hide().html('<label class="label label-danger">手機(jī)號(hào)錯(cuò)誤!</label>').show(300);
        }
    });
}

從Cookie的killPhone中獲取數(shù)據(jù)后用僧,就要驗(yàn)證手機(jī)號(hào)结胀,驗(yàn)證手機(jī)號(hào)的邏輯建議提取到更上層,因?yàn)榭赡芏鄠€(gè)地方都要用到

創(chuàng)建一個(gè)函數(shù)责循,名字為validatePhone糟港,這個(gè)函數(shù)的位置在這一節(jié)最開始的圖片上可以看到

//驗(yàn)證手機(jī)號(hào)
validatePhone : function(phone){
    if(phone && phone.length == 11 && !isNaN(phone)){
        return true;
    }else{
        return false;
    }
},

要驗(yàn)證手機(jī)號(hào),所以傳入一個(gè)手機(jī)號(hào)的參數(shù)院仿,這里使用if語句簡單的判斷一下

首先要判斷手機(jī)號(hào)是否為空秸抚,在js中直接傳入?yún)?shù),它會(huì)判斷這個(gè)參數(shù)是否為空歹垫,空的話就是undefine剥汤,就認(rèn)為是false

手機(jī)號(hào)長度必須為11位

isNaN是判斷這個(gè)參數(shù)是否是非數(shù)字,如果是非數(shù)字的話就是true排惨,所以這里要取反

接著就可以在init方法中調(diào)用validatePhone函數(shù)來驗(yàn)證手機(jī)號(hào)

if(!seckill.validatePhone(killPhone)){
    var killPhoneModal = $('#killPhoneModal');
    killPhoneModal.modal({
        show : true,//顯示登錄彈出層
        backdrop : 'static',//禁止位置關(guān)閉
        keyboard : false//關(guān)閉鍵盤事件
    });

如果手機(jī)號(hào)存在吭敢,就可以直接跳轉(zhuǎn)到詳情頁了,所以這里處理手機(jī)號(hào)不存在的情況暮芭,因?yàn)檫@個(gè)if語句中東西比較多鹿驼,所以分開來說,完整的代碼在前面已經(jīng)展示過了

手機(jī)號(hào)不存在辕宏,就需要用戶進(jìn)行綁定畜晰,之前在detail.jsp中也提前做好了一個(gè)登錄彈出層,并進(jìn)行了埋點(diǎn)

登錄彈出層

id為killPhoneModal瑞筐,在seckill.js中使用jQuery的選擇器可以取到這個(gè)節(jié)點(diǎn)

var killPhoneModal = $('#killPhoneModal');

這個(gè)登錄彈出層已經(jīng)不是單純的div了凄鼻,因?yàn)槭褂昧薆ootstrap的modal,它本身有一個(gè)modal的方法,向這個(gè)方法傳入json块蚌, 用于設(shè)置這個(gè)模態(tài)框的一些屬性

之前在detail.jsp中這個(gè)modal的屬性為fade闰非,是隱藏的,既然要讓用戶綁定手機(jī)號(hào)匈子,所以要把這個(gè)彈出層顯示出來

killPhoneModal.modal({
    show : true,//顯示登錄彈出層
    backdrop : 'static',//禁止位置關(guān)閉
    keyboard : false//關(guān)閉鍵盤事件

我們希望在用戶沒有正確的填寫手機(jī)號(hào)之前河胎,是不能關(guān)掉這個(gè)彈出層,所以把backdrop關(guān)掉虎敦,因?yàn)橛脩酎c(diǎn)擊其他區(qū)域可能把這個(gè)彈出層關(guān)掉游岳;通過鍵盤的ESC也可能關(guān)閉彈出層,所以要禁止鍵盤事件

彈出層顯示出來后其徙,要給按鈕做事件綁定

    $('#killPhoneBtn').click(function(){
        var inputPhone = $('#killPhoneKey').val();
        if(seckill.validatePhone(inputPhone)){
            $.cookie('killPhone', inputPhone, {expires:7, path:'/seckill'});//手機(jī)號(hào)寫入cookie
            window.location.reload();//刷新頁面
        }else{
            $('#killPhoneMessage').hide().html('<label class="label label-danger">手機(jī)號(hào)錯(cuò)誤!</label>').show(300);
        }
    });
}

按鈕事件綁定完成后整個(gè)驗(yàn)證手機(jī)號(hào)的if語句才完成了

對(duì)按鈕做綁定胚迫,首先就是要獲取到按鈕在詳情頁的節(jié)點(diǎn)

<div class="modal-footer">
    <!-- 驗(yàn)證信息 -->
    <span id="killPhoneMessage" class="glyphicon"></span>
    <button type="button" id="killPhoneBtn" class="btn btn-success">
        <span class="glyphicon glyphicon-phone"></span>
    </button>
</div>

可以看到,按鈕的節(jié)點(diǎn)為killPhoneBtn

當(dāng)用戶點(diǎn)擊了按鈕唾那,我們認(rèn)為用戶已經(jīng)填寫了在登錄彈出層的input

<div class="modal-body">
    <div class="row">
        <div class="col-xs-8 col-xs-offset-2">
            <input type="text" name="killPhone" id="killPhoneKey" 
                placeholder="填寫手機(jī)號(hào)^o^" class="form-control">
        </div>
    </div>
</div>

在input中访锻,之前已經(jīng)提前進(jìn)行了埋點(diǎn),id為killPhoneKey

在seckill.js中獲取到這個(gè)節(jié)點(diǎn)闹获,同時(shí)使用val()方法獲取到用戶輸入的內(nèi)容

var inputPhone = $('#killPhoneKey').val();

拿到用戶輸入的內(nèi)容期犬,還要再進(jìn)行驗(yàn)證,再調(diào)用用于驗(yàn)證手機(jī)號(hào)的函數(shù)validatePhone

if(seckill.validatePhone(inputPhone)){
     $.cookie('killPhone', inputPhone, {expires:7, path:'/seckill'});//手機(jī)號(hào)寫入cookie
     window.location.reload();//刷新頁面
}else{
     $('#killPhoneMessage').hide().html('<label class="label label-danger">手機(jī)號(hào)錯(cuò)誤!</label>').show(300);
}

如果驗(yàn)證通過了避诽,先將inputPhone的值也就是用戶輸入的手機(jī)號(hào)寫入Cookie中

  • expires:Cookie的有效期龟虎,單位是“天”
  • path:給出有效路徑,Cookie只在該路徑下有效

為什么path不寫全路徑沙庐?
因?yàn)楫?dāng)一些URL沒有用到這個(gè)Cookie的時(shí)候鲤妥,如果把Cookie中的path設(shè)置為全路徑,那么這個(gè)Cookie中的數(shù)據(jù)也會(huì)傳遞到后端拱雏,對(duì)后端處理會(huì)有一些影響棉安,所以這只這個(gè)killPhone只在seckill模塊下有效

然后就是刷新頁面,會(huì)重新調(diào)用detail屬性的init方法

如果驗(yàn)證沒有通過铸抑,在detail.jsp中登錄彈出層的modal-footer提前預(yù)留了一個(gè)span贡耽,用于顯示錯(cuò)誤信息

<span id="killPhoneMessage" class="glyphicon"></span>

同樣,在seckill.js中獲取到這個(gè)span節(jié)點(diǎn)

$('#killPhoneMessage').hide().html('<label class="label label-danger">手機(jī)號(hào)錯(cuò)誤!</label>').show(300);

對(duì)html標(biāo)簽進(jìn)行操作的時(shí)候鹊汛,通常是先隱藏一下菇爪,避免用戶看到中間過程,然后插入一些內(nèi)容柒昏,顯示的時(shí)候給一個(gè)時(shí)間,單位毫秒熙揍,這樣看起來有動(dòng)態(tài)的效果

插入的是label標(biāo)簽职祷,使用Bootstrap的CSS,這里顯示的文本沒有經(jīng)過處理,直接是寫死了有梆,實(shí)際的工作中這里應(yīng)該是要配合前端的數(shù)據(jù)字典是尖,根據(jù)不同的情況顯示不同的文本

至此,詳情頁初始化部分完成泥耀,也就是開頭的if語句

整個(gè)前端的流程基本完成


前端頁面交互流程

接著是詳情頁的流程


詳情頁流程

首先就是要獲取標(biāo)準(zhǔn)系統(tǒng)時(shí)間

所以在detail.jsp的最下面添加一些內(nèi)容饺汹,首先是要引入seckill.js

<!-- 開始編寫交互邏輯 -->
<script src="/resources/script/seckill.js" type="text/javascript"></script>

然后使用EL表達(dá)式傳入?yún)?shù)

<script type="text/javascript">
    $(function(){
        //使用EL表達(dá)式傳入?yún)?shù)
        seckill.detail.init({
            seckillId : "${seckill.seckillId}",
            startTime : "${seckill.startTime.time}",
            endTime : "${seckill.endTime.time}"
        });
        
    });
</script>

接著在seckill.js中獲取到這些參數(shù)

//已經(jīng)登錄
//計(jì)時(shí)交互邏輯
var startTime = parseInt(params['startTime']);
var endTime = parseInt(params['endTime']);
var seckillId = parseInt(params['seckillId']);
$.get(seckill.URL.now(), {}, function(result){
    if(result && result['success']){
        var nowTime = result['data'];
        //時(shí)間判斷,計(jì)時(shí)交互
        seckill.countdown(seckillId, nowTime, startTime, endTime);
    }else{
        console.log('result: ' + result);
    }
});

** 這里從列表頁傳遞過來的日期參數(shù)需要轉(zhuǎn)型痰催,否則之后會(huì)出現(xiàn)日期無效的情況 **

然后通過ajax請求來獲取到系統(tǒng)當(dāng)前時(shí)間

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

在SeckillController中的time方法就是用來獲取系統(tǒng)時(shí)間的兜辞,在@RequestMapping注解中顯示系統(tǒng)當(dāng)前時(shí)間的URL是“/time/now”,限制了請求方式為GET夸溶,所以在seckill.js中使用$.get()方法

簡單說下$.get()方法

$.get(URL,data,function(data,status,xhr),dataType)

  • URL:必需逸吵,規(guī)定您需要請求的 URL
  • data:可選,規(guī)定連同請求發(fā)送到服務(wù)器的數(shù)據(jù)
  • function(data,status,xhr):可選缝裁,規(guī)定當(dāng)請求成功時(shí)運(yùn)行的函數(shù)
    • data:包含來自請求的結(jié)果數(shù)據(jù)
    • status:包含請求的狀態(tài)("success"扫皱、"notmodified"、"error"捷绑、"timeout"韩脑、"parsererror")
    • xhr:包含 XMLHttpRequest 對(duì)象
  • dataType:可選,規(guī)定預(yù)期的服務(wù)器響應(yīng)的數(shù)據(jù)類型粹污,默認(rèn)地段多,jQuery 會(huì)智能判斷。
    可能的類型:
    • xml - 一個(gè) XML 文檔
    • html - HTML 作為純文本
    • text - 純文本字符串
    • script - 以 JavaScript 運(yùn)行響應(yīng)厕怜,并以純文本返回
    • json - 以 JSON 運(yùn)行響應(yīng)衩匣,并以 JavaScript 對(duì)象返回
    • jsonp - 使用 JSONP 加載一個(gè) JSON 塊,將添加一個(gè) "?callback=?" 到 URL 來規(guī)定回調(diào)
$.get(seckill.URL.now, {}, function(result){
    if(result && result['success']){
        var nowTime = result['data'];
        //時(shí)間判斷粥航,計(jì)時(shí)交互
        seckill.countdown(seckillId, nowTime, startTime, endTime);
    }else{
        console.log('result: ' + result);
    }
});

第一個(gè)參數(shù)是請求的URL琅捏,由于URL太多,為了后期維護(hù)递雀、代碼的整潔柄延,所以要對(duì)URL進(jìn)行統(tǒng)一的管理,在seckill中新建一個(gè)屬性URL缀程,用于封裝秒殺相關(guān)ajax的URL

//封裝秒殺相關(guān)ajax的URL 
URl : {
    now : function(){
        return '/seckill/time/now';
    }
},

在SeckillController中的time方法返回的是SeckillResult<Long>類型的對(duì)象

public class SeckillResult<T> {
    
    private boolean success;
    
    private T data;
    
    private String error;
}

這是SeckillResult中定義的屬性搜吧,其中success是判斷是否成功請求,所以在$.get()方法的回調(diào)函數(shù)中要判斷請求是否為空杨凑,如果不為空滤奈,則在控制臺(tái)輸出信息

if(result && result['success']){
   var nowTime = result['data'];
   //時(shí)間判斷,計(jì)時(shí)交互
   seckill.countdown(seckillId, nowTime, startTime, endTime);
}else{
   console.log('result: ' + result);
}

如果請求成功撩满,就可以獲取到系統(tǒng)當(dāng)前時(shí)間蜒程,再加上之前獲取到的三個(gè)參數(shù)绅你,就可以進(jìn)行時(shí)間判斷,判斷系統(tǒng)當(dāng)前時(shí)間在不在秒殺活動(dòng)期內(nèi)昭躺,如果不在是秒殺未開始還是秒殺已結(jié)束

在seckill中創(chuàng)建countdown函數(shù)忌锯,用于時(shí)間判斷

countdown : function(seckillId, nowTime, startTime, endTime){
    var seckillBox = $('#seckill-box');
    //時(shí)間判斷
    if(nowTime > endTime){
        //秒殺結(jié)束
        seckillBox.html('秒殺結(jié)束!');
    }else if(nowTime < startTime){
        //秒殺未開始,計(jì)時(shí)事件綁定
        var killTime = new Date(startTime + 1000);//設(shè)置基準(zhǔn)時(shí)間
        seckillBox.countdown(killTime, function(event){
            //時(shí)間格式
            var format = event.strftime('秒殺倒計(jì)時(shí): %D天 %H時(shí) %M分 %S秒');
            //時(shí)間完成后回調(diào)事件
        }).on('finish.countdown', function(){
            //調(diào)用執(zhí)行秒殺的函數(shù)
            seckill.handleSeckill(seckillId, seckillBox);
        });
    }else{
        //調(diào)用執(zhí)行秒殺的函數(shù)
        seckill.handleSeckill(seckillId, seckillBox);
    }
},

因?yàn)閷?duì)于時(shí)間判斷的不同結(jié)果领炫,要在詳情頁中展示不同的內(nèi)容偶垮,所以在detail.jsp中專門設(shè)置了一個(gè)span,用于顯示時(shí)間判斷的結(jié)果

<div class="panel-body">
    <h2 class="text-danger">
        <!-- 顯示time圖標(biāo) -->
        <span class="glyphicon glyphicon-time"></span> 
        <!-- 顯示面板 -->
        <span class="glyphicon" id="seckill-box"></span>
    </h2>
</div>

提前設(shè)置了埋點(diǎn)帝洪,id為seckill-box似舵,在seckill.js通過jQuery的加載器獲取到這個(gè)span節(jié)點(diǎn)

然后進(jìn)行時(shí)間判斷

if(nowTime > endTime){
    //秒殺結(jié)束
    seckillBox.html('秒殺結(jié)束!');
}

系統(tǒng)當(dāng)前時(shí)間大于秒殺的結(jié)束時(shí)間,說明秒殺結(jié)束碟狞,這里不用和后端做通信啄枕,可以直接通過時(shí)間的判斷就再詳情頁顯示“秒殺結(jié)束”的字樣,因?yàn)闀r(shí)間到了族沃,不管有沒有庫存频祝,都無所謂了

if(nowTime < startTime){
    //秒殺未開始,計(jì)時(shí)事件綁定
    var killTime = new Date(startTime + 1000);//設(shè)置基準(zhǔn)時(shí)間
    seckillBox.countdown(killTime, function(event){
        //時(shí)間格式
        var format = event.strftime('秒殺倒計(jì)時(shí): %D天 %H時(shí) %M分 %S秒');
        //時(shí)間完成后回調(diào)事件
    }).on('finish.countdown', function(){
        //調(diào)用執(zhí)行秒殺的函數(shù)
        seckill.handleSeckill(seckillId, seckillBox);
    });
}

系統(tǒng)當(dāng)前時(shí)間小于秒殺開啟時(shí)間脆淹,秒殺未開始常空,在詳情頁顯示倒計(jì)時(shí),既然是倒計(jì)時(shí)盖溺,就要給系統(tǒng)一個(gè)基準(zhǔn)時(shí)間漓糙,其實(shí)也就是秒殺的開啟時(shí)間,但是這里在秒殺開始時(shí)間的基礎(chǔ)+1s烘嘱,防止用戶端的計(jì)時(shí)偏移

接著使用Bootstrap提供的countdown方法昆禽,實(shí)際上就是一個(gè)事件綁定方法

seckillBox.countdown(killTime, function(event){
    //時(shí)間格式
    var format = event.strftime('秒殺倒計(jì)時(shí): %D天 %H時(shí) %M分 %S秒');
    seckillBox.html(format);
    //倒計(jì)時(shí)完成后回調(diào)事件
})

countdown事件綁定方法中也有一個(gè)回調(diào)函數(shù),當(dāng)日期在不斷的變化的時(shí)候蝇庭,這個(gè)回調(diào)函數(shù)會(huì)做相應(yīng)的輸出醉鳖,對(duì)日期的格式做個(gè)調(diào)整

countdown插件只是負(fù)責(zé)倒計(jì)時(shí),倒計(jì)時(shí)完成后就可以執(zhí)行秒殺操作了哮内,所以在countdown時(shí)間綁定后再接上一個(gè)事件操作

.on('finish.countdown', function(){
    //調(diào)用執(zhí)行秒殺的函數(shù)
    seckill.handleSeckill(seckillId, seckillBox);
});

事件的名字是finish.countdown盗棵,再加上一個(gè)回調(diào)函數(shù),用于倒計(jì)時(shí)完成后回調(diào)事件北发,在這個(gè)函數(shù)中要調(diào)用執(zhí)行秒殺的函數(shù)

這里把執(zhí)行秒殺的函數(shù)單獨(dú)的提取出來纹因,一是降低耦合,二是避免代碼重復(fù)琳拨,因?yàn)樵谧畛跽{(diào)用時(shí)間判斷函數(shù)countdown的時(shí)候瞭恰,可能秒殺正在進(jìn)行,而上面的代碼是秒殺未開始狱庇,倒計(jì)時(shí)完成后才可以執(zhí)行秒殺惊畏,在多個(gè)地方需要執(zhí)行秒殺的操作是牢,所以要把執(zhí)行秒殺的操作單獨(dú)創(chuàng)建一個(gè)函數(shù)

handleSeckill : function(seckillId, node){
    //獲取秒殺地址,控制顯示邏輯陕截,執(zhí)行秒殺
    node.hide()
        .html('<button class="btn btn-primary btn-lg" id="killBtn">開始秒殺</button>');
    $.post(seckill.URL.exposer(seckillId), {}, function(result){
        //在回調(diào)函數(shù)中執(zhí)行交互流程
        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);
                //綁定一次點(diǎn)擊事件
                $('#killBtn').one('click', function(){
                    //執(zhí)行秒殺請求
                    //1.禁用按鈕
                    $(this).addClass('disabled');
                        
                    //2.發(fā)送秒殺請求執(zhí)行秒殺
                    $.post(killUrl, {}, function(result){
                        if(result && result['success']){
                            var killResult = result['data'];
                            var state = killResult['state'];
                            var stateInfo = killResult['stateInfo'];
                                
                            //3.顯示秒殺結(jié)果
                            node.html('<span class="label label-success">' + stateInfo + '</span>');
                        }
                    });
                });
                node.show();
            }else{
                //未開啟秒殺
                var now = exposer['now'];
                var start = exposer['start'];
                var end = exposer['end'];
                seckill.countdown(seckillId, now, start, end);
            }
        }else{
            console.log('result: ' + result);
        }
    });
}.

這個(gè)方法的參數(shù)有個(gè)node批什,用來獲取節(jié)點(diǎn)的农曲,因?yàn)橹霸赿etail.jsp中有專門顯示時(shí)間判斷的結(jié)果的span,當(dāng)可以進(jìn)行秒殺的時(shí)候驻债,這個(gè)span顯示的就是一個(gè)按鈕乳规,所以這里也要獲取這個(gè)span節(jié)點(diǎn),來對(duì)這個(gè)span進(jìn)行操作合呐,加入一個(gè)button標(biāo)簽

node.hide()
    .html('<button class="btn btn-primary btn-lg" id="killBtn">開始秒殺</button>');

插入按鈕后先不要顯示出來暮的,因?yàn)楹竺孢€要對(duì)用戶信息也就是手機(jī)號(hào)進(jìn)行驗(yàn)證

執(zhí)行秒殺操作之前,就要先取得秒殺的地址

@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<Exposer>(true, exposer);
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
        result = new SeckillResult<Exposer>(false, e.getMessage());
    }
    return result;
}

在SeckillController的exposer方法就是用來暴露秒殺地址的淌实,這個(gè)方法只接收POST請求冻辩,返回的是SeckillResult對(duì)象,類型是Exposer拆祈、

在seckill.js中使用$.post()方法恨闪,類似前面講過的$.get()方法

$.post(seckill.URL.exposer(seckillId), {}, function(result){
    //在回調(diào)函數(shù)中執(zhí)行交互流程
    if(result && result['success']){
        var exposer = result['data'];
    }else{
        console.log('result: ' + result);
    }
});

要傳入請求的URL,也要放在seckill的URL屬性中

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

這個(gè)URL需要傳遞秒殺商品的id放坏,因?yàn)椴煌拿霘⑸唐沸枰鄳?yīng)的UEL

首先還是要判斷ajax請求是否成功咙咽,如果沒有請求成功,在控制臺(tái)打印信息

如果請求成功淤年,獲取$.post()方法返回過來的數(shù)據(jù)钧敞,是Exposer類型的,封裝在SeckillResult的data屬性中

public class Exposer {
    
    //是否開啟秒殺
    private boolean exposed;
    
    //加密措施
    private String md5;
    
    //id
    private long seckillId;
    
    //系統(tǒng)當(dāng)前時(shí)間(毫秒)
    private long now;
    
    //秒殺開啟時(shí)間
    private long start;
    
    //秒殺結(jié)束時(shí)間
    private long end;
}

獲取到Exposer對(duì)象后麸粮,在Exposer類中有一個(gè)exposed屬性溉苛,用來判斷是否開啟秒殺,如果開啟秒殺豹休,就要控制之前定義的按鈕炊昆,先綁定點(diǎn)擊事件,然后顯示出來

如果不開啟秒殺威根,就返回系統(tǒng)當(dāng)前時(shí)間凤巨、秒殺開啟時(shí)間、秒殺結(jié)束時(shí)間洛搀,再調(diào)用countdown函數(shù)

if(exposer['exposed']){

}else{
    //未開啟秒殺
    var now = exposer['now'];
    var start = exposer['start'];
    var end = exposer['end'];
    seckill.countdown(seckillId, now, start, end);
}

既然都到這一步了敢茁,什么情況下還是秒殺未開始?

當(dāng)不同的終端顯示過長的時(shí)間的時(shí)候留美,可能出現(xiàn)一些偏差彰檬,用戶顯示已經(jīng)開啟秒殺伸刃,但是實(shí)際上服務(wù)器的時(shí)間還沒到,雖然時(shí)間差很小逢倍,但是還是要重新計(jì)算計(jì)時(shí)邏輯捧颅,所以調(diào)用countdown函數(shù)

判斷開啟秒殺之后,先要獲取秒殺地址

//開啟秒殺较雕,獲取秒殺地址
var md5 = exposer['md5'];
var killUrl = seckill.URL.execution(seckillId, md5);
console.log('killUrl: ' + killUrl);

用于執(zhí)行秒殺操作的URL需要經(jīng)過MD5的加密碉哑,所以還要從后端獲取到MD5,同樣亮蒋,ajax請求的URL都要封裝在seckill.js的URL屬性中

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

這些URL之前在Controller層都已經(jīng)定義好的

@RequestMapping(
        value = "/{seckillId}/{md5}/execution",
        method = RequestMethod.POST,
        produces = {"application/json;charset=UTF-8"})
@ResponseBody
public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId") Long seckillId, 
                                                   @PathVariable("md5") String md5,
                                                   @CookieValue(value = "killPhone", required = false) Long phone)

獲取到了執(zhí)行秒殺的URL扣典,就可以控制按鈕,綁定點(diǎn)擊事件

//綁定一次點(diǎn)擊事件
$('#killBtn').one('click', function(){
    //執(zhí)行秒殺請求
    //1.禁用按鈕
    $(this).addClass('disabled');
                        
    //2.發(fā)送秒殺請求執(zhí)行秒殺
    $.post(killUrl, {}, function(result){
        if(result && result['success']){
            var killResult = result['data'];
            var state = killResult['state'];
            var stateInfo = killResult['stateInfo'];
                                
            //3.顯示秒殺結(jié)果
            node.html('<span class="label label-success">' + stateInfo + '</span>');
        }
    });
});

但是只綁定一次點(diǎn)擊事件慎玖,防止用戶連續(xù)點(diǎn)擊贮尖,比如用戶不放心頁面是否響應(yīng),所以可能會(huì)連續(xù)的點(diǎn)擊按鈕趁怔,如果不在這控制的話湿硝,這些點(diǎn)擊最后都會(huì)發(fā)送到服務(wù)器端,會(huì)造成服務(wù)器端在同一時(shí)間接到大量相同的URL請求痕钢,對(duì)各方面都有影響

所以點(diǎn)擊完之后就要禁用按鈕图柏,通過this指代當(dāng)前對(duì)象,也就是相當(dāng)于使用$('#killBtn')

之后就是發(fā)送秒殺請求任连,執(zhí)行秒殺操作蚤吹,在SeckillController的execute方法只接收POST請求,所以使用$.post()方法

然后通過SeckillResult中的success屬性判斷是否請求成功

if(phone == null){
    return new SeckillResult<SeckillExecution>(false, "未注冊");
}
//SeckillResult<SeckillExecution> result;
try {
    SeckillExecution execution = seckillService.executeSeckill(seckillId, phone, md5);
    return new SeckillResult<SeckillExecution>(true, execution);
} catch (RepeatKillException e) {
    SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.REPEAT_KILL);
    return new SeckillResult<SeckillExecution>(true, execution);
} catch (SeckillCloseException e) {
    SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.END);
    return new SeckillResult<SeckillExecution>(true, execution);
} catch (Exception e) {
    logger.error(e.getMessage(), e);
    SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
    return new SeckillResult<SeckillExecution>(true, execution);
}

這是SeckillController的execute方法随抠,返回的都是SeckillExecution對(duì)象裁着,這些對(duì)象存放在SeckillResult的data屬性中

public class SeckillExecution {
    
    private long seckillId;
    
    //秒殺結(jié)果執(zhí)行后的狀態(tài)
    private int state;
    
    //狀態(tài)信息
    private String stateInfo;

    //秒殺成功對(duì)象
    private SuccessKilled successKilled;
}

這是SeckillExecution類中定義的方法,在seckill.js中獲取到這些屬性

$.post(killUrl, {}, function(result){
    if(result && result['success']){
        var killResult = result['data'];
        var state = killResult['state'];
        var stateInfo = killResult['stateInfo'];
                                
        //3.顯示秒殺結(jié)果
        node.html('<span class="label label-success">' + stateInfo + '</span>');
    }
});

獲取到執(zhí)行秒殺的結(jié)果后拱她,還要在詳情頁中顯示出來二驰,所以控制節(jié)點(diǎn),輸出狀態(tài)信息秉沼,因?yàn)樵赟eckillController的execute方法中已經(jīng)定義了重復(fù)秒殺桶雀、秒殺結(jié)束等異常也算請求成功,只是不對(duì)數(shù)據(jù)庫進(jìn)行操作唬复,但是結(jié)果信息要返回到詳情頁

最后就可以把按鈕顯示出來了

node.show();

至此矗积,前端頁面完成了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市敞咧,隨后出現(xiàn)的幾起案子棘捣,更是在濱河造成了極大的恐慌,老刑警劉巖休建,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乍恐,死亡現(xiàn)場離奇詭異评疗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)茵烈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門百匆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人呜投,你說我怎么就攤上這事胧华。” “怎么了宙彪?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長有巧。 經(jīng)常有香客問我释漆,道長,這世上最難降的妖魔是什么篮迎? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任男图,我火速辦了婚禮,結(jié)果婚禮上甜橱,老公的妹妹穿的比我還像新娘逊笆。我一直安慰自己,他們只是感情好岂傲,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布难裆。 她就那樣靜靜地躺著呢蛤,像睡著了一般学辱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上直晨,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天亩进,我揣著相機(jī)與錄音症虑,去河邊找鬼。 笑死归薛,一個(gè)胖子當(dāng)著我的面吹牛谍憔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播主籍,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼习贫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了崇猫?” 一聲冷哼從身側(cè)響起沈条,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诅炉,沒想到半個(gè)月后蜡歹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屋厘,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年月而,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了汗洒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡父款,死狀恐怖溢谤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情憨攒,我是刑警寧澤世杀,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站肝集,受9級(jí)特大地震影響瞻坝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜杏瞻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一所刀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捞挥,春花似錦浮创、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至讹俊,卻和暖如春雏掠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背劣像。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國打工乡话, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人耳奕。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓绑青,卻偏偏與公主長得像,于是被迫代替她去往敵國和親屋群。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闸婴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容