zoukankan      html  css  js  c++  java
  • java秒杀系列(2)- 页面静态化技术


    ###前言 通过代码片段分别介绍服务端渲染、客户端渲染、对象缓存三种方式的写法。 代码片段仅供参考,具体实现需要根据业务场景自行适配,但思想都是一样。

    一、服务端渲染方式


    ####1、接口返回html页面的设置 ```java @Autowired ThymeleafViewResolver thymeleafViewResolver; @Autowired ApplicationContext applicationContext;

    @RequestMapping(value="/to_list", produces="text/html")
    @ResponseBody
    public String goodsList() {
    // 业务逻辑
    }

    <br>
    
    ####2、先从缓存中取,有就返回。
    ```java
    //取缓存
    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: ```html
    秒杀商品列表
    商品名称商品图片商品原价秒杀价库存数量详情
    详情
    ```

    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的官方配置 ```properties 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()如下 ```javascript ```

    四、对象缓存

    最基本最常用的缓存处理逻辑:

    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

  • 相关阅读:
    vue 中router-link下方的横线如何去除
    element-ui中如何去掉el-menu菜单栏中下划线
    vue中使用swiper做轮播页面,标题样式随着轮播页面改变而改变
    git pull失误提交
    通过计算机名获取本网段内电脑的IP地址和MAC地址
    C# 控件及常用设计整理
    TextBox控件中只输入整数的几种方法
    c#鼠标移动到Button 改变颜色
    C#编写条形码扫描
    C#编程中的crc16校验
  • 原文地址:https://www.cnblogs.com/fulongyuanjushi/p/11367038.html
Copyright © 2011-2022 走看看