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>
四、对象缓存
最基本最常用的缓存处理逻辑:
- 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
- 命中:应用程序从cache中取数据,取到后返回。
- 更新:先把数据存到数据库中,成功后,再让缓存失效。
参考代码:
// 先查缓存,再查数据库。 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