java秒殺系列(2)- 頁面靜態化技術

来源:https://www.cnblogs.com/fulongyuanjushi/archive/2019/08/17/11367038.html
-Advertisement-
Play Games

前言 通過代碼片段分別介紹服務端渲染、客戶端渲染、對象緩存三種方式的寫法。 代碼片段僅供參考,具體實現需要根據業務場景自行適配,但思想都是一樣。 一、服務端渲染方式 1、介面返回html頁面的設置 2、先從緩存中取,有就返回。 3、緩存中沒有,就手動渲染。 springboot1.5.x的寫法: s ...



前言

通過代碼片段分別介紹服務端渲染、客戶端渲染、對象緩存三種方式的寫法。
代碼片段僅供參考,具體實現需要根據業務場景自行適配,但思想都是一樣。




一、服務端渲染方式


1、介面返回html頁面的設置

@Autowired
ThymeleafViewResolver thymeleafViewResolver;
@Autowired
ApplicationContext applicationContext;

@RequestMapping(value="/to_list", produces="text/html")
@ResponseBody
public String goodsList() {
    // 業務邏輯
}


2、先從緩存中取,有就返回。

//取緩存
String html = redisService.get(GoodsKey.getGoodsList, "", String.class);
if(!StringUtils.isEmpty(html)) {
    return html;
}


3、緩存中沒有,就手動渲染。

springboot1.5.x的寫法:

List<GoodsVo> goodsList = goodsService.listGoodsVo();
model.addAttribute("goodsList", goodsList);
SpringWebContext ctx = new SpringWebContext(request,response, request.getServletContext(),request.getLocale(), model.asMap(), applicationContext );
//手動渲染
html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);

springboot2.x的寫法:

WebContext ctx = new WebContext(request, response, request.getServletContext(),  request.getLocale(), model.asMap());
//手動渲染
html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);


4、最後再將渲染內容加入到redis

if(!StringUtils.isEmpty(html)) {
    redisService.set(GoodsKey.getGoodsList, "", html);
}




二、客戶端渲染方式(商品詳情頁)


1、找到跳轉到商品詳情頁的路徑,修改為一個靜態html的路徑。

在商品列表頁,找到跳轉到詳情頁的動態路徑,直接修改為一個靜態路徑,尾碼為htm或shtml,總之不要是html即可,因為application.properties中一般會配置尾碼為html的都訪問templates文件夾下的。
註意代碼中詳情的鏈接,指向一個靜態頁面goods_detail.htm:

<body>

<div class="panel panel-default" style="height:100%;background-color:rgba(222,222,222,0.8)">
  <div class="panel-heading">秒殺商品列表</div>
  <table class="table" id="goodslist">
    <tr><td>商品名稱</td><td>商品圖片</td><td>商品原價</td><td>秒殺價</td><td>庫存數量</td><td>詳情</td></tr>
    <tr  th:each="goods,goodsStat : ${goodsList}">  
                <td th:text="${goods.goodsName}"></td>  
                <td ><img th:src="@{${goods.goodsImg}}" width="100" height="100" /></td>  
                <td th:text="${goods.goodsPrice}"></td>  
                <td th:text="${goods.miaoshaPrice}"></td>  
                <td th:text="${goods.stockCount}"></td>
                <td><a th:href="'/goods_detail.htm?goodsId='+${goods.id}">詳情</a></td>  
     </tr>  
  </table>
</div>
</body>


2、根據1的路徑,在static目錄下新建一個goods_detail.htm文件,原本動態頁面上的模板語言都去掉,換成id=“xx”,然後使用ajax來渲染靜態文件中的數據即可。

原始動態頁面:

<div class="panel panel-default">
  <div class="panel-heading">秒殺商品詳情</div>
  <div class="panel-body">
    <span th:if="${user eq null}"> 您還沒有登錄,請登陸後再操作<br/></span>
    <span>沒有收貨地址的提示。。。</span>
  </div>
  <table class="table" id="goodslist">
    <tr>  
        <td>商品名稱</td>  
        <td colspan="3" th:text="${goods.goodsName}"></td> 
     </tr>  
     <tr>  
        <td>商品圖片</td>  
        <td colspan="3"><img th:src="@{${goods.goodsImg}}" width="200" height="200" /></td>  
     </tr>
     <tr>  
        <td>秒殺開始時間</td>  
        <td th:text="${#dates.format(goods.startDate, 'yyyy-MM-dd HH:mm:ss')}"></td>
        <td id="miaoshaTip">    
            <input type="hidden" id="remainSeconds" th:value="${remainSeconds}" />
            <span th:if="${miaoshaStatus eq 0}">秒殺倒計時:<span id="countDown" th:text="${remainSeconds}"></span>秒</span>
            <span th:if="${miaoshaStatus eq 1}">秒殺進行中</span>
            <span th:if="${miaoshaStatus eq 2}">秒殺已結束</span>
        </td>
        <td>
            <form id="miaoshaForm" method="post" action="/miaosha/do_miaosha">
                <button class="btn btn-primary btn-block" type="submit" id="buyButton">立即秒殺</button>
                <input type="hidden" name="goodsId" th:value="${goods.id}" />
            </form>
        </td>
     </tr>
     <tr>  
        <td>商品原價</td>  
        <td colspan="3" th:text="${goods.goodsPrice}"></td>  
     </tr>
      <tr>  
        <td>秒殺價</td>  
        <td colspan="3" th:text="${goods.miaoshaPrice}"></td>  
     </tr>
     <tr>  
        <td>庫存數量</td>  
        <td colspan="3" th:text="${goods.stockCount}"></td>  
     </tr>
  </table>
</div>

靜態化之後的頁面:可以看到,動態模板語言都去掉了,直接通過JS來賦值。

<div class="panel panel-default" style="height:100%;background-color:rgba(222,222,222,0.8)" >
  <div class="panel-heading">秒殺商品詳情</div>
  <div class="panel-body">
    <span id="userTip"> 您還沒有登錄,請登陸後再操作<br/></span>
    <span>沒有收貨地址的提示。。。</span>
  </div>
  <table class="table" id="goodslist">
    <tr>  
        <td>商品名稱</td>  
        <td colspan="3" id="goodsName"></td> 
     </tr>  
     <tr>  
        <td>商品圖片</td>  
        <td colspan="3"><img  id="goodsImg" width="200" height="200" /></td>  
     </tr>
     <tr>  
        <td>秒殺開始時間</td>  
        <td id="startTime"></td>
        <td >   
            <input type="hidden" id="remainSeconds" />
            <span id="miaoshaTip"></span>
        </td>
        <td>
        <!--  
            <form id="miaoshaForm" method="post" action="/miaosha/do_miaosha">
                <button class="btn btn-primary btn-block" type="submit" id="buyButton">立即秒殺</button>
                <input type="hidden" name="goodsId"  id="goodsId" />
            </form>-->
            <div class="row">
                <div class="form-inline">
                    <img id="verifyCodeImg" width="80" height="32"  style="display:none" onclick="refreshVerifyCode()"/>
                    <input id="verifyCode"  class="form-control" style="display:none"/>
                    <button class="btn btn-primary" type="button" id="buyButton"onclick="getMiaoshaPath()">立即秒殺</button>
                </div>
            </div>
            <input type="hidden" name="goodsId"  id="goodsId" />
        </td>
     </tr>
     <tr>  
        <td>商品原價</td>  
        <td colspan="3" id="goodsPrice"></td>  
     </tr>
      <tr>  
        <td>秒殺價</td>  
        <td colspan="3"  id="miaoshaPrice"></td>  
     </tr>
     <tr>  
        <td>庫存數量</td>  
        <td colspan="3"  id="stockCount"></td>  
     </tr>
  </table>
</div>

核心js代碼:

<script>

$(function(){
   getDetail();
});

function getDetail(){
   var goodsId = g_getQueryString("goodsId");
   $.ajax({
      url:"/goods/detail/"+goodsId,
      type:"GET",
      success:function(data){
         if(data.code == 0){
            render(data.data);
         }else{
            layer.msg(data.msg);
         }
      },
      error:function(){
         layer.msg("客戶端請求有誤");
      }
   });
}

function render(detail){
   var miaoshaStatus = detail.miaoshaStatus;
   var  remainSeconds = detail.remainSeconds;
   var goods = detail.goods;
   var user = detail.user;
   if(user){
      $("#userTip").hide();
   }
   $("#goodsName").text(goods.goodsName);
   $("#goodsImg").attr("src", goods.goodsImg);
   $("#startTime").text(new Date(goods.startDate).format("yyyy-MM-dd hh:mm:ss"));
   $("#remainSeconds").val(remainSeconds);
   $("#goodsId").val(goods.id);
   $("#goodsPrice").text(goods.goodsPrice);
   $("#miaoshaPrice").text(goods.miaoshaPrice);
   $("#stockCount").text(goods.stockCount);
   countDown(); // 判斷秒殺開始狀態
}

// 判斷秒殺開始狀態
function countDown(){
    var remainSeconds = $("#remainSeconds").val();
    var timeout;
    if(remainSeconds > 0){//秒殺還沒開始,倒計時
       $("#buyButton").attr("disabled", true);
       $("#miaoshaTip").html("秒殺倒計時:"+remainSeconds+"秒");
        timeout = setTimeout(function(){
            $("#countDown").text(remainSeconds - 1);
            $("#remainSeconds").val(remainSeconds - 1);
            countDown();
        },1000);
    }else if(remainSeconds == 0){//秒殺進行中
        $("#buyButton").attr("disabled", false);
        if(timeout){
            clearTimeout(timeout);
        }
        $("#miaoshaTip").html("秒殺進行中");
        $("#verifyCodeImg").attr("src", "/miaosha/verifyCode?goodsId="+$("#goodsId").val());
        $("#verifyCodeImg").show();
        $("#verifyCode").show();
    }else{//秒殺已經結束
        $("#buyButton").attr("disabled", true);
        $("#miaoshaTip").html("秒殺已經結束");
        $("#verifyCodeImg").hide();
        $("#verifyCode").hide();
    }
}

</script>


3、記得服務端返回json格式數據,給靜態頁面做數據綁定。




三、客戶端渲染方式(秒殺介面)

和第二點的操作基本一樣,也是去除動態模板語言,改為ajax渲染。
不同的地方:
1)、多了一些springboot的配置;
2)、GET和POST的區別,這裡一定要用POST,有一些場景比如刪除操作,如果用了GET比如delete?id=XX這樣的寫法,那麼搜索引擎掃描到會自動幫你刪除了,所以一定要寫清楚類型。

1、springboot-1.5.x的配置

spring.resources.add-mappings=true #是否啟用預設資源處理
spring.resources.cache-period= 3600 #緩存時間
spring.resources.chain.cache=true #是否在資源鏈中啟用緩存
spring.resources.chain.enabled=true #是否啟用Spring資源處理鏈。預設情況下,禁用,除非至少啟用了一個策略。
spring.resources.chain.gzipped=true #是否對緩存壓縮
spring.resources.chain.html-application-cache=true #是否啟用HTML5應用程式緩存清單重寫
spring.resources.static-locations=classpath:/static/ #靜態資源的位置


2、springboot2.1.1的官方配置

spring.resources.add-mappings=true # 是否啟用預設資源處理
spring.resources.cache.cachecontrol.cache-private= # 表示響應消息是針對單個用戶的,不能由共用緩存存儲。
spring.resources.cache.cachecontrol.cache-public= # 表示任何緩存都可以存儲響應
spring.resources.cache.cachecontrol.max-age= # 響應被緩存的最大時間,如果沒有指定持續時間尾碼,以秒為單位。
spring.resources.cache.cachecontrol.must-revalidate= # 表明,一旦緩存過期,在未與伺服器重新驗證之前,緩存不能使用響應。
spring.resources.cache.cachecontrol.no-cache= # 表示緩存的響應只有在伺服器重新驗證時才能重用
spring.resources.cache.cachecontrol.no-store= # 表示在任何情況下都不緩存響應
spring.resources.cache.cachecontrol.no-transform= # 指示中介(緩存和其他)它們不應該轉換響應內容
spring.resources.cache.cachecontrol.proxy-revalidate= # 與“must-revalidate”指令的含義相同,只是它不適用於私有緩存。
spring.resources.cache.cachecontrol.s-max-age= # 響應被共用緩存緩存的最大時間,如果沒有指定持續時間尾碼,以秒為單位。
spring.resources.cache.cachecontrol.stale-if-error= # 當遇到錯誤時,響應可能使用的最大時間,如果沒有指定持續時間尾碼,以秒為單位。
spring.resources.cache.cachecontrol.stale-while-revalidate= # 如果沒有指定持續時間尾碼,則響應在過期後可以提供的最長時間(以秒為單位)。
spring.resources.cache.period= # 資源處理程式提供的資源的緩存周期。如果沒有指定持續時間尾碼,將使用秒。
spring.resources.chain.cache=true # 是否在資源鏈中啟用緩存。
spring.resources.chain.compressed=false # 是否啟用已壓縮資源(gzip, brotli)的解析。
spring.resources.chain.enabled= # 是否啟用Spring資源處理鏈。預設情況下,禁用,除非至少啟用了一個策略。
spring.resources.chain.html-application-cache=false # 是否啟用HTML5應用緩存清單重寫。
spring.resources.chain.strategy.content.enabled=false # 是否啟用內容版本策略。
spring.resources.chain.strategy.content.paths=/** # 應用於內容版本策略的以逗號分隔的模式列表。
spring.resources.chain.strategy.fixed.enabled=false # 是否啟用固定版本策略。
spring.resources.chain.strategy.fixed.paths=/** # 用於固定版本策略的以逗號分隔的模式列表。
spring.resources.chain.strategy.fixed.version= # 用於固定版本策略的版本字元串。
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/ # 靜態資源的位置。


3、第二點在點擊<立即秒殺>按鈕時調用的JS方法getMiaoshaPath()如下

<script>

function getMiaoshaPath(){
    var goodsId = $("#goodsId").val();
    g_showLoading();
    $.ajax({
        url:"/miaosha/path",
        type:"GET",
        data:{
            goodsId:goodsId,
            verifyCode:$("#verifyCode").val()
        },
        success:function(data){
            if(data.code == 0){
                var path = data.data;
                doMiaosha(path);
            }else{
                layer.msg(data.msg);
            }
        },
        error:function(){
            layer.msg("客戶端請求有誤");
        }
    });
}

function doMiaosha(path){
    $.ajax({
        url:"/miaosha/"+path+"/do_miaosha",
        type:"POST",
        data:{
            goodsId:$("#goodsId").val()
        },
        success:function(data){
            if(data.code == 0){
                //window.location.href="/order_detail.htm?orderId="+data.data.id;
                getMiaoshaResult($("#goodsId").val());
            }else{
                layer.msg(data.msg);
            }
        },
        error:function(){
            layer.msg("客戶端請求有誤");
        }
    });
    
}

function getMiaoshaResult(goodsId){
    g_showLoading();
    $.ajax({
        url:"/miaosha/result",
        type:"GET",
        data:{
            goodsId:$("#goodsId").val(),
        },
        success:function(data){
            if(data.code == 0){
                var result = data.data;
                if(result < 0){
                    layer.msg("對不起,秒殺失敗");
                }else if(result == 0){//繼續輪詢
                    setTimeout(function(){
                        getMiaoshaResult(goodsId);
                    }, 200);
                }else{
                    layer.confirm("恭喜你,秒殺成功!查看訂單?", {btn:["確定","取消"]},
                            function(){
                                window.location.href="/order_detail.htm?orderId="+result;
                            },
                            function(){
                                layer.closeAll();
                            });
                }
            }else{
                layer.msg(data.msg);
            }
        },
        error:function(){
            layer.msg("客戶端請求有誤");
        }
    });
}

</script>




四、對象緩存

最基本最常用的緩存處理邏輯:

  1. 失效:應用程式先從cache取數據,沒有得到,則從資料庫中取數據,成功後,放到緩存中。
  2. 命中:應用程式從cache中取數據,取到後返回。
  3. 更新:先把數據存到資料庫中,成功後,再讓緩存失效。


    參考代碼:
// 先查緩存,再查資料庫。
public MiaoshaUser getById(long id) {
   //取緩存
   MiaoshaUser user = redisService.get(MiaoshaUserKey.getById, ""+id, MiaoshaUser.class);
   if(user != null) {
      return user;
   }
   //取資料庫
   user = miaoshaUserDao.getById(id);
   if(user != null) {
      redisService.set(MiaoshaUserKey.getById, ""+id, user);
   }
   return user;
}

// 更新資料庫後,緩存也要做同步更新。
public boolean updatePassword(String token, long id, String formPass) {
   //取user
   MiaoshaUser user = getById(id);
   if(user == null) {
      throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
   }
   //更新資料庫
   MiaoshaUser toBeUpdate = new MiaoshaUser();
   toBeUpdate.setId(id);
   toBeUpdate.setPassword(MD5Util.formPassToDBPass(formPass, user.getSalt()));
   miaoshaUserDao.update(toBeUpdate);
   //處理緩存
   redisService.delete(MiaoshaUserKey.getById, ""+id);
   user.setPassword(toBeUpdate.getPassword());
   redisService.set(MiaoshaUserKey.token, token, user);
   return true;
}




總結

  • 服務端渲染:利用模板引擎技術生成靜態html頁面到指定目錄或者是redis等緩存中間件中去,頁面上的訪問路徑直接指向到該目錄下的靜態html,這種方式實際上還是屬於服務端渲染的靜態化方式。
  • 客戶端渲染:將原本的模板語言渲染的html,改變為純靜態的htm/shtml,然後用js/ajax渲染數據,加上spring配置文件的相關配置指定靜態文件存放路徑,達到利用瀏覽器緩存頁面的方式。推薦使用這種方式,這種屬於前後端分離的客戶端渲染方式,性能更好。
  • 對象緩存:對象級緩存主要是在service中對一些對象的緩存處理,要按照合理的步驟,先取緩存,再取資料庫,緩存中有就返回緩存對象,沒有就返回資料庫數據,最後將資料庫數據再放入緩存中。


    如果對緩存處理邏輯感興趣,可以參考這篇博客:http://blog.csdn.net/tTU1EvLDeLFq5btqiK/article/details/78693323



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一個軟體系統往往會存在很多隱藏的bug,最常用的功能bug往往很少。不常用的功能因為長時間不被人關註缺少重現的機會會一直隱藏在那裡伺機爆發。限流功能就是這些不被關註的功能之一。 ...
  • 1.IntelliJ IDEA的下載與安裝 IntelliJ IDEA簡稱IDEA,由JetBrains公司開發,是java語言開發的集成環境,也是目前業界被公認的最好的java開發工具之一。尤其在智能代碼助手、代碼自動提示、重構、J2EE支持、Ant、JUnit、CVS整合、代碼審查、 創新的GU ...
  • 1.在列表後面追加元素 2.在列表中插入元素 3.在列表中刪除元素 4.清空列表 ...
  • Springboot 使用外部 Tomcat 1.修改 pom.xml,改為打 war 包 2.將 Springboot 內置 tomcat 作用域改為 3.重寫 SpringBootServletInitializer 4.maven 打包出 war 包後,放到 tomcat 的 webapps ...
  • 一、AOP是什麼 AOP(面向切麵編程),可以說是一種編程思想,其中的Spring AOP和AspectJ都是現實了這種編程思想。相對OOP(面向過程編程)來說,提供了另外一種編程方式,對於OOP過程中產生的橫切性問題,這些橫切性與業務無關,可以通過預編譯方式和運行期動態代理來實現。比如可以應用在: ...
  • 1.首先下載小程式開發工具 2.小程式中的wxml就相當於html , wxss就相當於css 3.佈局和html佈局幾乎一樣 4.寬度使用百分比 5.input框里的文字上下居中是用padding撐出來的 6.最下麵的文字靠右,view相當於一個塊元素,設定寬度後,text-align右對齊 簡單 ...
  • 1. JVM運行時劃分哪幾個區域?哪些區域是線程共用的?哪些區域是線程獨占的? JVM運行時一共劃分:程式計數器、虛擬機棧、堆、本地方法棧、方法區。 線程共用的數據區域:堆、方法區。 線程獨享的數據區域區域:程式計數器、虛擬機棧、本地方法棧。 2. 這幾個記憶體區域分別存放什麼數據? 程式計數器記錄當 ...
  • 一、賦值運算符 1.賦值類運算符包括兩種: (1)基本賦值運算符:= (2)擴展的賦值運算符: += -= *= /= &= 賦值類的運算符優先順序:先執行等號右邊的表達式,將執行結果賦值給左邊的變數 2.例子: 總結:擴展類的運算符不改變運算結果後的變數的類型 二、字元串的連接運算符 關於java中 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...