1. 实现商品列表页、详情页

详见:具体实现流程


2. 秒杀功能

详见:具体实现流程


3. 压力测试

详见:具体实现流程


4. 项目优化——(缓存)

4.1.页面缓存

由于该项目前后端不分离,因此每次获取页面时,每次我们都需要进行查询渲染。这里我们考虑用redis做缓存,缓存页面。


首先缓存商品列表页
在GoodsController中,引入redis依赖。在跳转页面的RequestMapping中,添加produces参数。
页面缓存起来需要的操作:

从redis里读取缓存
1. 如果有页面,直接返回
2. 如果没有,手动渲染模板
3. 缓存到redis中,并把结果返回给输出端

注入依赖:

@Autowired
private RedisTemplate redisTemplate;@Autowired
private ThymeleafViewResolver thymeleafViewResolver;

添加ResponseBody注解,在RequestMapping中添加produce参数,返回整个页面:

/*** 跳转到商品列表页面**/
@RequestMapping(value = "/toList",produces = "text/html;charset=utf-8")
@ResponseBody
public String toList(Model model, User user, HttpServletRequest request, HttpServletResponse response){//redis中获取页面,如果不为空,直接返回页面ValueOperations valueOperations = redisTemplate.opsForValue();String html = (String)valueOperations.get("goodsList");if(StringUtils.hasLength(html)){return html;}model.addAttribute("user", user);model.addAttribute("goodsList", goodsService.findGoodsVo());//如果为空,手动渲染,存入redis,再返回WebContext webContext = new WebContext(request, response, request.getServletContext(), request.getLocale(), model.asMap());html = thymeleafViewResolver.getTemplateEngine().process("goodsList", webContext);//这里每60s会失效if(StringUtils.hasLength(html)){valueOperations.set("goodsList", html, 60, TimeUnit.SECONDS);}return html;
}

4.2. 对象缓存

在登录的时,我们已经将用户的信息保存到redis中了。
同时用户大部分时候是不更新的,基本上不设置过期时间;但是用户一旦做了变更,如修改密码,那我们需要删除redis中对应的缓存。

  public RespBean updatePassword(String userTicket, String password, HttpServletRequest request, HttpServletResponse response) {TUser user = getUserByCookie(userTicket, request, response);if (user == null) {throw new GlobalException(RespBeanEnum.MOBILE_NOT_EXIST);}//设置新的密码user.setPassword(MD5Util.inputPassToDBPass(password, user.getSalt()));int result = tUserMapper.updateById(user);if (1 == result) {//如果该用户存在,则需要删除Redis中的数据redisTemplate.delete("user:" + userTicket);return RespBean.success();}return RespBean.error(RespBeanEnum.PASSWORD_UPDATE_FAIL);}

4.3 页面静态化(减少需要传输的数据)

  • 虽然缓存页面提升了速度,但是还是存在一些问题。渲染整个html,占用存储大,且发送到前端的时候,发送时,发送量还是很大的。
    -因此我们将页面静态化,即前后端分离。变动的数据通过ajax发送就行,不变的部分静态化。
    前端通过ajax获取信息
 $(function () {// countDown();getDetails();});function getDetails() {var goodsId = g_getQueryString("goodsId");console.log(goodsId);$.ajax({url: '/goods/detail/' + goodsId,type: 'GET',success: function (data) {if (data.code == 200) {render(data.object);countDown();} else {layer.msg("客户端请求出错");}},error: function () {layer.msg("客户端请求出错");}})}

后端

    @ApiOperation("商品详情")@GetMapping("/detail/{goodsId}")@ResponseBodypublic RespBean toDetail(TUser user, @PathVariable Long goodsId) {GoodsVo goodsVo = itGoodsService.findGoodsVobyGoodsId(goodsId);Date startDate = goodsVo.getStartDate();Date endDate = goodsVo.getEndDate();Date nowDate = new Date();//秒杀状态int seckillStatus = 0;//秒杀倒计时int remainSeconds = 0;if (nowDate.before(startDate)) {//秒杀还未开始0remainSeconds = (int) ((startDate.getTime() - nowDate.getTime()) / 1000);} else if (nowDate.after(endDate)) {//秒杀已经结束seckillStatus = 2;remainSeconds = -1;} else {//秒杀进行中seckillStatus = 1;remainSeconds = 0;}//返回的实体类DetailVo detailVo = new DetailVo();detailVo.setTUser(user);detailVo.setGoodsVo(goodsVo);detailVo.setRemainSeconds(remainSeconds);detailVo.setSecKillStatus(seckillStatus);return RespBean.success(detailVo);}

5. 解决商品超卖的问题

  1. 在减库存时,判断库存是否足够(更新有行级锁,因此不会出现并发修改)
OrderServiceImpl.java//秒杀商品表减库存SeckillGoods seckillGoods = seckillGoodsService.getOne(new QueryWrapper<SeckillGoods>().eq("goods_id",goods.getId()));     //查询秒杀商品seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);    //商品数减1seckillGoodsService.update(new UpdateWrapper<SeckillGoods>().set("stock_count", seckillGoods.getStockCount()).eq("id", seckillGoods.getId()).gt("stock_count", 0)); //当商品的库存大于等于0时,再更新
  1. 解决同一用户同时秒杀多件商品——可以通过数据库建立唯一索引避免
    这里解决的是同一个人发起了两次请求时,可能两个都还正在操作,均未抢购成功,致判断为未抢购,导致用户多次秒杀。
    这里是线程安全的,利用了innodb行锁+复合索引的方式

  2. 将秒杀订单信息存入Redis,方便判断是否重复抢购时进行查询

/**
* 秒杀
* @return
*/
@Override
@Transactional
public Order seckill(User user, GoodsVo goods) {//秒杀商品表减库存SeckillGoods seckillGoods = seckillGoodsService.getOne(new
QueryWrapper<SeckillGoods>().eq("goods_id",goods.getId()));seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);boolean seckillGoodsResult = seckillGoodsService.update(new UpdateWrapper<SeckillGoods>().set("stock_count", seckillGoods.getStockCount()).eq("id", seckillGoods.getId()).gt("stock_count", 0));// seckillGoodsService.updateById(seckillGoods);if (!(goodsResult&&seckillGoodsResult)){return null;}//生成订单Order order = new Order();order.setUserId(user.getId());order.setGoodsId(goods.getId());order.setDeliveryAddrId(0L);order.setGoodsName(goods.getGoodsName());order.setGoodsCount(1);order.setGoodsPrice(seckillGoods.getSeckillPrice());order.setOrderChannel(1);order.setStatus(0);order.setCreateDate(new Date());orderMapper.insert(order);//生成秒杀订单SeckillOrder seckillOrder = new SeckillOrder();seckillOrder.setOrderId(order.getId());seckillOrder.setUserId(user.getId());seckillOrder.setGoodsId(goods.getId());seckillOrderService.save(seckillOrder);redisTemplate.opsForValue().set("order:" + user.getId() + ":" + goods.getId(), JsonUtil.object2JsonStr(seckillOrder));return order; }

取用户对应的秒杀商品表,若没有则抢购,否则返回已抢购过

  String seckillOrderJson = (String)
redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);if (!StringUtils.isEmpty(seckillOrderJson)) {return RespBean.error(RespBeanEnum.REPEATE_ERROR);}Order order = orderService.seckill(user, goods);if (null != order) {return RespBean.success(order);}

Seckill学习笔记——Day3(秒杀功能实现)相关推荐

  1. 从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!

    从零写一个具有IOC-AOP-MVC功能的框架-学习笔记 专栏往期文章链接: IOC功能相关章节: 从零写一个具有IOC-AOP-MVC功能的框架-学习笔记-01.项目初始化 从零写一个具有IOC-A ...

  2. 学海灯塔新增学习笔记上传功能

    又经过一天的努力,学海灯塔学习笔记上传功能实现.欢迎访问我们的学海灯塔 学习笔记这一模块的功能和课程文件类似,由同学们上传自己的学习笔记,用户可以下载,并且可以对笔记进行打分,后期将增加文件讨论功能. ...

  3. 《智能对话机器人开发实战20讲》--学习笔记--AIML基础功能拓展-与互联网的集成

    一.学习笔记 环境要求: aiml bs4 语料库: tuling.aiml search_web.aiml <that>WHICH SEARCH ENGINE WOULD YOU LIK ...

  4. Python数据分析三剑客学习笔记Day3——pandas包的使用:认识series类型,DataFrame类型,读取excel表格数据及数据操作

    本文是视频Python数据分析三剑客 数学建模基础 numpy.pandas.matplotlib的学习笔记. -------------------------------------------- ...

  5. C语言学习笔记Day3——持续更新中... ...

    上一篇文章C语言学习笔记Day2--持续更新中- - 八. 容器 1. 一维数组 1.1 什么是一维数组 当数组中每个元素都只带有一个下标(第一个元素的下标为0, 第二个元素的下标为1, 以此类推)时 ...

  6. 【学习笔记】秒杀系统架构设计

    秒杀其实主要解决两个问题 并发读 VS 并发写 并发读的核心优化理念是尽量减少用户到服务端来"读"数据,或者让他们读更少的数据 并发写的处理原则也一样,它要求我们在数据库层面独立出 ...

  7. 【三万字!】Dubbo、Zookeeper学习笔记!秒杀面试官!——双非上岸阿里巴巴系列

    东北某不知名双非本科,四面成功上岸阿里巴巴,在这里把自己整理的笔记分享出来,欢迎大家阅读. 恰个饭--><阿里巴巴 Java 开发手册>,业界普遍遵循的开发规范 本博客内容持续维护, ...

  8. node.js学习笔记Day3

    目录 第一部分:编写静态界面以及ajax请求 第二部分:我在当中遇到的报错 第三部分:我们来解决session存储用户信息的方法 今天我们简单的写一个登录验证的测试练习. 第一部分:编写静态界面以及a ...

  9. C++学习笔记day3

    目录 前言 一.类继承 1.1 继承概念(基类.派生类) 1.2 类创建顺序 1.3 继承权限 1.4 重定义(遮蔽) 二.虚函数 2.1 纯虚函数.虚析构 2.2 重写(覆盖) 2.3 多态 三.多 ...

  10. 【QBXT】学习笔记——Day3/4图论+dp

    继续上传一波笔记吧. Day3 1.16AM 今天讲图论,以习题为主. 开篇水题: 给一幅图,若删去一个点后变成一棵树,则这个点合法.问哪些点合法. 思路根据树的性质:这个点不是割点,m-这个点的度数 ...

最新文章

  1. 皮一皮:低调的凡尔赛...
  2. Python3.3 学习笔记1 - 初步安装
  3. linux 网卡丢弃多播包,rp_filter及Linux下多网卡接收多播的问题
  4. 搞不定 NodeJS 内存泄漏?先从了解垃圾回收开始
  5. Linux系统入门学习:在Debian或Ubuntu上安装完整的内核源码
  6. 2021牛客多校1 - Journey among Railway Stations(线段树区间合并)
  7. 发送广播_DHCP服务器什么时候发送?为什么request要广播发送?那还不看?
  8. 【LeetCode】跳水板
  9. FlyCms 是一个类似知乎以问答为基础的完全开源的JAVA语言开发的社交网络建站程序
  10. 陷阱房图纸_揭秘户型图 | 研究了100个户型图后,我发现了这4个重大陷阱
  11. Bugtags 测试平台(支持ios、android)
  12. 技术分享 | 一文带你了解测试流程体系
  13. Pandas——concat(合并)
  14. python代码编辑教程_python教程:pycharm编写代码的方式教学
  15. 本地VM安装虚拟机,使用xshell连接
  16. Java 8 异步 API、循环、日期,用好提高生产力!
  17. 中鸣机器人编程教程 c 语言,足球机器人编程(最好是图形化语言
  18. SAP QM数据库表清单
  19. Redmi K20 安卓9跨版本刷第三方ROM
  20. 页面请求方法参数最长_url传值的长度限制解决办法

热门文章

  1. 计算机中¥符号按哪个键,电脑键盘符号快捷键大全 电脑键盘上每个键的作用?...
  2. 计算机太极图怎么设计,AI绘制太极图两种方法介绍
  3. mentohust mac安装
  4. 【新知实验室】TRTC这么简单的demo快来试试吧
  5. linux 搜狗输入法隐藏状态栏
  6. 电脑桌面的计算机网络回收站图标不见了,桌面回收站图标不见了怎么办 回收站图标找回方法【图文】...
  7. [GPS]GPSGate x64下虚拟端口
  8. 【bat】bat批处理 用作为当前目录的以上路径启动了 CMD.EXE。 UNC 路径不受支持。默认值设为 Windows 目录。无法执行共享目录bat脚本处理方法
  9. C-V2X 网络层及适配层解析填充
  10. 如何拿下最适合晚上睡不着看的网站?建议收藏!