前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从构建分布式秒杀系统聊聊验证码

从构建分布式秒杀系统聊聊验证码

作者头像
java架构师
发布2019-03-19 14:32:03
1.2K0
发布2019-03-19 14:32:03
举报
文章被收录于专栏:Java架构师进阶Java架构师进阶

前言

为了拦截大部分请求,秒杀案例前端引入了验证码。淘宝上很多人吐槽,等输入完秒杀活动结束了,对,结束了...... 当然了,验证码的真正作用是,有效拦截刷单操作,让羊毛党空手而归。

验证码

那么到底什么是验证码呢?验证码作为一种人机识别手段,其终极目的,就是区分正常人和机器的操作。我们常见的互联网注册、登录、发帖、领优惠券、投票等等应用场景,都有被机器刷造成各类损失的风险。

目前常见的验证码形式多为图片验证码,即数字、字母、文字、图片物体等形式的传统字符验证码。这类验证码看似简单易操作,但实际用户体验较差(参见12306网站),且随着OCR技术和打码平台的利用,图片比较容易被破解,被破解之后就形同虚设。

这里我们使用腾讯的智能人机安全验证码,告别传统验证码的单点防御,十道安全栅栏打造立体全面的安全验证,将黑产拒之门外。

场景

下面我们来瞅瞅验证码轻松解决了那些场景安全问题:

登录注册,为你防护撞库攻击、阻止注册机批量注册

活动秒杀,有效拦截刷单操作,让羊毛党空手而归

点赞发帖,有效解决广告屠版、恶意灌水、刷票问题

数据保护,防止自动机、爬虫盗取网页内容和数据

申请

申请地址:https://007.qq.com/product.html

在线体验:https://007.qq.com/online.html

只要一个QQ就可以免费申请,对于一般的企业OA系统或者个人博客网站,验证码免费套餐足够了已经,具备以下特点:

2000次/小时安全防护

支持免验证+分级验证

三分钟快速接入

全功能配置后台

支持HTTPS

阈值内流量无广告

2000次/小时的安全防护,一般很少达到如此效果,当然了即时超出阈值,顶多也就是多个广告而已。

接入

快读接入:https://007.qq.com/quick-start.html

接入与帮助提供了多种客户端和服务端的接入案例,这里我们使用我们秒杀案例中最熟悉的Java语言来接入。

前端

引入JS:

页面元素:

代码语言:javascript
复制
<!--点击此元素会自动激活验证码,不一定是button,其他标签也可以-->
<!--id : 元素的id(必须)-->
<!--data-appid : AppID(必须)-->
<!--data-cbfn : 回调函数名(必须)--><!--data-biz-state : 业务自定义透传参数(可选)-->
验证

JS回调:

代码语言:javascript
复制
window.callback =function(res){
  console.log(res)
  // res(未通过验证)= {ret: 1, ticket: null}
  // res(验证成功) = {ret: 0, ticket: "String", randstr: "String"}
  if(res.ret ===0){ 
    startSeckill(res) 
  } 
}
//后台验证ticket,并进入秒杀队列
functionstartSeckill(res){
  $.ajax({url:"startSeckill",type:'post',data: {'ticket': res.ticket,'randstr':res.randstr},success:function(result){//验证是否通过,提示用户} }); 
}

后端

代码语言:javascript
复制
@Api(tags ="秒杀商品")@RestController@RequestMapping("/seckillPage")publicclassSeckillPageController {
  @AutowiredprivateActiveMQSender activeMQSender;//自定义工具类
  @AutowiredprivateHttpClient httpClient;//这里自行配置参数
  @Value("${qq.captcha.url}")privateStringurl;
  @Value("${qq.captcha.aid}")privateStringaid;
  @Value("${qq.captcha.AppSecretKey}")privateStringappSecretKey;
  @RequestMapping("/startSeckill")publicResult startSeckill(Stringticket,Stringrandstr,HttpServletRequest request) { 
    HttpMethod method =HttpMethod.POST; 
    MultiValueMap params=newLinkedMultiValueMap(); 
    params.add("aid", aid); params.add("AppSecretKey", appSecretKey); 
    params.add("Ticket", ticket); 
    params.add("Randstr", randstr); 
    params.add("UserIP", IPUtils.getIpAddr(request));
    Stringmsg = httpClient.client(url,method,params);/**
   * response: 1:验证成功,0:验证失败,100:AppSecretKey参数校验错误[required]
   * evil_level:[0,100],恶意等级[optional]
   * err_msg:验证错误信息[optional]
   *///{"response":"1","evil_level":"0","err_msg":"OK"}
   JSONObject json = JSONObject.parseObject(msg);
   Stringresponse = (String) json.get("response");
   if("1".equals(response)){
     //进入队列、假数据而已
     Destination destination =newActiveMQQueue("seckill.queue"); 
     activeMQSender.sendChannelMess(destination,1000+";"+1);
     returnResult.ok(); 
   }
   else{
     returnResult.error("验证失败"); 
   }
  }
}

自定义请求工具类 HttpClient:

代码语言:javascript
复制
@ServicepublicclassHttpClient{ 
  publicStringclient(Stringurl, HttpMethod method, MultiValueMap params){ 
    RestTemplate client =newRestTemplate(); 
    HttpHeaders headers =newHttpHeaders();
    // 请勿轻易改变此提交方式,大部分的情况下,提交方式都是表单提交
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); 
    HttpEntity> requestEntity =newHttpEntity>(params, headers);
    // 执行HTTP请求ResponseEntity 
    response = client.exchange(url, HttpMethod.POST, requestEntity,String.class);returnresponse.getBody(); 
  }
}

获取IP地址工具类 IPUtils :

代码语言:javascript
复制
/**
 * IP地址
 */publicclass IPUtils { private static Logger logger = LoggerFactory.getLogger(IPUtils.class);/**
 * 获取IP地址
 * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
 * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
 */
 public staticString getIpAddr(HttpServletRequest request) {
   String ip= null;
   try {
     ip= request.getHeader("x-forwarded-for");
     if(StringUtils.isEmpty(ip) ||"unknown".equalsIgnoreCase(ip)) {
       ip= request.getHeader("Proxy-Client-IP");
     }
     if(StringUtils.isEmpty(ip) ||ip.length() ==0||"unknown".equalsIgnoreCase(ip)) {
       ip= request.getHeader("WL-Proxy-Client-IP");
     }
     if(StringUtils.isEmpty(ip) ||"unknown".equalsIgnoreCase(ip)) {
       ip= request.getHeader("HTTP_CLIENT_IP");
     }
     if(StringUtils.isEmpty(ip) ||"unknown".equalsIgnoreCase(ip)) {
       ip= request.getHeader("HTTP_X_FORWARDED_FOR");
     }
     if(StringUtils.isEmpty(ip) ||"unknown".equalsIgnoreCase(ip)) {
       ip= request.getRemoteAddr();
     } 
   } 
   catch (Exception e) { 
     logger.error("IPUtils ERROR ", e);
   } 
   // 使用代理,则获取第一个IP地址
   if(StringUtils.isEmpty(ip) &&ip.length() >15) {
     if(ip.indexOf(",") >0) {
       ip=ip.substring(0, ip.indexOf(","));
     } 
   } 
   returnip;
 }
}

案例效果图

启动项目访问:http://localhost:8080/seckill/1000.shtml

定制接入

在系统登录的时候,我们需要先校验用户名以及密码,然后调用验证码操作,这里就需要我们定制接入了。

代码语言:javascript
复制
登录login:function(){
  //这里校验用户名以及密码
  // 直接生成一个验证码对象
  varcaptcha =newTencentCaptcha('2001344788',function(res){
    if(res.ret ===0){
      //回调成功
      vardata = {'username':username,'password':password,'ticket':res.ticket,'randstr':res.randstr} 
      $.ajax({type:"POST",url:"sys/loginCaptcha",data: data,dataType:"json",success:function(result){//校验是否成功} }); 
    } 
  }); 
  captcha.show();// 显示验证码
}

后台监控

腾讯后台还提供了简单实用的数据监控,如下:

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.03.12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
验证码
腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档