一、隐藏秒杀地址

思路:秒杀开始前,先去请求接口获取秒杀地址

1.接口改造,带上PathVariable参数

2.添加生成地址的接口

3.秒杀收到请求,先验证PathVariable

 

二、数学公式验证码

1.添加生产验证码接口

2.在获取秒杀路径的时候,验证验证码

3.ScriptEngine使用

 

package com.wings.seckill.controller;


import java.awt.image.BufferedImage;

import java.io.OutputStream;

import java.util.HashMap;

import java.util.List;


import javax.imageio.ImageIO;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


import org.springframework.beans.factory.InitializingBean;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.bind.annotation.ResponseBody;


import com.wings.seckill.access.AccessLimit;

import com.wings.seckill.domain.SeckillOrder;

import com.wings.seckill.domain.SeckillUser;

import com.wings.seckill.rabbitmq.MQSender;

import com.wings.seckill.rabbitmq.SeckillMessage;

import com.wings.seckill.redis.GoodsKey;

import com.wings.seckill.redis.OrderKey;

import com.wings.seckill.redis.RedisService;

import com.wings.seckill.redis.SeckillKey;

import com.wings.seckill.result.CodeMsg;

import com.wings.seckill.result.Result;

import com.wings.seckill.service.GoodsService;

import com.wings.seckill.service.OrderService;

import com.wings.seckill.service.SeckillService;

import com.wings.seckill.service.SeckillUserService;

import com.wings.seckill.vo.GoodsVo;


@Controller

@RequestMapping("/seckill")

public class SeckillController implements InitializingBean {


@Autowired

SeckillUserService userService;


@Autowired

RedisService redisService;


@Autowired

GoodsService goodsService;


@Autowired

OrderService orderService;


@Autowired

SeckillService seckillService;


@Autowired

MQSender sender;


private HashMap<Long, Boolean> localOverMap = new HashMap<Long, Boolean>();


@Override

public void afterPropertiesSet() throws Exception {

List<GoodsVo> goodsList = goodsService.listGoodsVo();

if (goodsList == null) {

return;

}

for (GoodsVo goods : goodsList) {

redisService.set(GoodsKey.getSeckillGoodsStock, "" + goods.getId(), goods.getStockCount());

localOverMap.put(goods.getId(), false);

}


}


@RequestMapping(value = "/{path}/do_seckill", method = RequestMethod.POST)

@ResponseBody

public Result<Integer> list(Model model, SeckillUser user, @RequestParam("goodsId") long goodsId,

@PathVariable("path") String path) {

model.addAttribute("user", user);

if (user == null) {

return Result.error(CodeMsg.SESSION_ERROR);

}

// 验证path

boolean check = seckillService.checkPath(user, goodsId, path);

if (!check) {

return Result.error(CodeMsg.REQUEST_ILLEGAL);

}

// 内存标记,减少redis访问

boolean over = localOverMap.get(goodsId);

if (over) {

return Result.error(CodeMsg.SECKill_OVER);

}

// 预减库存

long stock = redisService.decr(GoodsKey.getSeckillGoodsStock, "" + goodsId);

if (stock < 0) {

localOverMap.put(goodsId, true);

return Result.error(CodeMsg.SECKill_OVER);

}

// 判断是否已经秒杀到了

SeckillOrder order = orderService.getSeckillOrderByUserIdGoodsId(user.getId(), goodsId);

if (order != null) {

return Result.error(CodeMsg.REPEATE_SECKILL);

}

// 入队

SeckillMessage mm = new SeckillMessage();

mm.setUser(user);

mm.setGoodsId(goodsId);

sender.sendSeckillMessage(mm);

return Result.success(0);// 排队中


}


@RequestMapping(value = "/reset", method = RequestMethod.GET)

@ResponseBody

public Result<Boolean> reset(Model model) {

List<GoodsVo> goodsList = goodsService.listGoodsVo();

for (GoodsVo goods : goodsList) {

goods.setStockCount(10);

redisService.set(GoodsKey.getSeckillGoodsStock, "" + goods.getId(), 10);

localOverMap.put(goods.getId(), false);

}

redisService.delete(OrderKey.getSeckillOrderByUidGid);

redisService.delete(SeckillKey.isGoodsOver);

seckillService.reset(goodsList);

return Result.success(true);

}


/**

* orderId:成功 -1:秒杀失败 0: 排队中

*/

@RequestMapping(value = "/result", method = RequestMethod.GET)

@ResponseBody

public Result<Long> seckillResult(Model model, SeckillUser user, @RequestParam("goodsId") long goodsId) {

model.addAttribute("user", user);

if (user == null) {

return Result.error(CodeMsg.SESSION_ERROR);

}

long result = seckillService.getSeckillResult(user.getId(), goodsId);

return Result.success(result);

}


@AccessLimit(seconds = 5, maxCount = 5, needLogin = true)

@RequestMapping(value = "/path", method = RequestMethod.GET)

@ResponseBody

public Result<String> getSeckillPath(HttpServletRequest request, SeckillUser user,

@RequestParam("goodsId") long goodsId,

@RequestParam(value = "verifyCode", defaultValue = "0") int verifyCode) {

if (user == null) {

return Result.error(CodeMsg.SESSION_ERROR);

}

boolean check = seckillService.checkVerifyCode(user, goodsId, verifyCode);

if (!check) {

return Result.error(CodeMsg.REQUEST_ILLEGAL);

}

String path = seckillService.createSeckillPath(user, goodsId);

return Result.success(path);

}


@RequestMapping(value = "/verifyCode", method = RequestMethod.GET)

@ResponseBody

public Result<String> getSeckillVerifyCod(HttpServletResponse response, SeckillUser user,

@RequestParam("goodsId") long goodsId) {

if (user == null) {

return Result.error(CodeMsg.SESSION_ERROR);

}

try {

BufferedImage image = seckillService.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.SECKILL_FAIL);

}

}

}

package com.wings.seckill.service;


import java.awt.Color;

import java.awt.Font;

import java.awt.Graphics;

import java.awt.image.BufferedImage;

import java.util.List;

import java.util.Random;


import javax.script.ScriptEngine;

import javax.script.ScriptEngineManager;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;


import com.wings.seckill.domain.OrderInfo;

import com.wings.seckill.domain.SeckillOrder;

import com.wings.seckill.domain.SeckillUser;

import com.wings.seckill.redis.RedisService;

import com.wings.seckill.redis.SeckillKey;

import com.wings.seckill.util.Md5Util;

import com.wings.seckill.util.UUIDUtil;

import com.wings.seckill.vo.GoodsVo;


@Service

public class SeckillService {


@Autowired

GoodsService goodsService;


@Autowired

OrderService orderService;


@Autowired

RedisService redisService;


@Transactional

public OrderInfo seckill(SeckillUser user, GoodsVo goods) {

// 减库存 下订单 写入秒杀订单

boolean success = goodsService.reduceStock(goods);

if (success) {

// order_info maiosha_order

return orderService.createOrder(user, goods);

} else {

setGoodsOver(goods.getId());

return null;

}

}


public long getSeckillResult(Long userId, long goodsId) {

SeckillOrder order = orderService.getSeckillOrderByUserIdGoodsId(userId, goodsId);

if (order != null) {// 秒杀成功

return order.getOrderId();

} else {

boolean isOver = getGoodsOver(goodsId);

if (isOver) {

return -1;

} else {

return 0;

}

}

}


private void setGoodsOver(Long goodsId) {

redisService.set(SeckillKey.isGoodsOver, "" + goodsId, true);

}


private boolean getGoodsOver(long goodsId) {

return redisService.exists(SeckillKey.isGoodsOver, "" + goodsId);

}


public void reset(List<GoodsVo> goodsList) {

goodsService.resetStock(goodsList);

orderService.deleteOrders();

}


public boolean checkPath(SeckillUser user, long goodsId, String path) {

if (user == null || path == null) {

return false;

}

String pathOld = redisService.get(SeckillKey.getSeckillPath, "" + user.getId() + "_" + goodsId, String.class);

return path.equals(pathOld);

}


public String createSeckillPath(SeckillUser user, long goodsId) {

if (user == null || goodsId <= 0) {

return null;

}

String str = Md5Util.md5(UUIDUtil.uuid() + "123456");

redisService.set(SeckillKey.getSeckillPath, "" + user.getId() + "_" + goodsId, str);

return str;

}


public BufferedImage createVerifyCode(SeckillUser 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(SeckillKey.getSeckillVerifyCode, user.getId() + "," + goodsId, rnd);

// 输出图片

return image;

}


public boolean checkVerifyCode(SeckillUser user, long goodsId, int verifyCode) {

if (user == null || goodsId <= 0) {

return false;

}

Integer codeOld = redisService.get(SeckillKey.getSeckillVerifyCode, user.getId() + "," + goodsId,

Integer.class);

if (codeOld == null || codeOld - verifyCode != 0) {

return false;

}

redisService.delete(SeckillKey.getSeckillVerifyCode, user.getId() + "," + goodsId);

return true;

}


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;

}

}


private static char[] ops = new char[] { '+', '-', '*' };


/**

* + - *

*/

private String generateVerifyCode(Random rdm) {

int num1 = rdm.nextInt(10);

int num2 = rdm.nextInt(10);

int num3 = rdm.nextInt(10);

char op1 = ops[rdm.nextInt(3)];

char op2 = ops[rdm.nextInt(3)];

String exp = "" + num1 + op1 + num2 + op2 + num3;

return exp;

}

}

<!DOCTYPE HTML>

<html >

<head>

<title>商品详情</title>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

<!-- jquery -->

<script type="text/javascript" src="/js/jquery.min.js"></script>

<!-- bootstrap -->

<link rel="stylesheet" type="text/css" href="/bootstrap/css/bootstrap.min.css" />

<script type="text/javascript" src="/bootstrap/js/bootstrap.min.js"></script>

<!-- jquery-validator -->

<script type="text/javascript" src="/jquery-validation/jquery.validate.min.js"></script>

<script type="text/javascript" src="/jquery-validation/localization/messages_zh.min.js"></script>

<!-- layer -->

<script type="text/javascript" src="/layer/layer.js"></script>

<!-- md5.js -->

<script type="text/javascript" src="/js/md5.min.js"></script>

<!-- common.js -->

<script type="text/javascript" src="/js/common.js"></script>

<style type="text/css">

html,body{

height:100%;

width:100%;

}

body{

background:url('/img/bg2.jpg') no-repeat;

background-size:100% 100%;

}

#goodslist td{

border-top:1px solid #39503f61;

}

</style>

</head>

<body>


<div class="panel panel-default" style="height:100%;background-color:rgba(222,222,222,0.8)" >

<div class="panel-heading">秒杀商品详情</div>

<div class="panel-body">

<span id="userTip"> 您还没有登录,请登陆后再操作<br/></span>

<span>没有收货地址的提示。。。</span>

</div>

<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="seckillTip"></span>

</td>

<td>

<!--

<form id="seckillForm" method="post" action="/seckill/do_seckill">

<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="getSeckillPath()">立即秒杀</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="seckillPrice"></td>

</tr>

<tr>

<td>库存数量</td>

<td colspan="3" id="stockCount"></td>

</tr>

</table>

</div>

</body>

<script>


function getSeckillPath(){

var goodsId = $("#goodsId").val();

g_showLoading();

$.ajax({

url:"/seckill/path",

type:"GET",

data:{

goodsId:goodsId,

verifyCode:$("#verifyCode").val()

},

success:function(data){

if(data.code == 0){

var path = data.data;

doSeckill(path);

}else{

layer.msg(data.msg);

}

},

error:function(){

layer.msg("客户端请求有误");

}

});

}


function getSeckillResult(goodsId){

g_showLoading();

$.ajax({

url:"/seckill/result",

type:"GET",

data:{

goodsId:$("#goodsId").val(),

},

success:function(data){

if(data.code == 0){

var result = data.data;

if(result < 0){

layer.msg("对不起,秒杀失败");

}else if(result == 0){//继续轮询

setTimeout(function(){

getSeckillResult(goodsId);

}, 200);

}else{

layer.confirm("恭喜你,秒杀成功!查看订单?", {btn:["确定","取消"]},

function(){

window.location.href="/order_detail.htm?orderId="+result;

},

function(){

layer.closeAll();

});

}

}else{

layer.msg(data.msg);

}

},

error:function(){

layer.msg("客户端请求有误");

}

});

}


function doSeckill(path){

$.ajax({

url:"/seckill/"+path+"/do_seckill",

type:"POST",

data:{

goodsId:$("#goodsId").val()

},

success:function(data){

if(data.code == 0){

//window.location.href="/order_detail.htm?orderId="+data.data.id;

getSeckillResult($("#goodsId").val());

}else{

layer.msg(data.msg);

}

},

error:function(){

layer.msg("客户端请求有误");

}

});


}


function render(detail){

var seckillStatus = detail.seckillStatus;

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);

$("#seckillPrice").text(goods.seckillPrice);

$("#stockCount").text(goods.stockCount);

countDown();

}


$(function(){

//countDown();

getDetail();

});


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("客户端请求有误");

}

});

}


function countDown(){

var remainSeconds = $("#remainSeconds").val();

var timeout;

if(remainSeconds > 0){//秒杀还没开始,倒计时

$("#buyButton").attr("disabled", true);

$("#seckillTip").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);

}

$("#seckillTip").html("秒杀进行中");

$("#verifyCodeImg").attr("src", "/seckill/verifyCode?goodsId="+$("#goodsId").val());

$("#verifyCodeImg").show();

$("#verifyCode").show();

}else{//秒杀已经结束

$("#buyButton").attr("disabled", true);

$("#seckillTip").html("秒杀已经结束");

$("#verifyCodeImg").hide();

$("#verifyCode").hide();

}

}

function refreshVerifyCode(){

$("#verifyCodeImg").attr("src", "/seckill/verifyCode?goodsId="+$("#goodsId").val()+"&timestamp="+new Date().getTime());

}

</script>

</html>

 

三、接口防刷

思路:对接口做限流

1.可以用拦截器减少对业务侵入

 

package com.wings.seckill.access;


import com.wings.seckill.domain.SeckillUser;


public class UserContext {


private static ThreadLocal<SeckillUser> userHolder = new ThreadLocal<SeckillUser>();


public static void setUser(SeckillUser user) {

userHolder.set(user);

}


public static SeckillUser getUser() {

return userHolder.get();

}


}

package com.wings.seckill.access;


import static java.lang.annotation.ElementType.METHOD;

import static java.lang.annotation.RetentionPolicy.RUNTIME;


import java.lang.annotation.Retention;

import java.lang.annotation.Target;


@Retention(RUNTIME)

@Target(METHOD)

public @interface AccessLimit {

int seconds();

int maxCount();

boolean needLogin() default true;

}

package com.wings.seckill.access;


import java.io.OutputStream;


import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


import org.apache.commons.lang3.StringUtils;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import org.springframework.web.method.HandlerMethod;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;


import com.alibaba.fastjson.JSON;

import com.wings.seckill.domain.SeckillUser;

import com.wings.seckill.redis.AccessKey;

import com.wings.seckill.redis.RedisService;

import com.wings.seckill.result.CodeMsg;

import com.wings.seckill.result.Result;

import com.wings.seckill.service.SeckillUserService;


@Service

public class AccessInterceptor extends HandlerInterceptorAdapter{


@Autowired

SeckillUserService userService;


@Autowired

RedisService redisService;


@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

throws Exception {

if(handler instanceof HandlerMethod) {

SeckillUser user = getUser(request, response);

UserContext.setUser(user);

HandlerMethod hm = (HandlerMethod)handler;

AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);

if(accessLimit == null) {

return true;

}

int seconds = accessLimit.seconds();

int maxCount = accessLimit.maxCount();

boolean needLogin = accessLimit.needLogin();

String key = request.getRequestURI();

if(needLogin) {

if(user == null) {

render(response, CodeMsg.SESSION_ERROR);

return false;

}

key += "_" + user.getId();

}else {

//do nothing

}

AccessKey ak = AccessKey.withExpire(seconds);

Integer count = redisService.get(ak, key, Integer.class);

if(count == null) {

redisService.set(ak, key, 1);

}else if(count < maxCount) {

redisService.incr(ak, key);

}else {

render(response, CodeMsg.ACCESS_LIMIT_REACHED);

return false;

}

}

return true;

}


private void render(HttpServletResponse response, CodeMsg cm)throws Exception {

response.setContentType("application/json;charset=UTF-8");

OutputStream out = response.getOutputStream();

String str = JSON.toJSONString(Result.error(cm));

out.write(str.getBytes("UTF-8"));

out.flush();

out.close();

}


private SeckillUser getUser(HttpServletRequest request, HttpServletResponse response) {

String paramToken = request.getParameter(SeckillUserService.COOKIE_TOKEN_NAME);

String cookieToken = getCookieValue(request, SeckillUserService.COOKIE_TOKEN_NAME);

if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {

return null;

}

String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;

return userService.getByToken( token, response);

}


private String getCookieValue(HttpServletRequest request, String cookiName) {

Cookie[] cookies = request.getCookies();

if(cookies == null || cookies.length <= 0){

return null;

}

for(Cookie cookie : cookies) {

if(cookie.getName().equals(cookiName)) {

return cookie.getValue();

}

}

return null;

}


}

package com.wings.seckill.config;


import java.util.List;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.method.support.HandlerMethodArgumentResolver;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;


import com.wings.seckill.access.AccessInterceptor;


@Configuration

public class WebConfig extends WebMvcConfigurerAdapter {


@Autowired

private UserArgumentResolver userArgumentResolver;


@Autowired

AccessInterceptor accessInterceptor;


@Override

public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {

argumentResolvers.add(userArgumentResolver);

}


@Override

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(accessInterceptor);

}

}

 

 

---------------------

作者:插上小翅膀的程序猿Wings

版权声明:本文为博主原创文章,转载请附上博文链接!