秒杀商品展示及商品秒杀
目录
一、登录方式调整
二、秒杀商品展示&秒杀商品详情&商品秒杀功能
1.生成秒杀订单
2.绑定秒杀商品
3.查看看秒商品
4.订单秒杀
① 移除seata相关
② 生成秒杀订单
③ 前端页面秒杀测试
一、登录方式调整
第1步:从zmall-common的pom.xml中移除spring-session-data-redis依赖
(注意:由于我的spring session没有了,用户以及商品和订单可能会报错注释掉就好了)
注意:
1)本章节中不采用spring-session方式,改用redis直接存储用户登录信息,主要是为了方便之后的jmeter压测;
2)这里只注释调用spring-session的依赖,保留redis的依赖;
springboot 集成redis 只要yml中添加redis相关配置,那么Spring容器就会初始化一个redisConnection对象
第2步:在zmall-common公共模块中定义ReidsConfig配置类
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String,Object> restTemplate(RedisConnectionFactory redisConnectionFactory){RedisTemplate<String,Object> 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前缀出现乱码问题。细心!!!细心!!!细心!!!o(╥﹏╥)o
参考地址:RedisTemplate写入Redis数据出现无意义乱码前缀\xac\xed\x00\x05_hunger_wang的博客-CSDN博客_\xac
第3步:在zmall-common公共模块中配置redis相关服务,建一个包叫service
IRedisService
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
@Service
public class RedisServiceImpl implements IRedisService {@Autowiredprivate RedisTemplate<String,Object> 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
/*** 自定义用户参数类*/
@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
@Component
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate UserArgumentResolver userArgumentResolver;@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(userArgumentResolver);}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {//添加静态资源访问映射//registry.addResourceHandler("/static/**")// .addResourceLocations("classpath:/static/");}
}
第5步:用户登录业务调整,将spring-session方式更改为redis方式存储登录用户信息。
UserServiceimpl
@Autowiredprivate RedisServiceImpl redisService;
...//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);
这里采用Redis方式直接存储登录用户信息,只为后续使用Jmeter压测时提供便利。正常运行使用项目还是可以使用spring-session方式。
第6步:修改商品服务zmall-product模块中的index方法,将之前从HttpSession中获取登录用户信息改换成User对象参数方式
@RequestMapping("/index.html")public String index(Model model, User user){System.out.println(user);
// 按照商品的销量降序排序获取销量排名Top5的商品List<Product> products = productService.list(new QueryWrapper<Product>().orderByDesc("hot").last("limit 5"));model.addAttribute("hots",products);return "index";}
在调用index方法之前,先由自定义的参数解析器进行参数解析并返回解析结果User,所以在这里可直接在方法参数中获取的User对象。
在启动项目之前要先启动我们的nacos和nginx在启动我们的项目
第7步:重启zmall-user和zmall-product模块,http://zmall.com/user-serv/login.html完成用户登录后,直接在浏览器地址栏输入:http://zmall.com/product-serv/index.html,查看zmall-product模块中的控制台是否已经获取到登录用户对象信息。
二、秒杀商品展示&秒杀商品详情&商品秒杀功能
1.生成秒杀订单
我们用的表zmall-kill,由于我们之前的表没有数据,所以我们要手动添加数据如下图所示:
/*Navicat Premium Data TransferSource Server : 本地连接Source Server Type : MySQLSource Server Version : 80013Source Host : localhost:3306Source Schema : zmallTarget Server Type : MySQLTarget Server Version : 80013File Encoding : 65001Date: 10/02/2023 10:09:28
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for zmall_kill
-- ----------------------------
DROP TABLE IF EXISTS `zmall_kill`;
CREATE TABLE `zmall_kill` (`id` int(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',`item_id` int(11) NULL DEFAULT NULL COMMENT '商品id',`total` int(11) NULL DEFAULT NULL COMMENT '可被秒杀的总数',`start_time` datetime(0) NULL DEFAULT NULL COMMENT '秒杀开始时间',`end_time` datetime(0) NULL DEFAULT NULL COMMENT '秒杀结束时间',`is_active` tinyint(11) NULL DEFAULT 1 COMMENT '是否有效(1=是;0=否)',`create_time` timestamp(0) NULL DEFAULT NULL COMMENT '创建的时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '待秒杀商品表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of zmall_kill
-- ----------------------------
INSERT INTO `zmall_kill` VALUES (1, 733, 10, '2023-02-09 00:04:56', '2024-02-10 00:05:01', 1, '2023-02-10 00:05:13');
INSERT INTO `zmall_kill` VALUES (2, 734, 9, '2023-02-09 00:04:56', '2024-02-10 00:05:01', 1, '2023-02-10 00:05:59');
INSERT INTO `zmall_kill` VALUES (3, 735, 9, '2023-02-09 00:04:56', '2024-02-10 00:05:01', 1, '2023-02-10 00:06:16');
INSERT INTO `zmall_kill` VALUES (4, 736, 10, '2023-02-09 00:04:56', '2024-02-10 00:05:01', 1, '2023-02-10 00:06:34');
INSERT INTO `zmall_kill` VALUES (5, 737, 10, '2023-02-09 00:04:56', '2024-02-10 00:05:01', 0, '2023-02-10 00:06:53');
INSERT INTO `zmall_kill` VALUES (6, 738, 10, '2023-02-09 00:04:56', '2024-02-10 00:05:01', 1, '2023-02-10 00:07:10');SET FOREIGN_KEY_CHECKS = 1;
2.绑定秒杀商品
添加sellDetail.html页面到zmall-product模块中;实现首页秒杀商品展示,必须保证秒杀商品状态为已激活、且秒杀商品的活动时间为有效时间范围之内。
将资料目录中的《易买网网页素材.rar》,将其中SellDetails.html添加到zmall-product项目的templates下,最好请将SellDetails.html页面首字母改成小写
将页面中的头部申明<!DOCTYPE html ....>修改成<!DOCTYPE html>(支持H5风格)
在页面中通过<#include>指令引入common目录中的head.html
index.html 找到我们显示秒杀的地方修改为如下代码。
<#if kills??><#list kills as g><div class="sell_${g_index?if_exists+1}"><div class="sb_img"><a href="${ctx}/sellDetail.html?pid=${g.item_id}"><img src="${g.fileName}" width="242" height="356" /></a></div><div class="s_price">¥<span>${g.price}</span></div><div class="s_name"><h2><a href="${ctx}/sellDetail.html?pid=${g.item_id}">${g.name}</a></h2>倒计时:<span>1200</span> 时 <span>30</span> 分 <span>28</span> 秒</div></div></#list></#if>
把我们生成的mapper复制到公共模块中,如下图所示:可以把生成中的mapper删除掉了。
ProductMapper.xml
<select id="queryKillProdNews" resultType="java.util.Map">selectk.id,k.item_id,p.name,p.price,p.fileNamefromzmall_kill k,zmall_product pwhere k.item_id=p.id andk.is_active=1 and(now() between start_time and end_time)order by k.create_time desclimit 4</select><select id="queryKillProdById" resultType="java.util.Map">selectk.id,k.item_id,k.total,p.name,p.price,p.fileNamefromzmall_kill k,zmall_product pwhere k.item_id=p.id and k.is_active=1 and item_id=#{value}</select>
mapper层
@Repository
public interface ProductMapper extends BaseMapper<Product> {
// @MapKey("queryKillProdNews")List<Map<String,Object>> queryKillProdNews();Map<String,Object> queryKillProdById(Integer pid);
}
service层
public interface IProductService extends IService<Product> {void updateStock(Integer pid,Integer num);/*** 首页显示秒杀商品查询* @return*/List<Map<String,Object>> queryKillProdNews();/*** 根据商品ID查询秒杀商品信息* @param pid 秒杀商品ID* @return*/Map<String,Object> queryKillProdById(Integer pid);
}
ProductServiceImpl实现层
@Autowiredprivate ProductMapper productMapper;@Overridepublic List<Map<String, Object>> queryKillProdNews() {return productMapper.queryKillProdNews();}@Overridepublic Map<String, Object> queryKillProdById(Integer pid) {return productMapper.queryKillProdById(pid);}
controller层
方法一:@RequestMapping("/index.html")public String index(Model model, User user){System.out.println(user);
// 按照商品的销量降序排序获取销量排名Top5的商品List<Product> products = productService.list(new QueryWrapper<Product>().orderByDesc("hot").last("limit 5"));model.addAttribute("hots",products);//获取显示秒杀商品List<Map<String, Object>> maps = productService.queryKillProdNews();model.addAttribute("kills",maps);return "index";}方法二:
@RequestMapping("/index.html")public ModelAndView toIndex(User user){System.out.println("user:"+ JSON.toJSONString(user));ModelAndView mv=new ModelAndView();//获取热卖商品列表List<Product> hot = productService.list(new QueryWrapper<Product>().orderByDesc("hot").last("limit 4"));//获取显示秒杀商品List<Map<String, Object>> maps = productService.queryKillProdNews();mv.addObject("kills",maps);mv.addObject("hots",hot);mv.setViewName("index");return mv;}
我们已经绑定秒杀商品成功了,全是从数据库里查询出来的。
没修改之前
修改之后
3.查看看秒商品
点击限时秒杀中的秒杀商品,根据秒杀商品ID查询秒杀商品详情信息并跳转到sellDetail.html页面展示秒杀商品信息。
ProductController
@RequestMapping("/sellDetail.html")public String sellDetail(@RequestParam Integer pid, Model model){//根据商品ID查询秒杀商品信息Map<String, Object> prod = productService.queryKillProdById(pid);model.addAttribute("prod",prod);return "sellDetails";}
sellDetail.html
<table border="0" style="width:100%; margin-bottom:50px;" cellspacing="0" cellpadding="0"><tr valign="top"><td width="315"><div class="lim_name">${(prod.name)!}</div><div class="lim_price"><span class="ch_txt">¥${(prod.price)!}</span><a href="javascript:void(0);" class="ch_a" pid="${(prod.item_id)!}" price="${(prod.price)!}">抢购</a></div><div class="lim_c"><table border="0" style="width:100%; color:#888888;" cellspacing="0" cellpadding="0"><tr><td width="35%">市场价 </td><td width="65%">折扣</td></tr><tr style="font-family:'Microsoft YaHei';"><td style="text-decoration:line-through;">¥${(prod.price)!}</td><td>8.0</td></tr></table></div><div class="lim_c"><div class="des_choice"><span class="fl">型号:</span><ul><li class="checked">30ml<div class="ch_img"></div></li><li>50ml<div class="ch_img"></div></li><li>100ml<div class="ch_img"></div></li></ul></div><div class="des_choice"><span class="fl">颜色:</span><ul><li>红色<div class="ch_img"></div></li><li class="checked">白色<div class="ch_img"></div></li><li>黑色<div class="ch_img"></div></li></ul></div></div><div class="lim_c"><span class="fl">数量:</span><input type="text" value="${(prod.total)!}" class="lim_ipt" /></div><div class="lim_clock">距离团购结束还有<br /><span>1200 时 30 分 28 秒</span></div></td><td width="525" align="center" style="border-left:1px solid #eaeaea;"><img src="${(prod.fileName)!}" width="460" height="460" /></td></tr></table>
4.订单秒杀
① 移除seata相关
第1步:先注释掉zmall-order和zmall-product模块中的seata依赖
第2步:分别删掉zmall-order和zmall-product模块中resources目录下的bootstrap.xml和register.conf文件
seata分布式事务,进行jmeter压测秒杀订单接口效率太低(1000个并发请求,吞吐量为4.5/s)o(╥﹏╥)o
② 生成秒杀订单
将SnowFlake雪花ID生成工具类导入到zmall-common模块中utils,然后再生成秒杀订单时使用雪花ID来充当秒杀订单编号;在zmall-order模块中完成秒杀订单生成工作。
IOrderService
public interface IOrderService extends IService<Order> {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<Kill>().eq("item_id",pid));if(kill.getTotal()<1)throw new BusinessException(JsonResponseStatus.STOCK_EMPTY);//2.秒杀商品库存减一killService.update(new UpdateWrapper<Kill>().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);}
把我们的启动类上的数据源删除
package com.zking.zmall;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;@EnableDiscoveryClient
@EnableFeignClients
@MapperScan({"com.zking.zmall.mapper"})
@SpringBootApplication
public class ZmallOrderApplication {public static void main(String[] args) {SpringApplication.run(ZmallOrderApplication.class, args);}}
③ 前端页面秒杀测试
在sellDetail.html页面中添加订单秒杀JS方法
<script>$(function(){$('.ch_a').click(function(){let pid=$(this).attr('pid');let price=$(this).attr('price');console.log(pid);console.log(price);$.post('http://zmall.com/order-serv/createKillOrder',{pid:pid,price:price},function(rs){console.log(rs);if(rs.code===200)alert('秒杀成功');elsealert(rs.msg);},'json');});});
</script>
启动类
这里虽然已经能正常展示秒杀效果,但是还是存在很多问题,比如:重复抢购问题等等问题。
注意:当我们是以下代码的时候
$.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){});则会出现跨域问题;
秒杀商品展示及商品秒杀相关推荐
- 微服务项目【秒杀商品展示及商品秒杀】
登录方式调整 第1步:从zmall-common的pom.xml中移除spring-session-data-redis依赖 注意: 1)本次不采用spring-session方式,改用redis直接 ...
- 秒杀项目之秒杀商品展示及商品秒杀
目录 前言 一.登录方式调整 二.生成秒杀订单 2.1 绑定秒杀商品 2.2 查看秒杀商品 2.3 订单秒杀 2.3.1 移除seata相关(方便测压) 2.3.2 生成秒杀订单 2.3.3 前端页面 ...
- 秒杀项目之商品展示及商品秒杀
目录 登录方式调整 生成秒杀订单 绑定秒杀商品 查看秒杀商品 订单秒杀 移除seata相关 生成秒杀订单 前端页面秒杀测试 登录方式调整 第1步:从zmall-common的pom.xml中移除spr ...
- java秒杀系统 代码大全_Java秒杀系统:商品秒杀代码实战
内容: "商品秒杀"功能模块是建立在"商品详情"功能模块的基础之上,对于这一功能模块而言,其主要的核心流程在于:前端发起抢购请求,该请求将携带着一些请求数据:待 ...
- 网站服务器时间秒杀,Javascript实现商品秒杀倒计时(时间与服务器时间同步)...
现在有很多网站都在做秒杀商品,而这其中有一个很重要的环节就是倒计时. 关于倒计时,有下面几点需要注意: 1.应该使用服务器时间而不是本地时间(本地时间存在时区不同.用户自行设置等问题). 2.要考虑网 ...
- 电商项目实战之商品秒杀
电商项目实战之商品秒杀 定时任务 corn表达式 实现方式 基于注解 基于接口 实战 秒杀系统 秒杀系统关注问题 秒杀架构设计 商品上架 获取当前秒杀商品 获取当前商品的秒杀信息 秒杀最终处理 参考链 ...
- php商品秒杀功能笔记
系统环境搭建 基础:linux+php+mysql+redis 升级:CDN,只能DNS,分布式缓存,全国多节点,多线路接入(内容分发网络) 硬件升级:负载均衡LVS (高效稳定) 大型WEB集群 环 ...
- 高性能商品秒杀抢购系统
完整资料进入[数字空间]查看--baidu搜索"writebug" Go+iris+rabbbitmq+mysql构建高性能商品秒杀抢购系统 一.项目介绍 1. 课程目标 应用Go ...
- Spring Boot + redis解决商品秒杀库存超卖,看这篇文章就够了
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:涛哥谈篮球 来源:toutiao.com/i68366119 ...
最新文章
- springMvc+mybatis+spring 整合 包涵整合activiti 基于maven
- linux-squirrel
- [Android] android的消息队列机制
- burp suite 二级域名扫描插件
- 重载-运算符(感觉怪怪的)
- Python简单的拼写检查
- Javascript远程加载框架 - JFO Remote Framework 1.0
- 解决问题--DatabaseMetaData的getTables()返回所有数据库的表信息
- java 静态相关内容
- Codeforces 627D Preorder Test(二分+树形DP)
- 设计师谈中望CAD2010应用心得 作者:刘国勤
- Coverity 配置coverity扫描python静态代码检测
- html5手机app抽奖页面,app H5活动抽奖转盘 前端+后台
- matlab 对信号抽样,matlab信号抽样与恢复
- Android - 跳转系统网络设置
- Stefan - 人生目前学到的21样事
- Android 5.0、6.0、7.0、8.0、9.0 新特性,DownloadManager踩坑记
- 基于JavaWeb的影视创作论坛的设计与实现/影视系统
- CC2640R2F BLE5.0 CC2640R2F UART驱动
- NF_Exp4_20164306