前言
通过代码片段分别介绍服务端渲染, 客户端渲染, 对象缓存三种方式的写法.
代码片段仅供参考, 具体实现需要根据业务场景自行适配, 但思想都是一样.
一, 服务端渲染方式
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){
- //Windows.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(){
- Windows.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
来源: https://www.cnblogs.com/fulongyuanjushi/p/11367038.html