1. 页面缓存优化
1.1 未经优化之前的代码
1 2 3 4 5 6 7 8
| @RequestMapping("/to_list") public String toList(Model model,MiaoShaUser user){ model.addAttribute("user",user); List<GoodsVo> goodsVos = goodsService.listGoodsVo(); model.addAttribute("goodsList",goodsVos); return "goods_list"; } 1234567
|
1.2 优化产生的改变
![在这里插入图片描述]()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @RequestMapping(value = "/to_list",produces = "text/html") @ResponseBody public String toList(HttpServletRequest request, HttpServletResponse response, Model model, MiaoShaUser user){ model.addAttribute("user",user); String html = redisService.get(GoodsKey.goodsKeyPrefix, "", String.class); if(! StringUtils.isEmpty(html)) return html;
List<GoodsVo> goodsVos = goodsService.listGoodsVo(); model.addAttribute("goodsList",goodsVos); IWebContext 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.goodsKeyPrefix,"",html); }
return html; }
|
- 首先,我们应用缓存,一定要引入RedisService
- @RequestMapping(value = “/to_list”,
produces = "text/html"
)produces标注了返回值的类型,必须与@ResponseBody搭配使用
- 手动渲染过程中,我们要注入
ThymeleafViewResolver
,这个是框架给我们准备好的Bean,利用它来渲染页面,其中第二个参数,需要注入IContext
- 在
Spring5
版本中,SpringWebContext
已经没有了,我们需要使用WebContext
来代替。它剔除了之前对ApplicationContext 过多的依赖,现在thymeleaf渲染不再过多依赖spring容器
- 再者,我们对Redis缓存的时间设置了
60秒
的限制,超过60秒过期,这个时间不宜过长。在60秒内我们看到的网页一直一样是暂且可以接受的
2. 对象缓存与缓存更新
2.1 对象缓存
对象缓存,我们之前已经做过了一个,就是在MiaoshaService中的getByToken
方法,通过token值,从Redis中获取对象信息。
这次,我们实现一个getById()方法,即通过Id值,从Redis中获取user对象。(对象缓存没有设置过期时间
,而且对象缓存是粒度最小
的缓存)
1 2 3 4 5 6 7 8 9 10 11 12
| public MiaoShaUser getById(long id){ MiaoShaUser user = redisService.get(MiaoShaUserKey.idPrefix, "" + id, MiaoShaUser.class); if(user != null) return user;
user = miaoShaUserDao.getById(id); if(user != null) redisService.set(MiaoShaUserKey.idPrefix,"" + id,user);
return user; } 1234567891011
|
2.2 缓存更新
我们模拟一个场景,我们要对密码进行修改,那么缓存也需要修改,现在先列出视频中给的方法,通过Id值取出用户,修改数据库,之后,对token-user缓存进行修改,id-user缓存进行删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public boolean updatePassword(long id,String formPass,String token){ MiaoShaUser user = getById(id); if(user == null) throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
user.setPassword(MD5Util.formPassToDBPass(formPass,user.getSalt())); miaoShaUserDao.update(user); redisService.set(MiaoShaUserKey.getTokenPrefix,token,user); redisService.delete(MiaoShaUserKey.idPrefix,id);
return true; } 123456789101112131415
|
- 个人理解:我们上网时的多数场景,修改完密码之后都要我们进行重新登录,而且在我们这个项目中,登录的过程中会对token-user缓存进行重新添加,那么我们在修改密码的时候,可以直接将token-user和id-user全部都删除,而不需要对其中的缓存进行值的修改。
3. 页面静态化
3.1 将商品详情页进行静态化处理(订单详情也做了静态化)
通常情况下,页面不采用第一种缓存的方式实现优化,而是通过静态化处理,比较常用的技术有Vue。通过静态化处理,我们将页面缓存在客户端浏览器中,不需要与服务器交互就能访问到页面。
以下,我们用JQuery实现。
3.1.1 对后端代码进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| @RequestMapping(value = "/detail/{goodsId}") @ResponseBody public Result<GoodsDetailVo> toDetail(HttpServletRequest request, HttpServletResponse response, Model model, MiaoShaUser user, @PathVariable("goodsId") long goodsId){
GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId);
long startDate = goodsVo.getStartDate().getTime(); long endDate = goodsVo.getEndDate().getTime(); long now = System.currentTimeMillis();
int miaoshaStatus = 0; int remainSeconds = 0;
if(now < startDate){ remainSeconds = (int) (startDate - now) / 1000; }else if(now > endDate){ miaoshaStatus = 2; remainSeconds = -1; }else { remainSeconds = 0; miaoshaStatus = 1; } GoodsDetailVo goodsDetailVo = new GoodsDetailVo(); goodsDetailVo.setGoods(goodsVo); goodsDetailVo.setUser(user); goodsDetailVo.setMiaoshaStatus(miaoshaStatus); goodsDetailVo.setRemainSeconds(remainSeconds);
return Result.success(goodsDetailVo); } 123456789101112131415161718192021222324252627282930313233343536
|
- @RequestMapping中,去掉produces属性
去掉Model向前端传值的逻辑
,只留下业务处理过程,并将所需要的的值封装在GoodsDetailVo
对象中
注意事项
在GoodsDetailVo中的属性字段要与前端所需要的字段名保持一致,如下所示,这样才能获取
1 2 3 4 5 6 7 8 9
| @Data public class GoodsDetailVo { private long miaoshaStatus; private long remainSeconds; private GoodsVo goods; private MiaoShaUser user; }
12345678
|
对应前端
![在这里插入图片描述]()
3.1.2 对前端跳转的修改
我们从商品列表页面跳转到商品详情页,修改为如下
![在这里插入图片描述]()
注意其中/goods_detail.htm
,它是放在static目录下的静态资源,为了防止视图解析器的跳转,将html写为htm
![在这里插入图片描述]()
3.1.3 在application.properties中配置
1 2 3 4 5 6 7 8 9
| # static spring.resources.add-mappings=true spring.resources.cache.period= 3600 #缓存时间 spring.resources.chain.cache=true spring.resources.chain.enabled=true #spring.resources.chain.gzipped=true spring.resources.chain.html-application-cache=true spring.resources.static-locations=classpath:/static/ 12345678
|
4. POST请求和GET请求的区别
- GET:这个请求是幂等的,从服务端获取数据,反复获取不会对数据有影响。因为GET因为是读取,就可以对GET请求的数据做缓存。这个缓存可以做到浏览器本身上(彻底避免浏览器发请求),也可以做到代理上(如nginx),或者做到server端(用Etag,至少可以减少带宽消耗)
- POST:该请求是不幂等的,它会在页面表单上提交数据,请求服务器的响应,往往会对数据进行修改
5. 解决超卖问题
- 当多个线程同时读取到同一个库存数量时,防止超卖,修改SQL语句
1 2 3
| #添加stock_count > 0的条件 update miaosha_goods set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count > 0 12
|
- 防止同一个用户秒杀多个,添加唯一索引,绑定user_id和goods_id,这样同一个用户对同一个商品的秒杀订单是唯一的
![在这里插入图片描述]()