使用聚合數據API查詢快遞數據-短信驗證碼-企業核名

有位朋友讓我給他新開的網站幫忙作幾個小功能,以下:javascript

  1. 輸入快遞公司、快遞單號,查詢出這個快件的全部動態(從哪裏出發,到了哪裏)
  2. 在註冊、登陸等場景下的手機驗證碼(要求有必定的防刷策略)
  3. 經過輸入公司名的關鍵詞,查看這個公司是否已經註冊、法人信息、有相似名稱的公司等等

而且能夠用的接口、文檔都提供給我了。
其中需求 一、2,都經過 聚合數據 這家網站提供的接口實現;需求 3 經過 雲聚數據 來實現。php

本項目的文件

由於朋友的網站是用 ThinkPHP 寫的,爲了保持未來代碼的兼容,這三個功能也用 ThinkPHP 寫成。html

項目的全部文件都放在了 GitHub 上,部分敏感數據已經隱藏,你須要自行替換,地址以下:前端

GitHub 地址:使用聚合數據API查詢快遞數據-短信驗證碼-企業核名java

由於這三個功能並非正式產品,未來會須要嵌入到網站的各個功能模塊中去,因此爲了查看起來方便,三個功能的代碼都寫在一個文件裏,你只要重點關注如下幾個文件就好:mysql

  • /Home/Conf/config.php 參數配置文件
  • /Home/Controller/IndexController.class.php 後端代碼、API的請求與處理
  • /Home/View/Index_index.html 前端 html

申請 KEY 和閱讀開發文檔

分別到上面兩家網站上找到「快遞」、「短信」、「核名」的文檔地址,根據裏面的說明,把 KEY、URL 等信息放入配置文件Home/Conf/config.php,方便後面重複使用。jquery

經常使用快遞API文檔git

短信API文檔github

核名-文檔ajax

注意 短信的 API 服務,要先到網站的後臺添加「短信模板」,讓管理員審覈後才能夠正常調用

最後,Home/Conf/config.php 配置文件裏的內容以下

return array(
    // 快遞查詢
    'EXPRESS_APP_KEY' => '你的快遞 APPKEY',
    'EXPRESS_QUERY_URL' => 'http://v.juhe.cn/exp/index', //快遞單號查詢
    'EXPRESS_COM_URL' => 'http://v.juhe.cn/exp/com', //快遞公司查詢
    // 發短信
    'SEND_SMS_KEY' => '你的短信接口 APPKEY',
    'SEND_SMS_URL' => 'http://v.juhe.cn/sms/send',
    // 核名
    'COMPANY_KEY' => '核名 APPKEY',
    'COMPANY_URL' => 'http://eci.yjapi.com/ECIFast/Search',
    //數據庫配置信息
    'DB_TYPE'   => 'mysql', // 數據庫類型
    'DB_HOST'   => 'localhost', // 服務器地址
    'DB_NAME'   => '你的數據庫名', // 數據庫名
    'DB_USER'   => '你的數據庫用戶名', // 用戶名
    'DB_PWD'    => '密碼', // 密碼
    'DB_PORT'   => 3306, // 端口
    'DB_PREFIX' => 'pre_', // 數據庫表前綴 
    'DB_CHARSET'=> 'utf8', // 字符集
);

設置數據庫

爲了防止惡意用戶利用暴露在外的短信接口搗亂,須要對每一個手機號碼的行爲進行記錄。例如:

  • 向某個手機號碼發送短信驗證碼後, 60 秒內不能再次發送
  • 必須在 1 小時內填寫短信驗證碼,不然會過時
  • 避免 ajax 請求地址被機器人程序利用,在表單裏要使用圖片驗證碼才能提交數據

關於「表單驗證碼」咱們後面代碼會說明,這裏先建立表結構以下,用於記錄短信發送記錄:

CREATE TABLE `pre_smsrecord` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `mobile` varchar(11) NOT NULL DEFAULT '',
  `tpl_id` int(11) NOT NULL,
  `code` int(6) NOT NULL,
  `time` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=M
  • mobile 是手機號碼
  • tpl_id是在網站後臺添加並經過審覈的短信模板
  • code是發送的驗證碼(通常是4位或6位)
  • time是發送時間戳

直接下載sql進行還原:在本項目的 GitHub 地址上也能夠直接從 /Pubic 目錄找到 sql 文件,你能夠直接把它還原你的 MySQL 上。

表單驗證碼的實現

最終效果以下:

驗證碼

由於三個功能都須要表單驗證碼,因此首先實現它。

打開 /Home/View/Index_index.html,注意裏面圖片驗證碼 img 標籤,以及對應的 javascript

<p>
    (通用)輸入驗證碼 <input type="text" name="verify" id="verify">
    <img id="verify-img" src="/?m=Home&c=Index&a=verify" alt="">
    <a id="btn-refresh-verify" href="javascript:;" title="">刷新</a>
</p>
<script type="text/javascript" charset="utf-8">
jQuery(document).ready(function($) {
    // 刷新驗證碼按鈕
    $("#btn-refresh-verify").click(function(event) {
    
        $("#verify-img").attr('src', '/?m=Home&c=Index&a=verify' + "&time=" + new Date().getTime());
    });
});
</script>

對應的後端代碼在 /Home/Controller/IndexController.class.php 中

/**
* +--------------------------------------------------------------------------
* 生成驗證碼
*
* +--------------------------------------------------------------------------
*/
public function verify(){
    $Verify = new \Think\Verify();
    $Verify->entry();
}
/**
* +--------------------------------------------------------------------------
* 檢查驗證碼
*
* @param string $code 輸入的驗證碼
* @return boolean
* +--------------------------------------------------------------------------
*/
protected function check_verify($code){
    $verify = new \Think\Verify();
    return $verify->check($code);
}

通用的 Curl 方法,用來向第三方網站的 API 發起請求並獲取返回值

由於 3 個功能實際上都是經過網絡來請求一個第三方網站的 API 接口地址,所以能夠統一成一個通用的方法。代碼以下,能夠傳入三個變量,分別爲 :url、參數數組、請求方式(是不是post,默認爲0),返回一個 json 格式的數據。

/**
* +--------------------------------------------------------------------------
* 通用的「聚合數據」請求接口,返回JSON數據
*
* @param string $url 接口地址
* @param array $params 傳遞的參數
* @param int $ispost 是否以POST提交,默認GET
* @return json
* +--------------------------------------------------------------------------
*/
public function juhecurl($url,$params=false,$ispost=0){
    $httpInfo = array();
    $ch = curl_init();
    curl_setopt( $ch, CURLOPT_HTTP_VERSION , CURL_HTTP_VERSION_1_1 );
    curl_setopt( $ch, CURLOPT_USERAGENT , 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22' );
    curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT , 30 );
    curl_setopt( $ch, CURLOPT_TIMEOUT , 30);
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER , true );
    if( $ispost )
    {
        curl_setopt( $ch , CURLOPT_POST , true );
        curl_setopt( $ch , CURLOPT_POSTFIELDS , $params );
        curl_setopt( $ch , CURLOPT_URL , $url );
    }
    else
    {
        if($params){
            curl_setopt( $ch , CURLOPT_URL , $url.'?'.$params );
        }else{
            curl_setopt( $ch , CURLOPT_URL , $url);
        }
    }
    $response = curl_exec( $ch );
    if ($response === FALSE) {
          //echo "cURL Error: " . curl_error($ch);
        return false;
    }
    $httpCode = curl_getinfo( $ch , CURLINFO_HTTP_CODE );
    $httpInfo = array_merge( $httpInfo , curl_getinfo( $ch ) );
    curl_close( $ch );
    return $response;
}

後面咱們獲取快遞數據、發送短信、查詢企業名稱,均可以調用這個通用的方法。

獲取快遞數據列表的實現

最終效果以下:

快遞查詢

打開 /Home/View/Index_index.html

<h1>獲取快遞數據</h1>
<div id="express-module">
    <p>
        選擇公司
        <select name="express-company" id="express-company">
            <option value="sf">順豐</option>
            <option value="sto">申通</option>
            <option value="yt">圓通</option>
            <option value="yd">韻達</option>
            <option value="tt">每天</option>
            <option value="ems">EMS</option>
            <option value="zto">中通</option>
            <option value="ht">匯通</option>
        </select>
    </p>
    <p> 
        輸入單號 <input type="text" name="express-number" id="express-number"> 
    </p>
    <p>
        <button id="btn-query-express" type="button" class="btn btn-default">查詢快遞</button> 
    </p>
    
    <p>返回結果:</p>
    <p id="reason" style="color:#FF0000"></p>
    
    <p>快件動態:</p>
    <ul id="express-result">
        
    </ul>
</div>    
<!-- 引入jquery庫 -->
<script src="__PUBLIC__/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
jQuery(document).ready(function($) {
    //點擊快遞查詢按鈕
    $("#btn-query-express").click(function(event) {
        $("#reason").html("");
        // 更新驗證碼
        $("#verify-img").attr('src', '/?m=Home&c=Index&a=verify' + "&time=" + new Date().getTime());
        $.getJSON(
            '/?m=Home&c=Index&a=getExpressData', 
            {
                company: $("#express-company").val(),
                number: $("#express-number").val(),
                verify: $("#verify").val(),
            }, 
            function(json, textStatus) {
                if(json['resultcode'] == 200){
                    var result_list = json['result']['list'];
                    $("#express-result").html("");
                    for(var i = 0, l = result_list.length; i < l; i++) {
                        $("#express-result").append("<li>" + result_list[i]['datetime'] + "," + result_list[i]['remark'] + "," +result_list[i]['zone'] + "</li>");
                    }
                }
                $("#reason").html(json['reason']);
        });
    });
});
</script>

對應的後端代碼爲以下,特別注意,你要把 $content = $this->juhecurl(C("EXPRESS_QUERY_URL"), $params, 1); 這段的註釋去掉(由於我開發的時候查詢餘額不足,因此使用了一個寫死的數組來讓程序能正常運行)

/** 
* +--------------------------------------------------------------------------
* 獲取快遞數據
*
* @param string $get.company 快遞公司代碼
* @param string $get.number 快遞單號
* @return json
* +--------------------------------------------------------------------------
*/
public function getExpressData(){
    // 傳入 get 參數,包括公司代號、快遞單號、驗證碼
    $com    = I("get.company");
    $no     = I("get.number");
    $verify = I("get.verify");
    // 處理驗證碼
    if ( !$this->check_verify($verify) ) {
        $content = array('resultcode'=>1000, 'reason'=>'驗證碼填寫錯誤');
        echo json_encode($content);
        exit();
    }
    // 處理機器人程序刷接口(目前經過IP判斷)
    $ip = get_client_ip(0, true);
    $Record = M("expressrecord");
    $express_record = $Record->where("ip='" . $ip . "'")->find();
    if( $express_record && ( (time() - $express_record['time']) < 60 ) ){
        echo json_encode(array('reason'=>'60秒內只能查詢一次'));
        exit();
    }
    if ( $com && $no ) {
        $params = array(
            'key' => C("EXPRESS_APP_KEY"),
            'com'  => $com,
            'no' => $no
        );
        // 開發測試階段直接返回值,不請求 API
        // $content = $this->juhecurl(C("EXPRESS_QUERY_URL"), $params, 1);
        $content = array('resultcode'=>200, 'reason'=>'查詢成功', 'result'=>array('list'=>array()));
        // 刪除舊記錄(若是有),而後添加新的記錄
        $Record->where("ip='" . $ip . "'")->delete();
        $data = array(
            'ip' => $ip,
            'time'=>time()
        );
        $Record->add($data);
        //$returnArray = json_decode($content,true);
        echo json_encode($content, true);
    }
}

短信驗證碼的發送和檢驗

短信驗證碼

廢話很少說,前端html直接上代碼。這裏實際上有兩個動做,一個是「給我手機號碼 xxxxx 發個驗證碼」,另外一個是「我已經收到了,並填寫了,請看我填寫的驗證碼對不對」。

<h1>發送短信驗證碼與檢查</h1>
<div id="sms-module">
    <p>
        短信模板:
        <select name="send-sms-tplid" id="send-sms-tplid">
            <option value="5596">申請註冊</option>
            <option value="5602">申請找回密碼</option>
            <option value="5603">在線核名</option>
        </select>
    </p>
    
    <p>
        手機號碼:<input type="text" name="send-sms-mobile" id="send-sms-mobile">
    </p>
    
    <p>
        輸入手機驗證碼:<input type="text" name="send-sms-code" id="send-sms-code">
        <span id="sms-count-down"></span>
        <button id="btn-send-sms" type="button" class="btn btn-default">發送驗證碼</button>
    </p>
    
    <p>
        <button id="btn-check-sms" type="button" class="btn btn-default">提交手機驗證碼</button>
    </p>
</div>
<script type="text/javascript" charset="utf-8">
/**
* 發送短信驗證碼後,60秒倒計時
*/
var seconds_left = 60;
function sms_count_down(){
    if(seconds_left > 0){
        seconds_left = seconds_left-1;
        //var b=new Date();
        
        document.getElementById("sms-count-down").innerHTML = seconds_left + "";
        setTimeout("sms_count_down()",1000);
    } else {
        //根據 http://stackoverflow.com/questions/7526601/setattributedisabled-false-changes-editable-attribute-to-false
        // 不能爲 setAttribute 設置任何值,都會變成 」disabled「,要使用 removeAttribute
        document.getElementById("btn-send-sms").removeAttribute("disabled");
        document.getElementById("btn-send-sms").innerHTML = "從新發送";
        document.getElementById("sms-count-down").innerHTML = "";
    }
}
jQuery(document).ready(function($) {
//發送短信
    $("#btn-send-sms").click(function(event) {
        $("#reason").html("");
        $.getJSON(
            '/?m=Home&c=Index&a=sendSMS', 
            {
                tplid: $("#send-sms-tplid").val(),
                mobile: $("#send-sms-mobile").val(),
            }, 
            function(json, textStatus) {
                $("#reason").html(json['reason']);
                var error_code = json['error_code'];
                if(error_code == 0){
                    // 測試階段,不由用,可檢查 60 秒重複發送
                    //$("#btn-send-sms").attr("disabled", true);
                    //開始倒計時
                    sms_count_down();
                }else{
                    $("#btn-send-sms").html("從新發送");
                }
    
        });
    });
    //檢查短信驗證碼
    $("#btn-check-sms").click(function(event) {
        $("#reason").html("");
        // 更新驗證碼
        $("#verify-img").attr('src', '/?m=Home&c=Index&a=verify' + "&time=" + new Date().getTime());
        $.getJSON(
            '/?m=Home&c=Index&a=checkSmsCode', 
            {
                tplid: $("#send-sms-tplid").val(),
                mobile: $("#send-sms-mobile").val(),
                code: $("#send-sms-code").val(),
                verify: $("#verify").val(),
            }, 
            function(json, textStatus) {
                $("#reason").html(json['reason']);
    
        });
    });
    
});

一樣由於有兩個動做,後端會稍微複雜一點點

/**
* +--------------------------------------------------------------------------
* 請求發送短信接口,60秒後才能從新發送
*
* @param int $get.tplid 短信模板id
* @param string $get.mobile 手機號碼
* @return json
* +--------------------------------------------------------------------------
*/
public function sendSMS(){
    $tpl_id = I("get.tplid"); // 短信模板id:註冊 5596 找回密碼 5602 在線核名 5603
    $mobile = I("get.mobile"); // 手機號碼
    // 檢查數據庫記錄 ,是否在 60 秒內已經發送過一次
    $Record = M("smsrecord");
    $where = array(
        'mobile' => $mobile,
        'tpl_id' => $tpl_id,
    );
    $sms_record = $Record->where($where)->find();
    if( $sms_record && ( (time() - $sms_record['time']) < 60 ) ){
        echo json_encode(array('reason'=>'60秒內不能屢次發送'));
        exit();
    }
    // 若是60秒內沒有發過,則發送驗證碼短信(6位隨機數字)
    $code = mt_rand(100000, 999999);
    $smsConf = array(
        'key'   => C("SEND_SMS_KEY"), //您申請的APPKEY
        'mobile'    => $mobile, //接受短信的用戶手機號碼
        'tpl_id'    => $tpl_id, //您申請的短信模板ID,根據實際狀況修改
        'tpl_value' =>'#code#=' . $code //您設置的模板變量,根據實際狀況修改 '#code#=1234&#company#=聚合數據'
    );
     
    //測試階段,不發短信,直接設置一個「發送成功」 json 字符串
    $content = $this->juhecurl(C("SEND_SMS_URL") ,$smsConf, 1); //請求發送短信
    //$content = json_encode(array('error_code'=>0, 'reason'=>'發送成功'));
    if($content){
        $result = json_decode($content,true);
        $error_code = $result['error_code'];
        if($error_code == 0){
            // 狀態爲0,說明短信發送成功
            // 數據庫存儲發送記錄,用於處理倒計時和輸入驗證,首先要刪除舊記錄
            $Record->where("mobile=" . $mobile)->delete();
                $data = array(
                    'mobile' => $mobile,
                    'tpl_id'=> $tpl_id,
                    'code'=>$code,
                    'time'=>time()
                );
                $Record->data($data)->add();
            //echo "短信發送成功,短信ID:".$result['result']['sid'];
        }else{
            //狀態非0,說明失敗
            //echo "短信發送失敗(".$error_code."):".$msg;
        }
    }else{
        //返回內容異常,如下可根據業務邏輯自行修改
        //$result['reason'] = '短信發送失敗';
    }
    echo $content;
}
/**
* +--------------------------------------------------------------------------
* 檢查填寫的手機驗證碼是否填寫正確
* 能夠添加更多字段改形成註冊、登陸等表單
*
* @param string $get.verify 驗證碼
* @param string $get.mobile 手機號碼
* @param int $get.tplid 短信模板ID
* @param int $get.code 手機接收到的驗證碼
* +--------------------------------------------------------------------------
*/
public function checkSmsCode(){
    $verify = I("get.verify");
    $tpl_id = I("get.tplid"); // 短信模板id:註冊 5596 找回密碼 5602 在線核名 5603
    $mobile = I("get.mobile"); // 手機號碼
    $code = I("get.code"); // 手機收到的驗證碼
    if(!$this->check_verify($verify)){
        $content = array('resultcode'=>1000, 'reason'=>'驗證碼填寫錯誤');
        echo json_encode($content);
        exit();
    }
    // 檢查數據庫記錄,輸入的手機驗證碼是否和以前經過短信 API 發送到手機的一致
    $Record = M("smsrecord");
    $where = array(
        'mobile' => $mobile,
        'tpl_id' => $tpl_id,
        'code' => $code,
    );
    $sms_record = $Record->where($where)->find();
    if($sms_record){
        echo json_encode(array('reason'=>'短信驗證碼覈對成功'));
        // 處理後面的程序(如繼續登陸、註冊等)
    }else{
        echo json_encode(array('reason'=>'短信驗證碼錯誤'));
    }
}

最後是企業核名

由於這個接口原本只提供了每次查詢一個關鍵詞,而朋友的網站要求能每次查詢用「,」分隔的多個關鍵詞,所以須要對提交的數據和返回到前端的進行一番處理(循環處理多個列表)。

企業核名

前端代碼比較簡單

<h1>企業核名</h1>
<div id="company-module">
    <p>
        省份:上海
        <input type="hidden" name="company-province" id="company-province" value="SH">
    </p>
    <p>
        <input type="text" name="company-name" id="company-name">
    </p>
</div>
<button id="btn-company" type="button" class="btn btn-default">企業核名</button>
<p>公司查詢列表:</p>
<ul id="company-result">
    
</ul>
<javascript 略……
//企業核名(可輸入多個以逗號分隔的名稱來屢次查詢)
$("#btn-company").click(function(event) {
    $("#reason").html("");
    $("#company-result").html("");
    // 更新驗證碼
    $("#verify-img").attr('src', '/?m=Home&c=Index&a=verify' + "&time=" + new Date().getTime());
    $.getJSON(
        '/?m=Home&c=Index&a=getCompanyName', 
        {
            province: $("#company-province").val(),
            companyName: $("#company-name").val(),
            verify: $("#verify").val(),
        }, 
        function(json, textStatus) {
            if(json['reason']){
                $("#reason").html(json['reason']);
            } else {
                //console.log(json);
                var json_list = JSON.parse(json);
                if (json_list.length > 0){
                    for(i in json_list){
                        var result_list = json_list[i].Result;
                        if (result_list.length > 0){
                            for(j in result_list){
                                //console.log(result_list[j]);
                                $("#company-result").append("<li> 名稱:" + result_list[j].Name + ", 法人:" + result_list[j].OperName + ", 狀態:" + result_list[j].Status + "</li>");
                            }
                        }
                    }
                }
            }
    });
});
</javascript>

後端代碼

/** 
* +--------------------------------------------------------------------------
* 企業核名
* 該接口 http://eci.yjapi.com/ECIFast/Search 僅支持 GET 方式,所以 $param 爲字符串形式
*
* @param string $get.province 省份
* @param string $get.companyName 公司名
* @return json
* +--------------------------------------------------------------------------
*/
public function getCompanyName(){
    // 傳入 get 參數
    $province                = I("get.province");
    $companyName    = I("get.companyName");
    $verify         = I("get.verify");
    // 處理驗證碼
    if ( !$this->check_verify($verify) ) {
        $content = array('resultcode'=>1000, 'reason'=>'驗證碼填寫錯誤');
        echo json_encode($content);
        exit();
    }
    // 處理機器人程序刷接口(目前經過IP判斷)
    $ip = get_client_ip(0, true);
    $Record = M("companyrecord");
    $company_record = $Record->where("ip='" . $ip . "'")->find();
    // if( $company_record && ( (time() - $company_record['time']) < 60 ) ){
    //     echo json_encode(array('reason'=>'60秒內只能查詢一次企業名稱'));
    //     exit();
    // }
    $resultArray = array();
    if ( $province && $companyName ) {
        // 刪除舊記錄(若是有),而後添加新的記錄
        $Record->where("ip='" . $ip . "'")->delete();
        // 循環處理多個公司名
        $companies = explode(",", $companyName);
        foreach ($companies as $key => $value) {
            $params = "key=" . C("COMPANY_KEY") . "&province={$province}&companyName={$value}";
            // 開發測試階段直接返回值,不請求 API
            $content = $this->juhecurl(C("COMPANY_URL"), $params, 0);
            $returnArray = json_decode($content,true);
            // 循環插入新的結果
            $resultArray[] = $returnArray;
            //$content = array('resultcode'=>200, 'reason'=>'查詢成功', 'result'=>array('list'=>array()));
        }
        $data = array(
            'ip' => $ip,
            'time'=>time()
        );
        $Record->add($data);
        $content = json_encode($resultArray, true);
        echo json_encode($content, true);
    }
}

結束

至此三個功能都已經完成了,可是在防止惡意用戶利用接口乾壞事的策略上,還能夠更進一步。例如

  • 必需要註冊用戶才能發送請求
  • 手機號碼天天只能限制發送有限的驗證碼(由於目前即便每分鐘發送一次,一天下來也比較可觀了)
  • 手機號碼註冊後,再次請求發送驗證碼時,只能發給「已有」的手機號碼數據表裏的手機號碼,而不能把「找回密碼」發給一個還沒註冊的人。
相關文章
相關標籤/搜索