从构建分布式秒杀系统聊聊验证码(代码片段)

smallsevens smallsevens     2023-01-03     584

关键词:

技术分享图片

前言

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

验证码

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

目前常见的验证码形式多为图片验证码,即数字、字母、文字、图片物体等形式的传统字符验证码。这类验证码看似简单易操作,但实际用户体验较差(参见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:

 <script src="https://ssl.captcha.qq.com/TCaptcha.js"></script>

页面元素:

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

JS回调:

<script type="text/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,并进入秒杀队列
    function startSeckill(res)
        $.ajax(
            url : "startSeckill",
            type : ‘post‘,
            data : ‘ticket‘ : res.ticket,‘randstr‘:res.randstr,
            success : function(result) 
                //验证是否通过,提示用户
            
        );
    
</script>

后端

@Api(tags = "秒杀商品")
@RestController
@RequestMapping("/seckillPage")
public class SeckillPageController 
    
    @Autowired
    private ActiveMQSender activeMQSender;
    //自定义工具类
    @Autowired
    private HttpClient httpClient;
    //这里自行配置参数
    @Value("$qq.captcha.url")
    private String url;
    @Value("$qq.captcha.aid")
    private String aid;
    @Value("$qq.captcha.AppSecretKey")
    private String appSecretKey;
    
    @RequestMapping("/startSeckill")
    public Result  startSeckill(String ticket,String randstr,HttpServletRequest request) 
        HttpMethod method =HttpMethod.POST;
        MultiValueMap<String, String> params= new LinkedMultiValueMap<String, String>();
        params.add("aid", aid);
        params.add("AppSecretKey", appSecretKey);
        params.add("Ticket", ticket);
        params.add("Randstr", randstr);
        params.add("UserIP", IPUtils.getIpAddr(request));
        String msg = 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);
        String response = (String) json.get("response");
        if("1".equals(response))
            //进入队列、假数据而已
            Destination destination = new ActiveMQQueue("seckill.queue");
            activeMQSender.sendChannelMess(destination,1000+";"+1);
            return Result.ok();
        else
            return Result.error("验证失败");
        
    

自定义请求工具类 HttpClient:

@Service
public class HttpClient 
    public String client(String url, HttpMethod method, MultiValueMap<String, String> params)
        RestTemplate client = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        //  请勿轻易改变此提交方式,大部分的情况下,提交方式都是表单提交
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(params, headers);
        //  执行HTTP请求
        ResponseEntity<String> response = client.exchange(url, HttpMethod.POST, requestEntity, String.class);
        return response.getBody();
    

获取IP地址工具类 IPUtils :

/**
 * IP地址
 */
public class 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 static String 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(","));
            
        
        return ip;
    

案例效果图

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

技术分享图片

技术分享图片

定制接入

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

<!-- 项目中使用了Vue -->
<div class="log_btn"  @click="login" >登录</div>
login: function () 
    //这里校验用户名以及密码
    // 直接生成一个验证码对象
    var captcha = new TencentCaptcha(‘2001344788‘, function(res) 
        if(res.ret === 0)//回调成功
            var data = ‘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(); // 显示验证码
,

后台监控

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

技术分享图片

小结

总体来说,系统接入人机验证码还是很方便的,并没有技术难点,难点已经被提供商封装,我们只需要简单的调用即可。

秒杀案例:https://gitee.com/52itstyle/spring-boot-seckill

演示案例(点击生成按钮):http://jichou.52itstyle.com

从构建分布式秒杀系统聊聊分布式锁(代码片段)

...感悟。路漫漫,借此,把前段时间搞着玩的秒杀案例中的分布式锁深入了解一下。案例介绍在尝试了解分布式锁之前,大家可以想象一下,什么场景下会使用分布式锁?单机应用架构中,秒杀案例使用ReentrantLcok或者synchronized来... 查看详情

从构建分布式秒杀系统聊聊lock锁使用中的坑(代码片段)

前言在单体架构的秒杀活动中,为了减轻DB层的压力,这里我们采用了Lock锁来实现秒杀用户排队抢购。然而很不幸的是尽管使用了锁,但是测试过程中仍然会超卖,执行了N多次发现依然有问题。输出一下代码吧,可能大家看的... 查看详情

从构建分布式秒杀系统聊聊lock锁使用中的坑(代码片段)

前言在单体架构的秒杀活动中,为了减轻DB层的压力,这里我们采用了Lock锁来实现秒杀用户排队抢购。然而很不幸的是尽管使用了锁,但是测试过程中仍然会超卖,执行了N多次发现依然有问题。输出一下代码吧,可能大家看的... 查看详情

从构建分布式秒杀系统聊聊分布式锁(代码片段)

...感悟。路漫漫,借此,把前段时间搞着玩的秒杀案例中的分布式锁深入了解一下。案例介绍在尝试了解分布式锁之前,大家可以想象一下,什么场景下会使用分布式锁?单机应用架构中,秒杀案例使用ReentrantLcok或者synchronized来... 查看详情

从构建分布式秒杀系统聊聊分布式锁

...感悟。路漫漫,借此,把前段时间搞着玩的秒杀案例中的分布式锁深入了解一下。案例介绍在尝试了解分布式锁之前,大家可以想象一下,什么场景下会使用分布式锁?单机应用架构中,秒杀案例使用ReentrantLcok或者synchronized来... 查看详情

秒杀系统——秒杀功能设计理念(代码片段)

...架构1)订单量:从1000到100万锁机制悲观锁乐观锁分布式锁消息队列消息队列:架构2从电商系统到秒杀系统流量限制热门资源隔离最终:架构3总结1.秒杀系统特点2.秒杀系统问题3.秒杀系统主要方案4.其他问题电商... 查看详情

秒杀系统——秒杀功能设计理念(代码片段)

...架构1)订单量:从1000到100万锁机制悲观锁乐观锁分布式锁消息队列消息队列:架构2从电商系统到秒杀系统流量限制热门资源隔离最终:架构3总结1.秒杀系统特点2.秒杀系统问题3.秒杀系统主要方案4.其他问题电商... 查看详情

springboot开发案例从0到1构建分布式秒杀系统

前言最近,被推送了不少秒杀架构的文章,忙里偷闲自己也总结了一下互联网平台秒杀架构设计,当然也借鉴了不少同学的思路。俗话说,脱离案例讲架构都是耍流氓,最终使用SpringBoot模拟实现了部分秒杀场景,同时跟大家分... 查看详情

聊聊滑块验证码的识别(代码片段)

很长一段时间没写文章了,今天来一篇,聊聊滑块验证码。之前一段时间在研究下滑块验证码相关的东西,拿腾讯的验证码来玩,使用Selenium来模拟滑动,这里也就先不介绍Selenium的基本用法来,主要来聊... 查看详情

基于redis分布式锁实现“秒杀”(代码片段)

最近在项目中遇到了类似“秒杀”的业务场景,在本篇博客中,我将用一个非常简单的demo,阐述实现所谓“秒杀”的基本思路。业务场景所谓秒杀,从业务角度看,是短时间内多个用户“争抢”资源,这里的资源在大部分秒杀... 查看详情

《java——帮你解决高并发秒杀》(代码片段)

【准备】首先我们要考虑的是为什么要解决高并发,高并发瓶颈出现在哪里,有了解过的朋友肯定知道是在数据库,因为在大量请求去操作数据库时会出现数据的错乱,超卖,系统崩溃,mysql死锁等现象。【思路】(一)、页面... 查看详情

聊聊滑块验证码的识别(代码片段)

很长一段时间没写文章了,今天来一篇,聊聊滑块验证码。之前一段时间在研究下滑块验证码相关的东西,拿腾讯的验证码来玩,使用Selenium来模拟滑动,这里也就先不介绍Selenium的基本用法来,主要来聊... 查看详情

分布式抽奖秒杀系统,ddd架构设计和实现分享(代码片段)

作者:小傅哥博客:https://bugstack.cn沉淀、分享、成长,让自己和他人都能有所收获!😄一、用大项目,贯穿知识体系写CRUD、堆API、改屎山⛰,熬多少个996也只是成为重复的螺丝钉。如果你希望捅破现... 查看详情

被问到kafka,不要再说你不会了(代码片段)

Kafka是一个优秀的分布式消息中间件,许多系统中都会使用到Kafka来做消息通信。对分布式消息系统的了解和使用几乎成为一个后台开发人员必备的技能。今天码哥字节就从常见的Kafka面试题入手,和大家聊聊Kafka的那些事儿。讲... 查看详情

爬虫进阶之分布式爬虫编写(代码片段)

本篇文章将是『如何构建一个分布式爬虫』系列文章的最后一篇,拟从实战角度来介绍如何构建一个稳健的分布式微博爬虫。这里我没敢谈高效,抓过微博数据的同学应该都知道微博的反爬虫能力,也知道微博数据抓取的瓶颈在... 查看详情

秒杀系统:如何打造并维护一个超大流量的秒杀系统?(代码片段)

秒杀大家都不陌生。自2011年首次出现以来,无论是双十一购物还是12306抢票,秒杀场景已随处可见。简单来说,秒杀就是在同一时刻大量请求争抢购买同一商品并完成交易的过程。从架构视角来看,秒杀系统本质是一个高性能、... 查看详情

聊聊高并发系统之限流特技(代码片段)

...而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/ 查看详情

秒杀系统实战|如何优雅的实现订单异步处理(代码片段)

...终于能喘口气了,继续把之前挖的坑填起来。写完上一篇秒杀系统(四):数据库与缓存双写一致性深入分析后,感觉文章深度一下子被我抬高了一些,现在构思新文章的时候,反而畏手畏脚,不敢随便写了。对于未来文章内容... 查看详情