基于springboot+redis+rabbitmq的高并发秒杀系统实现-3

项目源码下载地址:

https://github.com/wangqianlong513/springboot-redis-rabbitmq-seckill

上文讲到了登录成功,然后开始往后台/goods/to_list跳转。

1、/goods/to_list方法如下。注意此处使用了thymeleaf模板引擎,通过引擎生成了html文件。@RequestMapping(value="/to_list", produces="text/html")中的produdes=“text/html”表示此方法将返回html格式的文件。在list方法中,先通过goodsSevice方法查询到商品列表数据。然后把列表数据存入到上下文SpringWebContxt中,最后再把上下文作为参数,传入到thymeleaf的视图解析器的模板引擎中,从而产生html文件,最终把html文件返回。

    @RequestMapping(value="/to_list", produces="text/html")
    @ResponseBody
    public String list(HttpServletRequest request, HttpServletResponse response, Model model,MiaoshaUser user) {
    	model.addAttribute("user", user);
    	//取缓存
    	List<GoodsVo> goodsList = goodsService.listGoodsVo();
    	model.addAttribute("goodsList", goodsList);
    	SpringWebContext ctx = new SpringWebContext(request,response,
    			request.getServletContext(),request.getLocale(), model.asMap(), applicationContext );
    	//手动渲染
    	String html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);
    	if(!StringUtils.isEmpty(html)) {
    		redisService.set(GoodsKey.getGoodsList, "", html);
    	}
    	return html;
    }

2、good_list.html文件内容如下。主要是商品的列表展示,通过table标签分行展示,对后台查询的数据goodsList通过th:each进行遍历展示。然后有一个详情超链接,此超链接往商品详情页goods_detail.htm跳转,注意:是直接由一个html向另外一个html文件跳转,传入参数是商品id:goods.id。

<table class="table" id="goodslist">
  	<tr><td>商品名称</td><td>商品图片</td><td>商品原价</td><td>秒杀价</td><td>库存数量</td><td>详情</td></tr>
  	<tr  th:each="goods,goodsStat : ${goodsList}">  
                <td th:text="${goods.goodsName}"></td>  
                <td ><img th:src="@{${goods.goodsImg}}" width="100" height="100" /></td>  
                <td th:text="${goods.goodsPrice}"></td>  
                <td th:text="${goods.miaoshaPrice}"></td>  
                <td th:text="${goods.stockCount}"></td>
                <td><a th:href="'/goods_detail.htm?goodsId='+${goods.id}">详情</a></td>  
     </tr>  
  </table>

3、goods_detail.htm文件主要内容如下。现在问题来了,因为这个页面是从上一个goods_list页面跳转过来的,而且跳转的时候仅仅传递了goods_id,那么这个detail页面中的数据是如何获取到的呢?这里是通过页面中的js函数进行查询的。我们知道,在JQuery中,有$(document).ready(function(){})或者$(function(){})函数,作用是在网页加载完毕后需要执行的function方法,与普通的js方法不同,此方法使用了符号$。此处也是使用了类似的功能。PS:此处使用的是页面静态化技术。一般有两种方案来应对高并发,一种是此处的静态化技术,还有一种就是缓存技术。

<table class="table" id="goodslist">
  	<tr>  
        <td>商品名称</td>  
        <td colspan="3" id="goodsName"></td> 
     </tr>  
     <tr>  
        <td>商品图片</td>  
        <td colspan="3"><img  id="goodsImg" width="200" height="200" /></td>  
     </tr>
     <tr>  
        <td>秒杀开始时间</td>  
        <td id="startTime"></td>
        <td >	
        	<input type="hidden" id="remainSeconds" />
        	<span id="miaoshaTip"></span>
        </td>
        <td>
        <!--  
        	<form id="miaoshaForm" method="post" action="/miaosha/do_miaosha">
        		<button class="btn btn-primary btn-block" type="submit" id="buyButton">立即秒杀</button>
        		<input type="hidden" name="goodsId"  id="goodsId" />
        	</form>-->
        	<div class="row">
        		<div class="form-inline">
		        	<img id="verifyCodeImg" width="80" height="32"  style="display:none" onclick="refreshVerifyCode()"/>
		        	<input id="verifyCode"  class="form-control" style="display:none"/>
		        	<button class="btn btn-primary" type="button" id="buyButton"onclick="getMiaoshaPath()">立即秒杀</button>
        		</div>
        	</div>
        	<input type="hidden" name="goodsId"  id="goodsId" />
        </td>
     </tr>
     <tr>  
        <td>商品原价</td>  
        <td colspan="3" id="goodsPrice"></td>  
     </tr>
      <tr>  
        <td>秒杀价</td>  
        <td colspan="3"  id="miaoshaPrice"></td>  
     </tr>
     <tr>  
        <td>库存数量</td>  
        <td colspan="3"  id="stockCount"></td>  
     </tr>
  </table>

4、detail详情页中有下面js方法,作用是在页面加载完成后执行这个js方法,这个方法中调用了getDetail()函数

$(function(){
   getDetail();
});

5、getDetail()函数如下。此函数接收从goods_list中传递过来的商品id(g_getQueryString就是自定义的从URL地址中获取指定名称对应的参数值的方法):goods_id,然后通过ajax调用后台的方法/goods/deatail方法,同时要传入参数goodsId。

function getDetail(){
	var goodsId = g_getQueryString("goodsId");
	$.ajax({
		url:"/goods/detail/"+goodsId,
		type:"GET",
		success:function(data){
			if(data.code == 0){
				render(data.data);
			}else{
				layer.msg(data.msg);
			}
		},
		error:function(){
			layer.msg("客户端请求有误");
		}
	});
}

6、后台的/goods/detail方法如下。此方法作用是根据商品id查询商品的详信息并返回。

@RequestMapping(value="/detail/{goodsId}")
    @ResponseBody
    public Result<GoodsDetailVo> detail(HttpServletRequest request, HttpServletResponse response, Model model,MiaoshaUser user,
    		@PathVariable("goodsId")long goodsId) {
    	GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
    	long startAt = goods.getStartDate().getTime();
    	long endAt = goods.getEndDate().getTime();
    	long now = System.currentTimeMillis();
    	int miaoshaStatus = 0;
    	int remainSeconds = 0;
    	if(now < startAt ) {//秒杀还没开始,倒计时
    		miaoshaStatus = 0;
    		remainSeconds = (int)((startAt - now )/1000);
    	}else  if(now > endAt){//秒杀已经结束
    		miaoshaStatus = 2;
    		remainSeconds = -1;
    	}else {//秒杀进行中
    		miaoshaStatus = 1;
    		remainSeconds = 0;
    	}
    	GoodsDetailVo vo = new GoodsDetailVo();
    	vo.setGoods(goods);
    	vo.setUser(user);
    	vo.setRemainSeconds(remainSeconds);
    	vo.setMiaoshaStatus(miaoshaStatus);
    	return Result.success(vo);
    }

7、上述6中的方法返回成功后,会返回到上述5中的getDetail方法中的success模块。此模块中会调用render方法来渲染页面。render方法如下。此方法会把上述6中返回的数据“填充”到detail页面中。至此,可以看到商品详情的基本数据。同时此render方法中也调用了countDown方法。

function render(detail){
	var miaoshaStatus = detail.miaoshaStatus;
	var  remainSeconds = detail.remainSeconds;
	var goods = detail.goods;
	var user = detail.user;
	if(user){
		$("#userTip").hide();
	}
	$("#goodsName").text(goods.goodsName);
	$("#goodsImg").attr("src", goods.goodsImg);
	$("#startTime").text(new Date(goods.startDate).format("yyyy-MM-dd hh:mm:ss"));
	$("#remainSeconds").val(remainSeconds);
	$("#goodsId").val(goods.id);
	$("#goodsPrice").text(goods.goodsPrice);
	$("#miaoshaPrice").text(goods.miaoshaPrice);
	$("#stockCount").text(goods.stockCount);
	countDown();
}

8、countDown方法如下。此方法主要是对秒杀时间进行控制处理,比如秒杀进行中、秒杀已经结束和倒计时功能。其中倒计时中,使用了setTimeout方法,设置参数为1000,表示每隔1000ms(1s)中执行一个更新剩余时间的操作。有个需要特别注意的地方,对于秒杀进行中的商品,也就是允许秒杀的商品,在点击“秒杀”按钮之前,需要先填入验证码,此处属于秒杀系统的“流量销峰”的优化操作。这个验证码,调用了后台的/miaosha/verifyCode方法,传入了goodsId参数。

function countDown(){
	var remainSeconds = $("#remainSeconds").val();
	var timeout;
	if(remainSeconds > 0){//秒杀还没开始,倒计时
	   $("#buyButton").attr("disabled", true);
	   $("#miaoshaTip").html("秒杀倒计时:"+remainSeconds+"秒");
		timeout = setTimeout(function(){
			$("#countDown").text(remainSeconds - 1);
			$("#remainSeconds").val(remainSeconds - 1);
			countDown();
		},1000);
	}else if(remainSeconds == 0){//秒杀进行中
		$("#buyButton").attr("disabled", false);
		if(timeout){
			clearTimeout(timeout);
		}
		$("#miaoshaTip").html("秒杀进行中");
		$("#verifyCodeImg").attr("src", "/miaosha/verifyCode?goodsId="+$("#goodsId").val());
		$("#verifyCodeImg").show();
		$("#verifyCode").show();
	}else{//秒杀已经结束
		$("#buyButton").attr("disabled", true);
		$("#miaoshaTip").html("秒杀已经结束");
		$("#verifyCodeImg").hide();
		$("#verifyCode").hide();
	}
}

9、后台的/miaosha/verifyCode方法如下。方法中调用了miaoshaService中的createVerifyCode方法。此方法返回一个BufferedImage类型的对象,在java中,Image是一个抽象类,BufferedImage是其实现类,是一个带缓冲区图像类,主要作用是将一幅图片加载到内存中(BufferedImage生成的图片在内存里有一个图像缓冲区,利用这个缓冲区我们可以很方便地操作这个图片),提供获得绘图对象、图像缩放、选择图像平滑度等功能,通常用来做图片大小变换、图片变灰、设置透明不透明等。然后通过ImageIO(ImageIO框架提供了读取与写入图片数据的基本方法,使用它可以直接获取到图片文件的内容数据)的write方法把内存中的图像数据已JPEG的格式输出。

@RequestMapping(value="/verifyCode", method=RequestMethod.GET)
    @ResponseBody
    public Result<String> getMiaoshaVerifyCod(HttpServletResponse response,MiaoshaUser user,
    		@RequestParam("goodsId")long goodsId) {
    	if(user == null) {
    		return Result.error(CodeMsg.SESSION_ERROR);
    	}
    	try {
    		BufferedImage image  = miaoshaService.createVerifyCode(user, goodsId);
    		OutputStream out = response.getOutputStream();
    		ImageIO.write(image, "JPEG", out);
    		out.flush();
    		out.close();
    		return null;
    	}catch(Exception e) {
    		e.printStackTrace();
    		return Result.error(CodeMsg.MIAOSHA_FAIL);
    	}
    }

10、miaoshaService中的createVerifyCode的方法如下。此方法就是具体的生成验证码的过程。此处的验证码是数字的四则运算,当随机生成了操作符(+、-、*、/)和操作数的时候,调用了calc方法来计算运算结果。同时把计算结果存入缓存(为什么存入缓存?因为待会用户输入验证码后就可以直接从redis中取出正确的计算结果进行匹配啦)。最后返回BufferedImage对象。

public BufferedImage createVerifyCode(MiaoshaUser user, long goodsId) {
		if(user == null || goodsId <=0) {
			return null;
		}
		int width = 80;
		int height = 32;
		//create the image
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		Graphics g = image.getGraphics();
		// set the background color
		g.setColor(new Color(0xDCDCDC));
		g.fillRect(0, 0, width, height);
		// draw the border
		g.setColor(Color.black);
		g.drawRect(0, 0, width - 1, height - 1);
		// create a random instance to generate the codes
		Random rdm = new Random();
		// make some confusion
		for (int i = 0; i < 50; i++) {
			int x = rdm.nextInt(width);
			int y = rdm.nextInt(height);
			g.drawOval(x, y, 0, 0);
		}
		// generate a random code
		String verifyCode = generateVerifyCode(rdm);
		g.setColor(new Color(0, 100, 0));
		g.setFont(new Font("Candara", Font.BOLD, 24));
		g.drawString(verifyCode, 8, 24);
		g.dispose();
		//把验证码存到redis中
		int rnd = calc(verifyCode);
		redisService.set(MiaoshaKey.getMiaoshaVerifyCode, user.getId()+","+goodsId, rnd);
		//输出图片	
		return image;
	}

11、calc方法如下。此方法是通过JavaScript引擎来计算验证码结果。

private static int calc(String exp) {
		try {
			ScriptEngineManager manager = new ScriptEngineManager();
			ScriptEngine engine = manager.getEngineByName("JavaScript");
			return (Integer)engine.eval(exp);
		}catch(Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

12、在detail页面的验证码标签单击,会刷新验证码。js方法如下。和上述生成二维码的过程是一样的。

<img id="verifyCodeImg" width="80" height="32"  style="display:none" onclick="refreshVerifyCode()"/>
function refreshVerifyCode(){
	$("#verifyCodeImg").attr("src", "/miaosha/verifyCode?goodsId="+$("#goodsId").val()+"&timestamp="+new Date().getTime());
}

 

至此,商品列表展示、商品详情展示已经介绍完。接下来在商品详情页单击“秒杀”按钮就可以进行商品秒杀了。下篇继续讲解。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值