详见:具体实现流程
详见:具体实现流程
详见:具体实现流程
由于该项目前后端不分离,因此每次获取页面时,每次我们都需要进行查询渲染。这里我们考虑用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;
}
在登录的时,我们已经将用户的信息保存到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);
}
$(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}")
@ResponseBody
public 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)) {
//秒杀还未开始0
remainSeconds = (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);
}
OrderServiceImpl.java
//秒杀商品表减库存
SeckillGoods seckillGoods = seckillGoodsService.getOne(new QueryWrapper<SeckillGoods>().eq("goods_id",goods.getId())); //查询秒杀商品
seckillGoods.setStockCount(seckillGoods.getStockCount() - 1); //商品数减1
seckillGoodsService.update(new UpdateWrapper<SeckillGoods>().set("stock_count", seckillGoods.getStockCount()).eq("id", seckillGoods.getId()).gt("stock_count", 0)); //当商品的库存大于等于0时,再更新
解决同一用户同时秒杀多件商品——可以通过数据库建立唯一索引避免
这里解决的是同一个人发起了两次请求时,可能两个都还正在操作,均未抢购成功,致判断为未抢购,导致用户多次秒杀。
这里是线程安全的,利用了innodb行锁+复合索引的方式
将秒杀订单信息存入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);
}