有位朋友讓我給他新開的網站幫忙作幾個小功能,以下:javascript
而且能夠用的接口、文檔都提供給我了。
其中需求 一、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、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', // 字符集 );
爲了防止惡意用戶利用暴露在外的短信接口搗亂,須要對每一個手機號碼的行爲進行記錄。例如:
關於「表單驗證碼」咱們後面代碼會說明,這裏先建立表結構以下,用於記錄短信發送記錄:
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); }
由於 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); } }
至此三個功能都已經完成了,可是在防止惡意用戶利用接口乾壞事的策略上,還能夠更進一步。例如