php后台對接ios,安卓,API接口設計和實踐完全攻略,漲薪必備技能


2016年12月29日13:45:27 
 
關於接口設計要說的東西很多,可能寫一個系列都可以,vsd圖都得畫很多張,但是由於個人時間和精力有限,所有有些東西后面再補充
 
說道接口設計第一反應就是restful api 請理解一點,這個只是設計指導思想,也就是設計風格 ,比如你需要遵循這些原則

原則條件
REST 指的是一組架構約束條件和原則。滿足這些約束條件和原則的應用程序或設計就是 RESTful。
Web 應用程序最重要的 REST 原則是,客戶端和服務器之間的交互在請求之間是無狀態的。從客戶端到服務器的每個請求都必須包含理解請求所必需的信息。如果服務器在請求之間的任何時間點重啟,客戶端不會得到通知。此外,無狀態請求可以由任何可用服務器回答,這十分適合雲計算之類的環境。客戶端可以緩存數據以改進性能。
在服務器端,應用程序狀態和功能可以分為各種資源。資源是一個有趣的概念實體,它向客戶端公開。資源的例子有:應用程序對象、數據庫記錄、算法等等。每個資源都使用 URI (Universal Resource Identifier) 得到一個唯一的地址。所有資源都共享統一的接口,以便在客戶端和服務器之間傳輸狀態。使用的是標准的 HTTP 方法,比如 GET、PUT、POST 和 DELETE。Hypermedia 是應用程序狀態的引擎,資源表示通過超鏈接互聯。

REST這個詞,是Roy Thomas Fielding在他2000年的博士論文中提出的

但是規則是很早之前的人設計的,但是it這個行業日新月異,業務結構復雜,變化性大,所有,你可以選擇遵守,也是適當改變,方便開發,比如現在移動化發展快速,app接口,微信網頁接口等

第一點要做的就是HTTPS  自2017年1月1號,開始新的ios都需要是https才能訪問,版本好像是10.0系列開始,並且滿足 蘋果ATS安全

如果您的APP如果仍采用HTTP傳輸,那么,在Apple Store中您的APP將不再能被用戶下載使用 ,所以,呵呵

 

場景分析和理論

 

 首先說下幾種運用場景:

1,后台服務端->app客戶端 ios 安卓

一般來說單向信任加密解密就可以,客戶端向服務器請求進行數據加密,發送服務器端進行解密,然后返回數據,

但是請求返回的數據是不加密的,信任是單向的,有時候也需要加密就是雙向加密,效率來說就比較麻煩和文件返回用

base64進行傳輸,處理效率也比較差,特別是服務器端還有專門的文件服務器的,代碼層級處理起來就比較麻煩,效率也

很差,做過ios和java的同學應該比較了解,直接作為文件傳輸,php文件上傳的時候,會先把文件上傳系統的臨時文件目錄

而不是先去驗證權限在上傳,全局變量$_FILE php在上傳到其他文件服務器,或者移動到文件目錄,如果客戶端

沒有做驗證,或者允許大文件上傳,就會存在temp文件夾爆炸,服務器宕機的危險,對於中小項目來說,不是被人惡意攻擊

問題不大,這種情況下需要使用base64傳輸就可以先驗證在判斷是不是在做處理,然后就是在服務器驗證時候加上針對每

個接入端標識存儲,線上好處理具體問題來自哪些方面的接口,可以比較好的定位

 

 

2,后台服務端->html5 微信 瀏覽器

因為現在狠多為了一種開發多處使用,必須現在流行的html5混合app開發簡單方便,如果不是做游戲,性能一般app足夠

使用,不要被原生性能更好的屁話左右,為了適配機器app需要做無數處理,麻煩的很,而且對於小公司人力成本是巨大的,

而且不同語言的數據對接本身就是時間消耗,精力消耗,各種未知bug的調試,特別是第一次做個的人,有些東西在不同語言

顯示方式根本不一樣,比如 一個數組(php)

key=>0,name=>z key在ios解析的時候key不顯示,安卓key顯示NULL,呵呵,所以一些細節很麻煩,但是也並不是原生的

不好,只是就現在的技術潮流來說是這樣,比如如果一些ios新特性,在app的混合開發框架肯定不會那么及時的更新api接口

,比如hbuilder,做的HTML5+,整體性能也肯定沒有object-c好,swift個人沒什么了解,所以不清楚,所以有利有弊,需要技

術經理,公司,開發人員來衡量。

 htnl5頁面在微信和瀏覽器里面需要的快速,如果每次都需要驗證數據加密,機密,效率首先需要考慮,而且js處理session

和cookies,在頁面不太好處理,很多現在很多都是純js去渲染數據,如果使用php來混合編寫也是可以的,使用php來模擬

數據加密,請求接口,這樣寫比較容易,但是無法再混合app中使用,如果你只是單獨開發手機瀏覽器和微信里面來使用php

混寫是沒有問題的,如果是頁面需要純js處理數據的話么就需要注意我說的上面的問題

數據認證的話,可以簡單做用戶登錄,比如微信自動等,根據name和password,salt計算一個唯一值作為token去數據庫校驗,

接口請求的時候js發送請求的時候校驗即可

 

3,文件服務器 特殊處理

很多項目到了后期都需要考慮單獨的文件服務器的問題,比如我前面博客說道的,掛在nfs文件服務,或者文件服務器接口

文件服務器處理起來比較麻煩,但是對於大的項目來說也是必要的,所有服務都需要接口化,在接口認證里面進行權限和

資源分配,這就是SOA的過程,比如使用文件接口,在富文本編輯器上傳文件的時候,就需要改造富文本編輯器的文件上傳

所以有點麻煩,uedit修改文件上傳路勁,支持api文件接口 我這篇博客就有介紹,文件服務器也有好處,就是可以規避一部

分文件安全問題

4,后台服務端->游戲客戶端

 這個和html有些相似,又有些不同,一個是為了高效數據傳輸,保持socket穩定,或者數據傳輸的速度,還有就是保證

數據不被篡改,比如游戲作弊,考慮的東西比較多,還有比如一局游戲某個用戶斷線了,多久T掉,保證游戲繼續進行下去,接口里面

需要處理的東西很多,因為游戲需要更新的狀態很多,合理的設計表結構也是無比重要,比如一個buff就增加一個字段的話,一個玩家狀態

對應就有幾百個字段,傳輸數據當然就會大,一次更新那么數據,速度和性能就得再次考慮

(后面更新)

 

 

 API接口理論設計實踐

1, 加密解密

使用https的 公鑰 私鑰 加密解密,但是其實也是發送數據不加密,只是通過簽名pkf crt來加密和驗證簽名的正確性,

很多借口采用的就是這種設計,但是數據安全性,我不是很理解,發送的是明文數據,木馬程序可以很輕松的監聽,

這種單向信任的明文數據發送驗證借口,在瀏覽器訪問的時候是有自帶https的公鑰,但是php curl的是可以不帶證書訪問的,

所有依然是接口里面是明文發送出去的,(如果這段有錯,請反饋)

 

2接口理論

現在普遍采用3des加密,只是因為方便,現在都出都有php ios java的通用demo方便,不方便的地方也有很多,發送的就是加密的數據,

雖然是對稱加密解密,單向信任,但是也是根據單個接口的賬號密碼進行二次驗證,如果你key和sercet,被別人知道,也是可以解密出加密發送的數據,如

果想使用接口獲取數據,還是得有該用的用戶名和秘密的,當然現在流行的手機號碼+手機短信驗證碼也是可以,但是服務器對應實現才可以,而且

最好ios和安卓開發框架需要支持session,cookies,https等為好,后面會有說明

推薦框架,但是需要支持這個多個協議

AFNetworking  ios
 OkGo  安卓  

 3,數據傳輸格式

json xml  數據流  二進制 等等,但是數據解析方便來說json還是最方便的。建議json,主要是怕麻煩

 

4,支持請求方法POST GET 文件上傳 

 麻煩一點就是文件上傳,base64上傳,或者直接文件上傳,但是臨時文件會出現上面說的系統臨時文件爆了情況

 

5,兼容舊代碼進行模擬登陸session cookies ,權限兼容

 其他很多系統都需要去舊的rabc的里面去獲取數據,模擬登錄的時候,就會涉及到權限問題,當然你也可以單獨剝離出來

這些功能,時間花的也比較長,特別是需要在客戶端也要實現一定的權限的時候就麻煩了,你單獨剝離出來,你又要去模擬

權限,當然,最好辦法就是,接口對應的賬號里面也去實現一套RBAC,這樣后續開發人員就輕松了,看起來有點蛋疼,但是是比較

實際的問題,這個問題如果在設計接口的時候沒有就考慮進去的話,就在對接一些舊功能就忒麻煩,特別是沒有獨立方法化的代碼時候,

耦合度大,改起來麻煩的要死

 

6,目前接口優缺點。另一種接口設計方式

 這個接口是所有終端同一個加密的秘鑰,也就說一個終端秘鑰丟失,就是可以獲得其他終端發送的數據進行解密,獲得發送數據,

所以中小項目,或者某個公司內部項目使用還不錯,但是大項目api化的數據安全性就不是很好

另一種接口設計,就是每個終端都是自己使用的8位key和32位的 value 然后加密還是下面的加密方法,但是在吧user_key直接明文也帶過

來先把數據庫的這個用戶的key 和value ,先解密,后吧對比解密數據里面的value 對比一樣就通過,去處理數據,就像一般的

用戶名密碼登錄校驗一樣,或者你想更安全就是md5('value .key') 去對比,全部不明文發送過來的數據

如果循環數據庫的key 和value去解密,效率太低,app接口需要就速度和效率

 

 

7,一些細節問題

 

$_REQUEST['header'] 為什么要這樣獲取數據,因為在ios和安卓,字典必須是要key和vaule的,所以兼容
urldecode建議不要使用這對函數 使用

rawurldecode($str);
rawurlencode($str);

會出現+ 和%2D 空格,多種語言交互的時候這樣的問題很多,所以要注意,特別是對接接口的時候,簽名的時候

 

簽名加密算法:sha1 長度40 (因為語言可能導致生產長度不一樣)

 
          
 
           
 foreach (unserialize($res['contract']['idcards_file_ids']) as $k => $v) {
                
             $rr['user_idcard_data'][$k]['file_paths'] = app_standard_path_new($file_path['file_path']);
                    $rr['user_idcard_data'][$k]['file_id'] = $v['file_id'];
                    $rr['user_idcard_data'][$k]['name'] = $v['name'];
               
            }
        }

 
           

如果把 $rr['user_idcard_data'] json傳給app端,他就是個字典不是數組,對於ios和安卓來說

"customer_idcard_file": {
            "1": {
                "file_paths": "http://app.xinyzx.com/Uploads/personal_app_ios/201704/11/58ec42d23c090.jpeg",
                "file_id": "717892",
                "name": "work_card"
            }
        },

 

$rr['user_idcard_data'] = array_values($rr['user_idcard_data']);

 

需要處理成以下格式

"file_id":[
            {
                "file_paths":"58ec42d22f310.jpeg",
                "file_id":"717891",
                "name":"bank_card1"
            },
            {
                "file_paths":"58ec42d23c090.jpeg",
                "file_id":"717892",
                "name":"work_card"
            }
        ]

 

 

 

demo實例代碼,測試代碼

本代碼基於tp3.1.2 目前不提供完整測試類,后面有時間在更新

目前只支持 key 8位 secket 32位,支持更多位數的后續跟新

Crypt3Des.class.php  加密算法 3DES

<?php

class Crypt3Des {

    private $key = "Symetric";
    private $iv = "Symetric";

    /**
    * 構造,傳遞二個已經進行base64_encode的KEY與IV
    *
    * @param string $key
    * @param string $iv
    */

    function __construct($key, $iv) {
        if (empty($key) || empty($iv)) {
            echo 'key and iv is not valid';
            exit();
        }
        $this->key = $key;
        $this->iv = $iv;
    }

    /**
    *加密
    * @param  $value
    * @return
    */
    public function encrypt($value) {
        $td = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_ECB, '');
        $iv = base64_decode($this->iv);
        $value = $this->PaddingPKCS7($value);
        $key = base64_decode($this->key);
        mcrypt_generic_init($td, $key, $iv);
        $ret = base64_encode(mcrypt_generic($td, $value));
        mcrypt_generic_deinit($td);
        mcrypt_module_close($td);
        return $ret;
    }

    /**
    *解密
    * @param  $value
    * @return
    */
    public function decrypt($value) {
        $td = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_ECB, '');
        $iv = base64_decode($this->iv);
        $key = base64_decode($this->key);
        mcrypt_generic_init($td, $key, $iv);
        $ret = trim(mdecrypt_generic($td, base64_decode($value)));
        $ret = $this->UnPaddingPKCS7($ret);
        mcrypt_generic_deinit($td);
        mcrypt_module_close($td);
        return $ret;
    }

    private function PaddingPKCS7($data) {
        $block_size = mcrypt_get_block_size('tripledes', 'cbc');
        $padding_char = $block_size - (strlen($data) % $block_size);
        $data .= str_repeat(chr($padding_char), $padding_char);
        return $data;
    }

    private function UnPaddingPKCS7($text) {
        $pad = ord($text{strlen($text) - 1});
        if ($pad > strlen($text)) {
            return false;
        }

        if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) {
            return false;
        }
        return substr($text, 0, - 1 * $pad);
    }

}

 

BaseAction.class.php

<?php

//API父類
class BaseAction extends Action {

    protected $user_id;
    protected $appkey;
    protected $secret;
    protected $appIDname; //接入用戶來源標示,可能在用戶數據錄入的時候會用到

    public function _initialize() {



        myLog($_REQUEST, 'api_log');
        if (empty($_REQUEST) && empty($_FILES)) {
            $this->response(array('code' => 0, 'msg' => '發送數據不能為空'), 'json', 200);
        }
        $data = $_REQUEST['header'];

        if (empty($data)) {
            $data = urldecode(file_get_contents('php://input'));  //兼容php發送數據接收 和 php模擬測試,正式不一定

            if (empty($data)) {
                $this->response(array('code' => 0, 'msg' => '發送數據不能為空'), 'json', 200);
            }
        }



        $data = $this->crypt3des()->decrypt($data);
        myLog($data, 'api_log');
        $_POST = json_decode($data, true);
        //api接口日志記錄--打印發送數據
        myLog($_POST, 'api_log');

        $this->origin = $this->check_sign($_POST);
    }

    function crypt3des() {
        import('App.ORG.Crypt3Des');
        $crypt3des = new Crypt3Des(base64_encode(C('crypt_key')), base64_encode(C('crypt_iv')));
        return $crypt3des;
    }

    function _empty() {
        $this->response(array('code' => 0, 'msg' => '_empty,非法操作'), 'json', 200);
    }

    protected function check_sign($data, $expires = 300) {
        $params = array();
        $params['timestamp'] = $data['timestamp'];
        if (time() - strtotime($params['timestamp']) > $expires) {
            $this->response(array('code' => 0, 'msg' => '簽名已過期'), 'json', 200);
        }
        //數據庫查詢校驗相關用戶數據
        $where['app_api_name'] = $data['appkey'];
        $res = M('app_api_partner')->where($where)->find();

        if (empty($res)) {
            $this->response(array('code' => 0, 'msg' => 'appkey錯誤'), 'json', 200);
        }
        if ($res['api_status'] !== '0') {
            $this->response(array('code' => 0, 'msg' => '目前api賬戶不可用'), 'json', 200);
        }
        if ($res['app_api_key'] !== $data['secret']) {
            $this->response(array('code' => 0, 'msg' => '通信密鑰錯誤'), 'json', 200);
        }
        $params['appkey'] = $res['app_api_name'];
        $params['secret'] = $res['app_api_key'];

        $sign = $this->data_auth_sign($params);

//        $this->response(array('msg' => $params, 'code' => 0), 'json', 200);
        if ($sign !== $data['sign']) {
            $this->response(array('code' => 0, 'msg' => '簽名錯誤'), 'json', 200);
        } else {
             myLog('簽名解析正確', 'api_log');
            $this->appIDname = $res['app_api_mark']; //返回接入的使用的名稱
        }


    }


    private function data_auth_sign($data) {
        if (!is_array($data)) {
            $data = (array) $data;
        }
        ksort($data);
        $param = array();
        foreach ($data as $key => $val) {
            $param[] = $key . "=" . $val;
        }
        $param = join("&", $param);
        $sign = sha1($param);
        return $sign;
    }

}


C 獲取系統配置數據
 
           
myLog 打印系統日志
function myLog($str, $flag = 'default') {
    //if( APP_DEBUG != true )return '';
    is_array($str) && $str = print_r($str, true);
    $dir = SYSTEM_ROOT . '/Uploads/logs/' . $flag . '/';

    !is_dir($dir) && @mkdir($dir, 0755, true);
    $file = $dir . date('Ymd') . '.log.txt';
    $fp = fopen($file, 'a');
    if (flock($fp, LOCK_EX)) {
        $content = "[" . date('Y-m-d H:i:s') . "]\r\n";
        $content .= $str . "\r\n\r\n";
        fwrite($fp, $content);
        flock($fp, LOCK_UN);
        fclose($fp);
        return true;
    } else {
        fclose($fp);
        return false;
    }
}

 

隨機生成一個8位隨機字符串

function get_rand_str($len) {
        $chars = array(
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
            'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
            'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
        );
        $charsLen = count($chars) - 1;
        shuffle($chars);
        $output = "";
        for ($i = 0; $i < $len; $i++) {
            $output .= $chars[mt_rand(0, $charsLen)];
        }
        return $output;
    }

 

 
 

 

 

 

 集成這個父控制就可以寫你自己的代碼了,下面是一個實力和一個接口測試的demo

 

<?php

//客戶信息相關api接口類

class CustomerAction extends BaseAction {

   
    public function test() {

        $this->response(array('code' => 1, 'msg' => '測試成功', 'data' => $data), 'json', 200);
    }

}

 

測試接口的demo

 import('app.ORG.Crypt3Des');
        $crypt3des = new Crypt3Des(base64_encode(C('crypt_key')), base64_encode(C('crypt_iv')));


        $data['appkey'] = $_data['appkey'] = '11111111'; // 用於簽名參數  對應C的取到的值
        $data['secret'] = $_data['secret'] = md5('11111111'); //用於簽名參數
        $data['timestamp'] = $_data['timestamp'] = date('YmdHis', time()); //用於簽名參數

        $data['sign'] = $this->data_auth_sign($_data);

        $data = json_encode($data);


        $crypt_data = (urlencode($crypt3des->encrypt($data)));

        $url = "http://127.0.0.1/app.php/customer/test";

        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_VERBOSE, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $crypt_data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        $return_data = curl_exec($ch);

        print_r($return_data);
        echo '<hr />';
        print_r(json_decode($return_data, true));

        if ($error = curl_error($ch)) {
            die($error);
        }
//        $rinfo = curl_getinfo($ch);
//        P($rinfo);
        curl_close($ch);

 

 2017年6月24日23:17:10

這里補充一點,這個接口有個比較大的問題,就是沒有版本控制,比如在接口加個V參數。

最新碰到一個問題就是,有個比較大的改版,因為ios發布不通過,導致安卓可以升級但是ios不能,所以整體升級就只能直接進行

解決發難,就是根據V參數的版本號去訪問不同的比如,在訪問控制器的ZxAction.class.php 的test方法的時候,實際訪問的是Zxv3Action.class.php,或者 V3_zxAction.class.php,

在基礎控制器里面處理一下,不然在大版本更新,如果某些接口是不能升級的話,就很需要這個參數進行處理對應的接口

/* 新增版本控制參數v很重要,根據版本控制,比如
         * 
         * 訪問 m=test&a=zx
         * 里面有$_POST['v'] =v1 
         * 實際訪問的就是 m=test&a=v1_zx
         */
        if (!empty($_POST['v'])) {
            $module = 'App_admin://' . MODULE_NAME;
            $function = $_POST['v'] . '_' . ACTION_NAME;
            A("$module")->$function();   這里實例化的時候,會再次經過_initialize方法,有問題
            die;
        }

這個如果寫在父控制器就會出現無限循環,出現問題


需要在自控制器里面加入這個,因為在初期沒有考慮到這個,就只能補充這個,唉,經驗不足
  public function __construct() {
        parent::__construct();
        if (!empty($_POST['v'])) {
            $function = trim($_POST['v']) . '_' . ACTION_NAME;
            $this->$function();
            die;
        }
    }
 
           

 

其實還可以在父控制使用二級域名來控制,進行版本控制

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM