秒杀商品展示及商品秒杀
创始人
2024-05-25 04:12:09
0

目录

  • 一、登录方式调整
  • 二、生成秒杀订单
    • 1、绑定秒杀商品
    • 2、查看秒杀商品
    • 3、订单秒杀
      • ①移除seata相关
      • ②生成秒杀订单
      • ③前端页面秒杀测试

一、登录方式调整

第1步:从zmall-common的pom.xml中移除spring-session-data-redis依赖
在这里插入图片描述

注意:
1)本章节中不采用spring-session方式,改用redis直接存储用户登录信息,主要是为了方便之后的jmeter压测;
2)这里只注释调用spring-session的依赖,保留redis的依赖;

第2步:在zmall-common公共模块中定义RedisConfig配置类

package com.zking.zmall.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate restTemplate(RedisConnectionFactory redisConnectionFactory){RedisTemplate redisTemplate=new RedisTemplate<>();//String类型Key序列化redisTemplate.setKeySerializer(new StringRedisSerializer());//String类型Value序列化redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());//Hash类型Key序列化redisTemplate.setHashKeySerializer(new StringRedisSerializer());//Hash类型Value序列化redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.setConnectionFactory(redisConnectionFactory);return redisTemplate;}
}

这里一定要注意,最后在将RedisConnectionFactory设置到RedisTemplate中,不要在最前做该步操作,不然会导致String和Hash类型的序列化无效,将采用默认的JdkSerializationRedisSerializer进行序列化,从而导致保存的key前缀出现乱码问题。细心!!!

第3步:在zmall-common公共模块中配置redis相关服务
IRedisServcie

package com.zking.zmall.service;import com.zking.zmall.model.User;public interface IRedisService {/*** 将登陆用户对象保存到Redis中,并以token来命名* @param token* @param user*/void setUserToRedis(String token, User user);/*** 根据token令牌从Redis中获取User对象* @param token* @return*/User getUserByToken(String token);
}

RedisServcieImple

package com.zking.zmall.service.impl;import com.zking.zmall.model.User;
import com.zking.zmall.service.IRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
public class RedisServiceImpl implements IRedisService {@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic void setUserToRedis(String token, User user) {String key="user:"+token;redisTemplate.boundValueOps(key).set(user,7200, TimeUnit.SECONDS);}@Overridepublic User getUserByToken(String token) {return (User) redisTemplate.opsForValue().get("user:"+token);}
}

用户登录成功后,将用户对象保存到Redis中,并设置超时时间7200秒。

第4步:在zmall-common公共模块中配置,配置自定义参数解析UserArgumentResolver、WebConfig
UserArgumentResolver

package com.zking.zmall.config;import com.zking.zmall.exception.BusinessException;
import com.zking.zmall.model.User;
import com.zking.zmall.service.IRedisService;
import com.zking.zmall.util.CookieUtils;
import com.zking.zmall.util.JsonResponseStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;import javax.servlet.http.HttpServletRequest;/*** 自定义用户参数类*/
@Component
public class UserArgumentResolver implements HandlerMethodArgumentResolver {@Autowiredprivate IRedisService redisService;/*** 只有supportsParameter方法执行返回true,才能执行下面的resolveArgument方法* @param methodParameter* @return*/@Overridepublic boolean supportsParameter(MethodParameter methodParameter) {Class type = methodParameter.getParameterType();return type== User.class;}@Overridepublic Object resolveArgument(MethodParameter methodParameter,ModelAndViewContainer modelAndViewContainer,NativeWebRequest nativeWebRequest,WebDataBinderFactory webDataBinderFactory) throws Exception {HttpServletRequest req= (HttpServletRequest) nativeWebRequest.getNativeRequest();//从cookie获取token令牌String token = CookieUtils.getCookieValue(req, "token");//判断cookie中的token令牌是否为空if(StringUtils.isEmpty(token))throw new BusinessException(JsonResponseStatus.TOKEN_ERROR);//根据token令牌获取redis中存储的user对象,方便jmeter测试User user = redisService.getUserByToken(token);if(null==user)throw new BusinessException(JsonResponseStatus.TOKEN_ERROR);return user;}
}

WebConfig

package com.zking.zmall.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.List;@Component
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate UserArgumentResolver userArgumentResolver;@Overridepublic void addArgumentResolvers(List resolvers) {resolvers.add(userArgumentResolver);}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {//添加静态资源访问映射//registry.addResourceHandler("/static/**")//        .addResourceLocations("classpath:/static/");}
}

第5步:用户登录业务调整,将spring-session方式更改为redis方式存储登录用户信息。

package com.zking.zmall.service.impl;import com.alibaba.nacos.common.utils.MD5Utils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zking.zmall.mapper.UserMapper;
import com.zking.zmall.model.User;
import com.zking.zmall.service.IUserService;
import com.zking.zmall.util.CookieUtils;
import com.zking.zmall.util.JsonResponseBody;
import com.zking.zmall.util.JsonResponseStatus;
import com.zking.zmall.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.UUID;/*** 

* 服务实现类*

** @author xnx* @since 2023-02-06*/ @Service public class UserServiceImpl extends ServiceImpl implements IUserService {@Autowiredprivate RedisServiceImpl redisService;@Overridepublic JsonResponseBody userLogin(UserVo user,HttpServletRequest req,HttpServletResponse resp) {//1.判断用户账号和密码是否为空if(StringUtils.isEmpty(user.getLoginName())||StringUtils.isEmpty(user.getPassword()))return new JsonResponseBody<>(JsonResponseStatus.USERNAME_OR_PWD_EMPTY);//2.根据用户名查询数据对应的用户信息User us = this.getOne(new QueryWrapper().eq("loginName", user.getLoginName()));//3.判断us用户对象是否为空if(null==us)return new JsonResponseBody<>(JsonResponseStatus.USERNAME_ERROR);try {//MD5加密转换处理String pwd= MD5Utils.md5Hex(user.getPassword().getBytes());//4.判断输入密码与数据库表存储密码是否一致if(!us.getPassword().equals(pwd)){return new JsonResponseBody<>(JsonResponseStatus.PASSWORD_ERROR);}} catch (Exception e) {e.printStackTrace();return new JsonResponseBody<>(JsonResponseStatus.ERROR);} // //5.通过UUID生成token令牌并保存到cookie中 // String token= UUID.randomUUID().toString().replace("-",""); // //将随机生成的Token令牌保存到Cookie中,并设置1800秒超时时间 // CookieUtils.setCookie(req,resp,"token",token,7200); // //6.将token令牌与spring session进行绑定并存入redis中 // HttpSession session = req.getSession(); // session.setAttribute(token,us);//5.通过UUID生成token令牌并保存到cookie中String token= UUID.randomUUID().toString().replace("-","");//将随机生成的Token令牌保存到Cookie中,并设置1800秒超时时间CookieUtils.setCookie(req,resp,"token",token,7200);//6.将token令牌与spring session进行绑定并存入redis中//HttpSession session = req.getSession();//session.setAttribute(token,us);//将token令牌与user绑定后存储到redis中,方便jmeter测试redisService.setUserToRedis(token,us);return new JsonResponseBody<>(token);} }

这里采用Redis方式直接存储登录用户信息,只为后续使用Jmeter压测时提供便利。正常运行使用项目还是可以使用spring-session方式。

第6步:修改商品服务zmall-product模块中的index方法,将之前从HttpSession中获取登录用户信息改换成User对象参数方式

@RequestMapping("/index.html")
public String index(Model model, User user){System.out.println(user);
}

在调用index方法之前,先由自定义的参数解析器进行参数解析并返回解析结果User,所以在这里可直接在方法参数中获取的User对象。

第7步:重启zmall-user和zmall-product模块,完成用户登录后,直接在浏览器地址栏输入:http://zmall.com/product-serv/index.html,查看zmall-product模块中的控制台是否已经获取到登录用户对象信息。
在这里插入图片描述

二、生成秒杀订单

1、绑定秒杀商品

添加sellDetail.html页面到zmall-product模块中;实现首页秒杀商品展示,必须保证秒杀商品状态为已激活、且秒杀商品的活动时间为有效时间范围之内。

index.html

 <#if kills??><#list kills as g>
${g.price}

${g.name}

倒计时:12003028

sellDetail.html

${(prod.name)!}
¥${(prod.price)!}抢购
市场价 折扣
¥${(prod.price)!}8.0
型号:
  • 30ml
  • 50ml
  • 100ml
颜色:
  • 红色
  • 白色
  • 黑色
数量:
距离团购结束还有
1200 时 30 分 28 秒

web层

@RequestMapping("/index.html")public ModelAndView toIndex(User user){System.out.println("user:"+ JSON.toJSONString(user));ModelAndView mv=new ModelAndView();//获取热卖商品列表List hot = productService.list(new QueryWrapper().orderByDesc("hot").last("limit 4"));//获取显示秒杀商品List> maps = productService.queryKillProdNews();mv.addObject("kills",maps);mv.addObject("hots",hot);mv.setViewName("index");return mv;}

service层

public interface IProductService extends IService {void updateStock(Integer pid,Integer num);/*** 首页显示秒杀商品查询* @return*/List> queryKillProdNews();/*** 根据商品ID查询秒杀商品信息* @param pid 秒杀商品ID* @return*/Map queryKillProdById(Integer pid);
}
@Overridepublic List> queryKillProdNews() {return productMapper.queryKillProdNews();}@Overridepublic Map queryKillProdById(Integer pid) {return productMapper.queryKillProdById(pid);}

mapper层

@Repository
public interface ProductMapper extends BaseMapper {
//    @MapKey("queryKillProdNews")List> queryKillProdNews();Map queryKillProdById(Integer pid);
}

productMapper.xml


在这里插入图片描述

2、查看秒杀商品

点击限时秒杀中的秒杀商品,根据秒杀商品ID查询秒杀商品详情信息并跳转到sellDetail.html页面展示秒杀商品信息。

@RequestMapping("/sellDetail.html")public String sellDetail(@RequestParam Integer pid, Model model){//根据商品ID查询秒杀商品信息Map prod = productService.queryKillProdById(pid);model.addAttribute("prod",prod);return "sellDetails";}

在这里插入图片描述

3、订单秒杀

①移除seata相关

第1步:先注释掉zmall-order和zmall-product模块中的seata依赖

第2步:分别删掉zmall-order和zmall-product模块中resources目录下的bootstrap.xml和register.conf文件

seata分布式事务,进行jmeter压测秒杀订单接口效率太低(1000个并发请求,吞吐量为4.5/s)

②生成秒杀订单

将SnowFlake雪花ID生成工具类导入到zmall-common模块中utils,然后再生成秒杀订单时使用雪花ID来充当秒杀订单编号;在zmall-order模块中完成秒杀订单生成工作。
IOrderService

public interface IOrderService extends IService {Order createOrder(Integer pid,Integer num);/*** 生成秒杀订单* @param user 登陆用户对象* @param pid  秒杀商品ID* @param price 秒杀商品价格* @return*/JsonResponseBody createKillOrder(User user, Integer pid, Float price);
}

OrderServiceImpl

@Autowired
private KillServiceImpl killService;
@Autowired
private OrderDetailServiceImpl orderDetailService;@Transactional@Overridepublic JsonResponseBody createKillOrder(User user, Integer pid, Float price) {//1.根据秒杀商品编号获取秒杀商品库存是否为空Kill kill = killService.getOne(new QueryWrapper().eq("item_id",pid));if(kill.getTotal()<1)throw new BusinessException(JsonResponseStatus.STOCK_EMPTY);//2.秒杀商品库存减一killService.update(new UpdateWrapper().eq("item_id",pid).setSql("total=total-1"));//3.生成秒杀订单及订单项SnowFlake snowFlake=new SnowFlake(2,3);Long orderId=snowFlake.nextId();int orderIdInt = new Long(orderId).intValue();//创建订单Order order=new Order();order.setUserId(user.getId());order.setLoginName(user.getLoginName());order.setCost(price);order.setSerialNumber(orderIdInt+"");this.save(order);//创建订单项OrderDetail orderDetail=new OrderDetail();orderDetail.setOrderId(orderIdInt);orderDetail.setProductId(pid);orderDetail.setQuantity(1);orderDetail.setCost(price);orderDetailService.save(orderDetail);return new JsonResponseBody();}

OrderController

@RequestMapping("/createKillOrder/{pid}/{price}")@ResponseBodypublic JsonResponseBody createKillOrder(User user,@PathVariable("pid") Integer pid,@PathVariable("price") Float price){return orderService.createKillOrder(user,pid,price);}

③前端页面秒杀测试

在sellDetail.html页面中添加订单秒杀JS方法。


在这里插入图片描述

这里虽然已经能正常展示秒杀效果,但是还是存在很多问题,比如:重复抢购问题等等问题。

注意:

 $.post('http://user.zmall.com/userLogin',{loginName:loginName,password:password},function(rs){console.log(rs);if(rs.code===200){location.href='http://product.zmall.com/index.html';}else{alert(rs.msg);}},'json');

post方式不能跨二级域名发送请求,location.href可以跨二级域名发送请求;

$(function(){$('.ch_a').click(function(){let pid=$(this).attr("pid");let price=$(this).attr("price");console.log("pid=%s,price=%s",pid,price);$.post('http://zmall.com/order-serv/createKillOrder/'+pid+'/'+price,{},function(rs){console.log(rs);if(rs.code===200)alert('秒杀成功');elsealert(rs.msg);},'json');});});

$.post(‘http://zmall.com/order-serv/createKillOrder/’+pid+‘/’+price,{},function(rs){});能够正常访问;

$.post(‘http://order.zmall.com/createKillOrder/’+pid+‘/’+price,{},function(rs){});则会出现跨域问题;

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...