欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java秒杀系列(2)- 页面静态化技术

程序员文章站 2022-05-18 21:07:39
前言 通过代码片段分别介绍服务端渲染、客户端渲染、对象缓存三种方式的写法。 代码片段仅供参考,具体实现需要根据业务场景自行适配,但思想都是一样。 一、服务端渲染方式 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