通过代码片段分别介绍服务端渲染、客户端渲染、对象缓存三种方式的写法。
代码片段仅供参考,具体实现需要根据业务场景自行适配,但思想都是一样。
@Autowired ThymeleafViewResolver thymeleafViewResolver; @Autowired ApplicationContext applicationContext; @RequestMapping(value="/to_list", produces="text/html") @ResponseBody public String goodsList() { // 业务逻辑 }
//取缓存 String html = redisService.get(GoodsKey.getGoodsList, "", String.class); if(!StringUtils.isEmpty(html)) { return html; }
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);
if(!StringUtils.isEmpty(html)) { redisService.set(GoodsKey.getGoodsList, "", 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>
原始动态页面:
<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>
和第二点的操作基本一样,也是去除动态模板语言,改为ajax渲染。
不同的地方:
1)、多了一些springboot的配置;
2)、GET和POST的区别,这里一定要用POST,有一些场景比如删除操作,如果用了GET比如delete?id=XX这样的写法,那么搜索引擎扫描到会自动帮你删除了,所以一定要写清楚类型。
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/ #静态资源的位置
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/ # 静态资源的位置。
<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>
最基本最常用的缓存处理逻辑:
// 先查缓存,再查数据库。 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; }