驗(yàn)證碼WEB端產(chǎn)品調(diào)研(二):極限驗(yàn)證

本文是驗(yàn)證碼系列的第二篇。筆者的寫(xiě)作順序基于心中排名,所以這次為大家?guī)?lái)的是極限驗(yàn)證——GeeTest。

寫(xiě)到這里可能會(huì)有人質(zhì)疑:難道阿里、騰訊、網(wǎng)易(以下簡(jiǎn)稱ATN)的名氣和實(shí)力不是比極驗(yàn)強(qiáng)嗎?不可否認(rèn),但我們討論的只是驗(yàn)證碼:

  • 邏輯上,知名度高,綜合實(shí)力強(qiáng),不代表垂直領(lǐng)域?qū)嵙σ惨欢◤?qiáng)——當(dāng)然,并不否認(rèn)ATN具備這樣的人才和實(shí)力。這應(yīng)該很好理解。玩吉他的人都知道YAMAHA,但YAMAHA的吉他卻不見(jiàn)得是最好的——大師可能更喜歡Martin or Taylor,或者純私人訂制。換句話說(shuō):如果你有一把YAMAHA FG730,發(fā)朋友圈會(huì)有很多人點(diǎn)贊豎大拇指,但是也有人會(huì)給你一個(gè)手動(dòng)微笑。

  • 市場(chǎng)上,極驗(yàn)在驗(yàn)證碼領(lǐng)域已深耕多年,合作伙伴從直播、金融、電商,到資訊、游戲、航旅,甚至...政府,且號(hào)稱有16w家企業(yè)正在使用。實(shí)力和人脈,可見(jiàn)一斑;而ATN的驗(yàn)證碼服務(wù)只是其云平臺(tái)下的一個(gè)分支,官網(wǎng)均未專門(mén)介紹具體合作方和數(shù)量。筆者雖沒(méi)有詳實(shí)的數(shù)據(jù)證明ATN接入方比極驗(yàn)少,但從個(gè)人上網(wǎng)經(jīng)歷來(lái)看,極驗(yàn)顯然更“面熟”。起碼簡(jiǎn)書(shū)都在用了。具體極驗(yàn)官網(wǎng)可查詢,一張圖感受下:

優(yōu)勢(shì)往往就是這樣建立并擴(kuò)大的:當(dāng)你和一個(gè)行業(yè)的Top 3合作愉快的時(shí)候

遺憾的是,極驗(yàn)并沒(méi)有給筆者任何軟文費(fèi)。鑒于此便不再繼續(xù)吹了。下面還是從產(chǎn)品和技術(shù)層面聊一聊。

產(chǎn)品背景

極驗(yàn)?zāi)壳暗漠a(chǎn)品已經(jīng)升級(jí)為3.0:


其實(shí)這三代的產(chǎn)品,目前是共存的勢(shì)態(tài):

存在即合理

1.0

1.0代表的是鍵盤(pán)輸入型字符驗(yàn)證碼,開(kāi)發(fā)成本低,常用開(kāi)源框架即可搞定(如Kaptcha 、JCaptcha)。其設(shè)計(jì)思路就一點(diǎn):
如何讓生成的問(wèn)題人可以解答,而機(jī)器不可以


因此傳統(tǒng)驗(yàn)證碼勢(shì)必要在讓機(jī)器感到壓力山大的事情上做文章。但純圖片上做的文章,總是可以一物降一物:
左側(cè)是驗(yàn)證碼常用技術(shù),右側(cè)是對(duì)應(yīng)的破解方法

當(dāng)網(wǎng)站本身被頻繁突破意義不大時(shí),自然不用考慮這些對(duì)抗上的“短兵相接”。但就此類驗(yàn)證碼而言,最明顯的問(wèn)題是:

  • 每次都要敲鍵盤(pán)輸入
  • 為了防范圖像識(shí)別破解,便增大識(shí)別難度,導(dǎo)致有時(shí)候人都難以識(shí)別

更有甚者直接利用“人類(大學(xué)生或許更準(zhǔn)確)目前在認(rèn)知學(xué)上對(duì)機(jī)器的優(yōu)勢(shì)“,在圖片上直接祭出了高數(shù),將反人類做到極致:


當(dāng)然,上圖可能只是一個(gè)段子。總之極驗(yàn)官網(wǎng)已經(jīng)號(hào)稱對(duì)此沒(méi)有興趣:字符驗(yàn)證碼不在服務(wù)范圍。而這也是大勢(shì)所趨:驗(yàn)證碼本身沒(méi)有任何商業(yè)價(jià)值,為了阻擋機(jī)器會(huì)自帶反人類屬性。因此設(shè)計(jì)時(shí)一定要同時(shí)兼顧安全和用戶體驗(yàn)。對(duì)于一些電商類的網(wǎng)站,用戶體驗(yàn)甚至是擺在首位的。

2.0

2.0產(chǎn)品,極驗(yàn)稱其為”行為式驗(yàn)證“。代表作是滑動(dòng)拼圖驗(yàn)證碼。目前也是各大驗(yàn)證碼平臺(tái)的標(biāo)配:


為什么官網(wǎng)2.0的配圖不是點(diǎn)選驗(yàn)證碼?點(diǎn)選同樣是標(biāo)配,極驗(yàn)也有這款產(chǎn)品:

筆者認(rèn)為,主要原因是:
1)體驗(yàn)不如滑動(dòng)——點(diǎn)擊雖然比輸入更便捷,但識(shí)別文字依然要花大量時(shí)間,尤其是當(dāng)備選漢字遠(yuǎn)多于待驗(yàn)證漢字。
2)安全性不輸于滑動(dòng)(如果設(shè)計(jì)足夠好),但其原理并非潮流所向——想要破解,首先要能摳出圖片中的漢字,保證所有的漢字都被識(shí)別,且能夠順序和待驗(yàn)證漢字對(duì)上。這里窮舉法(隨便點(diǎn)三個(gè))是沒(méi)有用的,或者說(shuō)成功概率很?。?/P(n,m))。因?yàn)橹灰e(cuò)一個(gè)字就會(huì)刷新。可以看出,其校驗(yàn)原理更側(cè)重前端展示,并不過(guò)分依賴行為分析。既讓用戶可見(jiàn),又要在可見(jiàn)基礎(chǔ)上安全,就勢(shì)必帶來(lái)體驗(yàn)的下降,這還是和字符驗(yàn)證碼有殊途同歸的意思。
所以2.0強(qiáng)調(diào)行為,也是基于思路的轉(zhuǎn)換:弱化前端展示,強(qiáng)化后臺(tái)分析,在不可見(jiàn)之處強(qiáng)化安全。
對(duì)滑動(dòng)拼圖而言,滑到正確位置只是一個(gè)必要條件,用戶行為特征的提取才是核心。通過(guò)采集并分析用戶使用鼠標(biāo)拖動(dòng)滑塊的行為特征(速度、頻率、耗時(shí)等)來(lái)判定是人還是機(jī)器。對(duì)用戶來(lái)說(shuō),體驗(yàn)上比輸入或點(diǎn)選字符要更輕松愉快,還兼具一定趣味性。但是便捷性往往和安全性相沖突:理論上只要機(jī)器模擬出人的行為,就可以繞過(guò)驗(yàn)證(有人可能會(huì)第一時(shí)間聯(lián)想到按鍵精靈)。其實(shí)是否輕松,就看對(duì)行為判定是否精準(zhǔn)了。這類判定,可以基于一些固定的規(guī)則(如滑動(dòng)時(shí)間小于某個(gè)閾值),也可以引入機(jī)器學(xué)習(xí)分析特征量(如Kmeans做聚類分析),看各家的思路和實(shí)力了。未來(lái)這些偏后端的技術(shù)才是賣(mài)點(diǎn),也不會(huì)輕易開(kāi)源。因此此類驗(yàn)證碼更多還是各類驗(yàn)證碼平臺(tái)提供,接入是有償?shù)摹?br> 知乎上有一位叫@darbra的兄弟,對(duì)破解極驗(yàn)2.0產(chǎn)品饒有興趣。他總結(jié)出了selenium大法 和 requests基本法,號(hào)稱破率分別是98%和80%。其Github上的代碼7.4還有更新。有興趣的讀者可以嘗試。

3.0

3.0時(shí)代則伴隨著人工智能的浪潮全面進(jìn)化,強(qiáng)調(diào)的則是進(jìn)一步弱化前端+強(qiáng)化機(jī)器學(xué)習(xí)。前端越簡(jiǎn)單越好,一個(gè)按鈕,一個(gè)滑塊,一個(gè)復(fù)選框即可,甚至...nothing。個(gè)人認(rèn)為,目前只有三家公司在這方面引領(lǐng)市場(chǎng):Google,極驗(yàn)和阿里。

1)Google第一篇已經(jīng)畫(huà)了大量篇幅介紹,已經(jīng)進(jìn)化到“化無(wú)形為有形”的invisible reCAPTCHA,驗(yàn)證碼實(shí)體完全透明,是目前來(lái)說(shuō)產(chǎn)品理念上最領(lǐng)先的(技術(shù)上就不談了)。
2)阿里和極驗(yàn)是第二檔,區(qū)別在于一個(gè)是滑塊,一個(gè)是按鈕。相當(dāng)于Google的noCAPTCHA產(chǎn)品。



極驗(yàn)的3.0產(chǎn)品思想,其實(shí)是官網(wǎng)提到的“驗(yàn)證碼形如按鈕,更像按鈕一樣百搭”——也就是說(shuō)這個(gè)按鈕會(huì)一直存在,只不過(guò)普通合法用戶,需要多點(diǎn)一下它來(lái)進(jìn)行人機(jī)識(shí)別罷了。只有非法用戶時(shí)才會(huì)出現(xiàn)滑動(dòng)拼圖或圖文點(diǎn)選驗(yàn)證。
如果極驗(yàn)有4.0,筆者大膽推測(cè)方向是invisible。
技術(shù)層面則強(qiáng)調(diào)使用了人工智能——其實(shí)各公司對(duì)應(yīng)的2.0產(chǎn)品,應(yīng)該都用到了。按照極驗(yàn)官方的說(shuō)法:
惡意程序模仿人類行為軌跡對(duì)驗(yàn)證碼進(jìn)行破解針對(duì)模擬,極驗(yàn)擁有超過(guò)4000萬(wàn)人機(jī)行為樣本的海量數(shù)據(jù)利用機(jī)器學(xué)習(xí)和神經(jīng)網(wǎng)絡(luò)構(gòu)建線上線下的多重靜態(tài)、動(dòng)態(tài)防御模型識(shí)別模擬軌跡,界定人機(jī)邊界
極驗(yàn)很可能是加強(qiáng)了人工智能的投入:例如新增更多特征量,優(yōu)化識(shí)別算法等。畢竟機(jī)器學(xué)習(xí)還是一個(gè)數(shù)據(jù)為王的技術(shù),沒(méi)有足夠多的訓(xùn)練樣本是無(wú)法做到足夠精準(zhǔn)的。所以以極驗(yàn)?zāi)壳暗氖袌?chǎng)份額,是有這個(gè)實(shí)力優(yōu)化的。但是加強(qiáng)了多少,效果如何,目前并沒(méi)有太好的印證方式,畢竟今年4月才上線??春笃谑袌?chǎng)的反饋如何吧。

接入方式

同Google一樣,極驗(yàn)的接入方式也如其UI一樣:清爽簡(jiǎn)潔
按官網(wǎng)的說(shuō)法:

  1. 引入初始化函數(shù)
<script src="gt.js"></script>
  1. 調(diào)用初始化函數(shù)進(jìn)行初始化
initGeetest({
    // 以下配置參數(shù)來(lái)自服務(wù)端 SDK
    gt: data.gt,
    challenge: data.challenge,
    offline: !data.success,
    new_captcha: data.new_captcha
}, function (captchaObj) {
    // 這里可以調(diào)用驗(yàn)證實(shí)例 captchaObj 的實(shí)例方法
})

結(jié)合配置參數(shù):


看似非常多,其實(shí)只有前面四個(gè)是必須,后面都是定制,按默認(rèn)來(lái)均可不填。這樣的參數(shù)配置,作為一家專業(yè)驗(yàn)證碼公司,還是十分合理的——既要考慮到宕機(jī)問(wèn)題,又得支持顧客的個(gè)性化需求。
參數(shù)含義,文檔已經(jīng)介紹的十分詳細(xì),Markdown排版閱讀起來(lái)也很舒適,筆者便不再贅言。值得一提的是,第二個(gè)參數(shù)challenge,在前面介紹的Google驗(yàn)證碼參數(shù)也出現(xiàn)過(guò),且含義基本一樣??赡芤话虢梃b,一半致敬吧。

源碼分析

細(xì)心的朋友會(huì)發(fā)現(xiàn),步驟1中加載的是一個(gè)本地路徑的gt.js,而非從極驗(yàn)服務(wù)器加載。按一般驗(yàn)證碼公司的做法,給出一個(gè)http://static.geetest.com/static/tools/gt.js 就夠了。極驗(yàn)則考慮到了failover層面——如果自己服務(wù)器故障怎么辦?在目前機(jī)房容災(zāi)能力越來(lái)越強(qiáng)的今天,這么考慮仍然是一種非常專業(yè)和負(fù)責(zé)的做法。online offline皆可進(jìn)行驗(yàn)證,這恰恰也是極驗(yàn)有別于其他驗(yàn)證碼的特點(diǎn)之一。

下面還是回到gt.js——最近喜歡看源碼。雖然筆者前端經(jīng)驗(yàn)并不豐富,但是覺(jué)得有趣的東西,還是忍不住分享:

gt.js的主要功能是定義初始化驗(yàn)證碼的函數(shù)initGeetest。

最開(kāi)始的地方如下:

"v0.4.6 Geetest Inc.";

(function (window) {
    "use strict";
    if (typeof window === 'undefined') {
        throw new Error('Geetest requires browser environment');
    }

……
})(window);

"v0.4.6 Geetest Inc."行首直接字符串秀出版本號(hào)和公司,一目了然。
這種重點(diǎn)要說(shuō)的是"use strict"

嚴(yán)以律己,寬以待人

"use strict",嚴(yán)格模式,是Javascript非常好的一個(gè)特性,可能會(huì)大大節(jié)約你查錯(cuò)的時(shí)間。
先舉個(gè)例子:

var zombie = {
    eyeLeft : 0,
    eyeRight: 1,
    // ... a lot of keys ...
    eyeLeft : 1
}
mingo= 5;
  1. eyeLeft出現(xiàn)了兩次,你打算用哪個(gè)?
  2. mingo會(huì)是一個(gè)全局變量,如果被用于各種嵌套循環(huán)中會(huì)怎樣?

這段代碼嚴(yán)格模式下會(huì)拋出兩個(gè)錯(cuò)誤,而非嚴(yán)格模式不會(huì)報(bào)。
它以嚴(yán)格換來(lái)了如下好處,主要是以下兩點(diǎn):

  • 消除語(yǔ)法的一些不嚴(yán)謹(jǐn)之處,減少怪異行為
  • 消除代碼運(yùn)行的一些不安全之處
    最初網(wǎng)景在開(kāi)發(fā)JavaScript可能存在一些考慮不周的情況,一些模棱兩可的特性被人詬病,新手可能分不清是語(yǔ)法糖還是語(yǔ)法坑。
    user stick便是語(yǔ)言成熟化的一個(gè)方向。畢竟JavaScript將來(lái)會(huì)是一種全棧語(yǔ)言,尤其是在服務(wù)端Node.js逐漸強(qiáng)勢(shì)的今天。

好像扯遠(yuǎn)了。其實(shí)筆者想說(shuō)的是極驗(yàn)代碼寫(xiě)的非常嚴(yán)謹(jǐn)。有一個(gè)細(xì)節(jié):

var loadScript = function (url, cb) {
    var script = document.createElement("script");
    script.charset = "UTF-8";
    script.async = true;

    script.onerror = function () {
        cb(true);
    };
    var loaded = false;
    script.onload = script.onreadystatechange = function () {
        if (!loaded &&
            (!script.readyState ||
            "loaded" === script.readyState ||
            "complete" === script.readyState)) {

            loaded = true;
            setTimeout(function () {
                cb(false);
            }, 0);
        }
    };
    script.src = url;
    head.appendChild(script);
};

不知該寫(xiě)法應(yīng)該是參考了JQuery如下源碼:

callback = errorCallback = xhr.onload =
                                xhr.onerror = xhr.onabort = xhr.onreadystatechange = null

這么寫(xiě)主要還是因?yàn)椋?br> IE的 script 元素支持onreadystatechange事件,不支持onload事件。FF則正好相反
如果要在一個(gè)<script src="x.js"> 加載完成執(zhí)行一個(gè)操作,F(xiàn)F使用onload事件就行了,IE下則要結(jié)合onreadystatechange事件和this.readyState。所以:

script.onload = script.onreadystatechange = function(){

     if(  ! this.readyState     //這是FF的判斷語(yǔ)句,因?yàn)閒f下沒(méi)有readyState,IE的readyState肯定有值
          || this.readyState=='loaded' || this.readyState=='complete'   //這是IE的判斷語(yǔ)句
    ){
          alert('loaded');
    }

};

此外還有一個(gè)細(xì)節(jié):
gt.js有一個(gè)盡量利用多CDN,使靜態(tài)文件盡可能加載成功的機(jī)制:

fallback_config: {
        slide: {
            static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"],
            type: 'slide',
            slide: '/static/js/geetest.0.0.0.js'
        },
        fullpage: {
            static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"],
            type: 'fullpage',
            fullpage: '/static/js/fullpage.0.0.0.js'
        }
    }

其目的是加載滑動(dòng)和點(diǎn)選的widget。在fullpage.js(已混淆)開(kāi)頭可見(jiàn)以下寫(xiě)法:

!function (a, b) {
  'use strict';
  'object' == typeof module && 'object' == typeof module.exports ? module.exports = a.document ? b(a, !0)  : function (a) {
    if (!a.document) throw new Error('Geetest requires a window with a document');
    return b(a)
  }
   : b(a)
}('undefined' != typeof window ? window : this, function (a, b) {
……
}

今天天氣不錯(cuò),挺風(fēng)和日麗的

這一段讓筆者想起中學(xué)時(shí)寫(xiě)作的范文——JQuery就是這么玩的。JQuery向來(lái)以嚴(yán)謹(jǐn)和兼容性強(qiáng)大著稱,究其根本是寫(xiě)法的包容性。大家可以對(duì)照看一下其的源碼開(kāi)頭,以最新版本為例:

/*!
 * jQuery JavaScript Library v3.2.1
 * https://jquery.com/
 *
 * Includes Sizzle.js
 * https://sizzlejs.com/
 *
 * Copyright JS Foundation and other contributors
 * Released under the MIT license
 * https://jquery.org/license
 *
 * Date: 2017-03-20T18:59Z
 */
( function( global, factory ) {

    "use strict";

    if ( typeof module === "object" && typeof module.exports === "object" ) {

        // For CommonJS and CommonJS-like environments where a proper `window`
        // is present, execute the factory and get jQuery.
        // For environments that do not have a `window` with a `document`
        // (such as Node.js), expose a factory as module.exports.
        // This accentuates the need for the creation of a real `window`.
        // e.g. var jQuery = require("jquery")(window);
        // See ticket #14549 for more info.
        module.exports = global.document ?
            factory( global, true ) :
            function( w ) {
                if ( !w.document ) {
                    throw new Error( "jQuery requires a window with a document" );
                }
                return factory( w );
            };
    } else {
        factory( global );
    }

// Pass this if window is not defined yet
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {

// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
// enough that all such attempts are guarded in a try block.
"use strict";

這里其實(shí)是一個(gè)閉包(Closures)。現(xiàn)今流行的一些 JS 庫(kù)中經(jīng)常見(jiàn)到以下形式的代碼。在閉包中,可以定義私有變量和函數(shù),外部無(wú)法訪問(wèn)它們,從而做到了私有成員的隱藏和隔離。而通過(guò)返回對(duì)象或函數(shù),或是將某對(duì)象作為參數(shù)傳入,在函數(shù)體內(nèi)對(duì)該對(duì)象進(jìn)行操作,就可以公開(kāi)希望對(duì)外暴露的方法與數(shù)據(jù)。

JQuery這一段的目的,注釋已經(jīng)寫(xiě)得非常清楚。其實(shí)就是為了保證不污染全局變量,將所有的對(duì)象及方法創(chuàng)建都放到了factory函數(shù)中執(zhí)行。通過(guò)形參global來(lái)傳遞window變量,在利用factory創(chuàng)建jQuery對(duì)象以前,首先進(jìn)行window變量的檢測(cè)。
module 和 module.exports主要是為了讓jQuery能夠以模塊的形式(如CMD)注入到?jīng)]有window.document變量的諸如Node.js的運(yùn)行環(huán)境中,當(dāng)遇到這種情況,就不會(huì)在window中設(shè)置jQuery$變量。要使用jQuery時(shí),則是使用所返回的jQuery對(duì)象,如在Node.js中:
var jQuery = require("jquery")(window);
所以極限以"范文"開(kāi)頭,也是考慮了同樣的事情。

最后還是聊一聊UBC的部分(寫(xiě)累了,突然想裝13)——筆者曾在阿里驗(yàn)證碼里看到了一個(gè)類似的文件,所以對(duì)這個(gè)縮寫(xiě)印象深刻——其實(shí)就是User Behavior Collection,給到后端做人機(jī)識(shí)別用。極驗(yàn)的UBC在http://apiguard.geetest.com/dist/geeguard.js 這里。其實(shí)做的事情就是用戶行為收集。
老套路,先搜一波userAgent——為什么?因?yàn)樽鲞@件事情必須要瀏覽器指紋,否則無(wú)法鎖定設(shè)備,也就缺少了一個(gè)關(guān)鍵風(fēng)控維度。
不出意料可以看到這樣一段代碼:

 '_getKeys': function () {
      return ['textLength',
      'HTMLLength',
      'documentMode'].concat(this._tagKeys).concat(['screenLeft',
      'screenTop',
      'screenAvailLeft',
      'screenAvailTop',
      'innerWidth',
      'innerHeight',
      'outerWidth',
      'outerHeight',
      'browserLanguage',
      'browserLanguages',
      'systemLanguage',
      'devicePixelRatio',
      'colorDepth',
      'userAgent',
      'cookieEnabled',
      'netEnabled',
      'screenWidth',
      'screenHeight',
      'screenAvailWidth',
      'screenAvailHeight',
      'localStorageEnabled',
      'sessionStorageEnabled',
      'indexedDBEnabled',
      'CPUClass',
      'platform',
      'doNotTrack',
      'timezone',
      'canvas2DFP',
      'canvas3DFP',
      'plugins',
      'maxTouchPoints',
      'flashEnabled',
      'javaEnabled',
      'hardwareConcurrency',
      'jsFonts',
      'timestamp',
      'performanceTiming'])
    },

其實(shí)這段代碼就是基于多維度給瀏覽器采集一個(gè)指紋。Github上有一款開(kāi)源Js叫
fingerprintjs,目前已經(jīng)出了第二版,定位就是Modern & flexible browser fingerprinting library,功能是一樣的,如下:

    get: function(done){
      var keys = [];
      keys = this.userAgentKey(keys);
      keys = this.languageKey(keys);
      keys = this.colorDepthKey(keys);
      keys = this.pixelRatioKey(keys);
      keys = this.hardwareConcurrencyKey(keys);
      keys = this.screenResolutionKey(keys);
      keys = this.availableScreenResolutionKey(keys);
      keys = this.timezoneOffsetKey(keys);
      keys = this.sessionStorageKey(keys);
      keys = this.localStorageKey(keys);
      keys = this.indexedDbKey(keys);
      keys = this.addBehaviorKey(keys);
      keys = this.openDatabaseKey(keys);
      keys = this.cpuClassKey(keys);
      keys = this.platformKey(keys);
      keys = this.doNotTrackKey(keys);
      keys = this.pluginsKey(keys);
      keys = this.canvasKey(keys);
      keys = this.webglKey(keys);
      keys = this.adBlockKey(keys);
      keys = this.hasLiedLanguagesKey(keys);
      keys = this.hasLiedResolutionKey(keys);
      keys = this.hasLiedOsKey(keys);
      keys = this.hasLiedBrowserKey(keys);
      keys = this.touchSupportKey(keys);
      keys = this.customEntropyFunction(keys);
      var that = this;
      this.fontsKey(keys, function(newKeys){
        var values = [];
        that.each(newKeys, function(pair) {
          var value = pair.value;
          if (typeof pair.value.join !== "undefined") {
            value = pair.value.join(";");
          }
          values.push(value);
        });
        var murmur = that.x64hash128(values.join("~~~"), 31);
        return done(murmur, newKeys);
      });
}

有興趣的讀者可以試著了解。

總結(jié)

個(gè)人觀點(diǎn):極驗(yàn)是國(guó)內(nèi)目前最好的驗(yàn)證碼產(chǎn)品——不論是產(chǎn)品設(shè)計(jì)、文檔風(fēng)格、還是代碼專業(yè)性。這么說(shuō)是因?yàn)?,其他平臺(tái),鮮有這幾方面都不錯(cuò)的。例如阿里最大的問(wèn)題就是接入配置較為復(fù)雜(接入頁(yè)面插入多段代碼);網(wǎng)易則是點(diǎn)選驗(yàn)證碼圖文結(jié)合過(guò)于丑陋,無(wú)視審美;點(diǎn)觸文檔排版不佳...諸如此類。
另外一個(gè)好感的原因,可能是筆者坐標(biāo)武漢,明白在武漢做互聯(lián)網(wǎng)之不易。不管是政府扶持力度,還是人才凝聚力,都和一線城市有差距。

希望極驗(yàn)越做越好。

后續(xù)不打算再介紹其他平臺(tái)的驗(yàn)證碼。一來(lái)都大同小異,其他平臺(tái)亮點(diǎn)不多,反復(fù)論述意義不大;二來(lái)自身疲乏,想轉(zhuǎn)移一下興趣點(diǎn)。下一篇可能會(huì)介紹機(jī)器學(xué)習(xí)在人機(jī)識(shí)別中的簡(jiǎn)單應(yīng)用。鄙人新手,如有翻車(chē),老司機(jī)多多包涵扶正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容