參數(shù)規(guī)則:接口參數(shù)規(guī)則配置

2018-11-21 10:24 更新

“播下一種思想,收獲一種行為;播下一種行為,收獲一種習(xí)慣;播下一種習(xí)慣,收獲一種性格;播下一種性格,收獲一種命運(yùn)?!? --《成君憶:水煮三國》

1.12.1 參數(shù)解析

參數(shù),對于接口來說,是非常重要的輸入。對于外部調(diào)用來說,同等重要。

因此,對于參數(shù)這塊,我們是希望能夠既減輕后臺(tái)開發(fā)對接口參數(shù)獲取、判斷、驗(yàn)證、文檔編寫的痛苦;又便于客戶端方便的、自由的調(diào)用;既利已又利他。

由此,我們引入了 參數(shù)解析 這一概念,即:通過配置參數(shù)的規(guī)則,即可自動(dòng)實(shí)現(xiàn)參數(shù)的獲取和驗(yàn)證。

1.12.2 參數(shù)解析的配置規(guī)則

熟悉Yii的同學(xué),對于以下的規(guī)則配置應(yīng)該倍感親切,但是不熟悉的同學(xué)也可以同樣快速上手。因?yàn)椋銜?huì)慢慢發(fā)現(xiàn),這樣的規(guī)則很符合我們PHP開發(fā)的規(guī)范,如果沒有,我們繼續(xù)努力改進(jìn)。

格式如下:

array(
    '參數(shù)名' => array('name' => '接口參數(shù)名稱', 'type' => '類型', 'default' => '默認(rèn)值', ...),
    ... ...
)

1.12.3 示例

(1)簡單的示例

假設(shè)這樣的業(yè)務(wù)場景,我們需要提供一個(gè)用戶登錄的接口,其中需要用戶名和密碼,因此:

<?php

class Api_User extends PhalApi_Api
{
    public function getRules()
    {
        return array(
            'login' => array(
                'username' => array('name' => 'username'),
                'password' => array('name' => 'password'),
            ),
        );
    }

    public function login()
    {
        return array('username' => $this->username, 'password' => $this->password);
    }                                    
}

當(dāng)我們這樣調(diào)用接口時(shí):

/?service=User.Login&username=test&password=123456

就可以獲取到需要的參數(shù):

{"ret":0,"data":{"username":"test","password":"123456"},"msg":""}

從中,可以很容易理解:參數(shù)規(guī)則需要統(tǒng)一配置在接口實(shí)現(xiàn)類里面的 getRules() 函數(shù),隨后即可以通過類成員屬性方式獲取,如: $this->username 。

(2)更完善的示例

很多時(shí)候我們都會(huì)對用戶名和密碼作一些驗(yàn)證,如是否必須、長度、最值,以及默認(rèn)值等。

繼續(xù)上面的業(yè)務(wù)場景,我們登錄下用戶名和密碼必須,且密碼長度至少為6個(gè)字符,則可以調(diào)整參數(shù)規(guī)則:

'login' => array(
   'username' => array('name' => 'username', 'require' => true),
   'password' => array('name' => 'password', 'require' => true, 'min' => 6),
),

嘗試一下非法的參數(shù)請求,如無任何參數(shù)的情況下,訪問/?service=User.Login,返回:

{"ret":400,"data":[],"msg":"Illegal Param: wrong param: username"}

再嘗試一下密碼長不對的情況,訪問/?service=User.Login&username=test&password=123,返回:

{"ret":400,"data":[],"msg":"Illegal Param: password.len should >= 6, but now password.len = 3"}

1.12.4 三級參數(shù)

(1)系統(tǒng)參數(shù)

已被系統(tǒng)固定占有的參數(shù),目前只有一個(gè),即:service,為需要調(diào)用的服務(wù),類型為字符串,格式為:XXX.XXX,首字母不區(qū)分大小寫,建議統(tǒng)一以大寫開頭。

以下是一些示例:

#推薦寫法
/?service=User.GetBaseInfo

#正確寫法(開頭小寫)
/?service=user.getBaseInfo
#正確寫法(方法名小寫,但類名只能開頭小寫,否則會(huì)導(dǎo)致linux系統(tǒng)下文件加載失?。?/?service=user.getbaseinfo

#錯(cuò)誤寫法(缺少方法名)
/?service=User
#錯(cuò)誤寫法(缺少點(diǎn)號分割)
/?service=UserGetBaseInfo
#錯(cuò)誤寫法(默認(rèn)只支持點(diǎn)號分割)
/?service=User|GetBaseInfo

(2)應(yīng)用參數(shù)

應(yīng)用參數(shù)是指在一個(gè)項(xiàng)目中,全部接口都需要的參數(shù),或者通用的參數(shù)規(guī)則。假如我們的項(xiàng)目中全部需要簽名sign參數(shù),且必須;以及非必須的版本號,則可以在./Config/app.php中的apiCommonRules配置:

//$vim ./Config/app.php
<?php

return array(
    /**
     * 應(yīng)用接口層的統(tǒng)一參數(shù)
     */
    'apiCommonRules' => array(
        //簽名
        'sign' => array(
            'name' => 'sign', 'require' => true,
        ),
        //客戶端App版本號,如:1.0.1
        'version' => array(
            'name' => 'version', 'default' => '', 
        ),
    ),

    ... ...

(3)接口參數(shù)

接口參數(shù)即為上面在各個(gè)接口子類中配置的規(guī)則,為特定接口所持有。同時(shí),為了方便同一套接口的規(guī)則重用,可以使用下標(biāo)為 '*' 表示是本接口通用規(guī)則,如我們?yōu)榱思訌?qiáng)安全性,為全部的用戶接口操作都加上4位的驗(yàn)證碼:

    public function getRules()
    {
        return array(
            '*' => array(
                'code' => array('name' => 'code', 'require' => true, 'min' => 4, 'max' => 4),
                ),
            'login' => array(
                'username' => array('name' => 'username', 'require' => true),
                'password' => array('name' => 'password', 'require' => true, 'min' => 6),
            ),
        );
    }

在完成對上面的應(yīng)用參數(shù)規(guī)則、接口通用規(guī)則和指定規(guī)則的參數(shù)進(jìn)行配置后,對用戶登錄的接口進(jìn)行請求時(shí)就需要這樣訪問:

/?service=User.login&sign=77f81c17d512302383e5f26b99dae4d9&username=test&password=123456&code=abcd

溫馨提示:在Api類里面配置規(guī)則時(shí),下標(biāo)不區(qū)分大小寫。因?yàn)榭蚣軙?huì)自動(dòng)將請求的函數(shù)名和全部的規(guī)則下標(biāo)轉(zhuǎn)換成小寫進(jìn)行匹配。

這里,再小結(jié)一下,接口參數(shù)可以分為兩種: 通用接口參數(shù)指定接口參數(shù) 。前者用 * 號下標(biāo)表示,后者則用函數(shù)名作為下標(biāo)表示。

(4)多個(gè)參數(shù)規(guī)則的優(yōu)先級

當(dāng)同一個(gè)參數(shù)規(guī)則分別在應(yīng)用參數(shù)、接口通用參數(shù)及特定接口參數(shù)出現(xiàn)時(shí),后面的規(guī)則會(huì)覆蓋前面的,即具體化的規(guī)則會(huì)替換通用的規(guī)則,以保證接口在特定場合的定制性。

簡而言之,多個(gè)參數(shù)規(guī)則的優(yōu)先級從高到下,分別是(正如你想到的那樣):

  • 1、指定接口參數(shù)
  • 2、通用接口參數(shù)
  • 3、應(yīng)用參數(shù)
  • 4、系統(tǒng)參數(shù)(通常忽略,因?yàn)橹挥衧ervice)

1.12.5 在線接口參數(shù)查詢工具

為了便于理解上面全部的參數(shù)規(guī)則,對于具體接口調(diào)用的要求,這里可以使用在線接口參數(shù)查詢工具在瀏覽器訪問查看:

/demo/checkApiParams.php?service=User.Login

可以看到:
show

此工具同時(shí)也可以方便客戶端實(shí)時(shí)查看接口文檔時(shí),進(jìn)行輔助的接口規(guī)則說明。

自描述數(shù)據(jù)

這里值得一提的是,我們這里所定義的參數(shù)規(guī)則實(shí)際上也是自描述數(shù)據(jù)。即配置的代碼真實(shí)同步反映了參數(shù)的相關(guān)屬性。

1.12.6 參數(shù)傳遞的方式

系統(tǒng)下GET和POST皆可,但是推薦:

  • 1、service參數(shù)以GET方式傳遞,接口統(tǒng)一以/?service=XXX.XXX鏈接請求,便于交流,更重要的是當(dāng)接口發(fā)生問題時(shí),可以快速在服務(wù)器上通過nginx日志定位問題;

  • 2、其他參數(shù)以POST方式傳遞,特別對于敏感數(shù)據(jù),如密碼,以相對保護(hù)數(shù)據(jù)安全;

  • 3、在編寫文檔,或者進(jìn)行調(diào)試時(shí),可以全部臨時(shí)使用GET方式,如本文檔的寫法,同時(shí)在瀏覽器時(shí)也可以使用GET;

  • 4、如果需要對數(shù)據(jù)包進(jìn)行加密或者壓縮、自定義參數(shù)格式,可以重載PhalApi_Request::genData(),然后再繼續(xù)使用參數(shù)規(guī)則解析;

1.12.7 參數(shù)規(guī)則

類型type參數(shù)名稱 name是否必須require默認(rèn)值default最小值&最大值min&max更多
字符串stringtrue/false,默認(rèn)false應(yīng)為字符串可選regex下標(biāo)為正則匹配的規(guī)則;format下標(biāo)可用于定義字符編碼的類型,如utf8、gbk,gb2312
整數(shù)inttrue/false,默認(rèn)false應(yīng)為整數(shù)可選---
浮點(diǎn)數(shù)floattrue/false,默認(rèn)false應(yīng)為浮點(diǎn)數(shù)可選---
布爾值booleantrue/false,默認(rèn)falsetrue/false---以下值會(huì)轉(zhuǎn)換為true: ok, true, success, on, yes, 1
時(shí)間戳/日期datetrue/false,默認(rèn)false會(huì)按格式轉(zhuǎn)換可選,僅當(dāng)為timestamp時(shí)才判斷格式:format 為timestamp時(shí)會(huì)將字符串的日期轉(zhuǎn)換
數(shù)組arraytrue/false,默認(rèn)false為非數(shù)組會(huì)自動(dòng)轉(zhuǎn)換/解析成數(shù)組可選,判斷數(shù)組元素個(gè)數(shù)格式:format 為explode時(shí),會(huì)根據(jù)separator將字符串分割成數(shù)組, 為json時(shí),會(huì)json解析
枚舉enumtrue/false,默認(rèn)false應(yīng)為range中的某個(gè)元素---必須,range,以數(shù)組指定枚舉的范圍
文件filetrue/false,默認(rèn)false數(shù)組類型min和max表示文件大小范圍range下標(biāo)表示允許上傳的文件類型,ext表示需要過濾的文件擴(kuò)展名
回調(diào)callabletrue/false,默認(rèn)false---callback設(shè)置回調(diào)函數(shù),params為回調(diào)函數(shù)的第三個(gè)參數(shù),第一個(gè)為參數(shù)值,第二個(gè)為所配置的規(guī)則

溫馨提示:
全部的參數(shù)規(guī)則,都可以配置desc下標(biāo),對應(yīng)在線接口文檔的”說明“部分。
如: array('name' => 'username', 'desc' => '用戶名')

下面是對各類型的示例說明。

(1)字符串 string

當(dāng)一個(gè)參數(shù)規(guī)則 未指定類型時(shí),默認(rèn)為string。一個(gè)完整的寫法可以為:

array('name' => 'username', 'type' => 'string', 'require' => true, 'default' => 'nobody', 'min' => 1, 'max' => 10)

若傳遞的參數(shù)長度過長,如&username=alonglonglonglongname,則會(huì)異常失敗返回:

{"ret":400,"data":[],"msg":"Illegal Param: username.len should <= 10, but now username.len = 21"}

但是當(dāng)需要驗(yàn)證的是類型是中文的話會(huì)出現(xiàn)一點(diǎn)問題一個(gè)中文字符會(huì)占用3個(gè)字節(jié)所以在min和max驗(yàn)證的時(shí)候會(huì)出現(xiàn)一些問題,PhalApi提供了format方式對你需要驗(yàn)證長度的string進(jìn)行指定格式可以排除此問題

array('name' => 'username', 'type' => 'string','format' => 'utf8', 'min' => 1, 'max' => 10)

對于正則表達(dá)式的驗(yàn)證,一個(gè)郵箱的例子是:

'email' => array(
    'name' => 'email',
    'require' => true,
    'min' => '1',
    'regex' => "/^([0-9A-Za-z\\-_\\.]+)@([0-9a-z]+\\.[a-z]{2,3}(\\.[a-z]{2})?)$/i",
    'desc' => '郵箱',
),

(2)整型 int

如通常數(shù)據(jù)庫中的id,即可配置成:

array('name' => 'id', 'type' => 'int', 'require' => true, 'min' => 1 )

當(dāng)傳遞的參數(shù),不在其配置的范圍內(nèi)時(shí),如&id=0,則會(huì)異常失敗返回:

{"ret":400,"data":[],"msg":"Illegal Param: id should >= 1, but now id = 0"}

(3)浮點(diǎn) float

浮點(diǎn)型,類似整型的配置,此處略。

(4)布爾值 boolean

布爾值,主要是可以對一些字符串轉(zhuǎn)換成布爾值,如ok, true, success, on, yes, 以及會(huì)被PHP解析成true的字符串,都會(huì)轉(zhuǎn)換成true,方便調(diào)用。如通常的是否記住我:

array('name' => 'isRememberMe', 'type' => 'boolean', 'default' => true)

(5)日期 date

日期可以按自己約定的格式傳遞,當(dāng)需要將字符串的日期轉(zhuǎn)換成timestamp時(shí),可以這樣配置:

array('name' => 'registerData', 'type' => 'date')

對應(yīng)地,risterData=2015-01-31 10:00:00則會(huì)被獲取到為:"2015-01-31 10:00:00"。

如果是配置成:

array('name' => 'registerData', 'type' => 'date', 'format' => 'timestamp')

則上面的參數(shù)再請求時(shí),則會(huì)被轉(zhuǎn)換成:1422669600。

(6)數(shù)組 array

很多時(shí)候在接口進(jìn)行批量獲取時(shí),都需要提供一組參數(shù),所以這時(shí)可以使用數(shù)組來進(jìn)行配置。如:

array('name' => 'uids', 'type' => 'array', 'format' => 'explode', 'separator' => ',')

對應(yīng)&uids=1,2,3則會(huì)被轉(zhuǎn)換成:

array ( 0 => '1', 1 => '2', 2 => '3', )

又如接口需要使用JSON來傳遞整塊參數(shù)時(shí),可以這樣配置:

array('name' => 'params', 'type' => 'array', 'format' => 'json')

對應(yīng)&params={"username":"test","password":"123456"}則會(huì)被轉(zhuǎn)換成:

array ( 'username' => 'test', 'password' => '123456', )

特別地,當(dāng)配置成了數(shù)組,卻未指定格式format時(shí),會(huì)轉(zhuǎn)換成一個(gè)元素的數(shù)組,如:&name=test,會(huì)轉(zhuǎn)換成:array('test')。

(7)枚舉 enum

在需要對接口參數(shù)進(jìn)行范圍限制時(shí),可以使用此枚舉型。如對于性別的參數(shù),可以這樣配置:

array('name' => 'sex', 'type' => 'enum', 'range' => array('female', 'male'))

當(dāng)傳遞的參數(shù)不合法時(shí),如&sex=unknow,則會(huì)被攔截,返回失?。?/p>

{"ret":400,"data":[],"msg":"Illegal Param: sex should be in female\/male, but now sex = unknow"}

關(guān)于枚舉類型的配置,這里需要特別注意配置時(shí),應(yīng)盡量使用字符串的值。
因?yàn)橥ǔ6?,接口通過GET/POST方式獲取到的參數(shù)都是字符串的,而如果配置規(guī)則時(shí)指定范圍用了整型,會(huì)導(dǎo)致底層規(guī)則驗(yàn)證時(shí)誤。如:

//接口參數(shù)為: &type=N

//接口參數(shù)規(guī)則為:
array('name' => 'type', 'type' => 'enum', 'range' => array(0, 1, 2))

//誤判,因?yàn)椋?var_dump(in_array('N', array(0, 1, 2))); //結(jié)果為true,因?yàn)?'N' == 0

為了避免這類情況發(fā)生,應(yīng)該這樣配置:

//接口參數(shù)規(guī)則為(使用字符串):
array('name' => '&type', 'type' => 'enum', 'range' => array(`0`, `1`, `2`))

(8)文件 file

在需要對上傳的文件進(jìn)行過濾、接收和處理時(shí),可以使用文件類型,如:

array(
    'name' => 'upfile', 
    'type' => 'file', 
    'min' => 0, 
    'max' => 1024 * 1024, 
    'range' => array('image/jpeg', 'image/png') , 
    'ext' => array('txt','xml')
)

其中,min和max分別對應(yīng)文件大小的范圍,單位為字節(jié);range為允許的文件類型,使用數(shù)組配置,且不區(qū)分大小寫。

如果成功,返回的值對應(yīng)的是$_FILES["upfile"],即會(huì)返回:

array(
     'name' => '',
     'type' => '',
     'size' => '',
     'tmp_name' => '',
)

對應(yīng)的是:

  • $_FILES["upfile"]["name"] - 被上傳文件的名稱
  • $_FILES["upfile"]["type"] - 被上傳文件的類型
  • $_FILES["upfile"]["size"] - 被上傳文件的大小,以字節(jié)計(jì)
  • $_FILES["upfile"]["tmp_name"] - 存儲(chǔ)在服務(wù)器的文件的臨時(shí)副本的名稱
  • $_FILES["upfile"]["error"] - 由文件上傳導(dǎo)致的錯(cuò)誤代碼

若需要配置默認(rèn)值default選項(xiàng),則也應(yīng)為一數(shù)組,且其格式應(yīng)類似如上。

其中,ext是對文件后綴名進(jìn)行驗(yàn)證,當(dāng)如果上傳文件后綴名不匹配時(shí)將拋出異常。文件擴(kuò)展名的過濾可以類似這樣進(jìn)行配置:

//單個(gè)后綴名 - 數(shù)組形式
'ext' => array('jpg')

//單個(gè)后綴名 - 字符串形式
'ext' => 'jpg'

//多個(gè)后綴名 - 數(shù)組形式
'ext' => array('jpg', 'jpeg', 'png', 'bmp')

//多個(gè)后綴名 - 字符串形式(以英文逗號分割)
'ext' => 'jpg,jpeg,png,bmp'

(9)回調(diào) callable

當(dāng)需要利用已有函數(shù)進(jìn)行自定義驗(yàn)證時(shí),可采用回調(diào)參數(shù)規(guī)則,如:

//配置規(guī)則
array('name' => 'version', 'type' => 'callable', 'callback' => array('Common_MyVersion', 'formatVersion'))

然后,回調(diào)時(shí)將調(diào)用下面這個(gè)函數(shù):

//新增一個(gè)自定義的版本檢測函數(shù)
class Common_MyVersion {

    public static function formatVersion($value, $rule) {
        if (count(explode('.', $value)) < 3) {
            throw new PhalApi_Exception_BadRequest('版本號格式錯(cuò)誤');
        }
    }
}

溫馨提示:第一個(gè)為參數(shù)值,第二個(gè)為所配置的規(guī)則,第三個(gè)參數(shù)為配置規(guī)則中的params(可忽略)

1.12.8 關(guān)于參數(shù)設(shè)計(jì)的原則

(1)通配的$_REQUEST

使用$_REQUEST獲取參數(shù),便于在不同場合下GET/POST之間的切換,同時(shí)在初始化DI()->request服務(wù)時(shí),可以指定傳遞的參數(shù),以便于靈活的單元測試;

(2)更自由的名稱映射

之所以沒把規(guī)則配置的下標(biāo)默認(rèn)成與客戶端傳遞的name一致,是為了更自由的名稱映射;
如可能我們PHP后臺(tái)開發(fā)喜歡用駝峰法來表示,但客戶端想用下劃線來分割,則通過這樣配置:

array(
    'isRememberMe' => array('name' => 'is_remember_me', 'type' => 'boolean', 'default' => true),
)

更重要的是,有時(shí)我們希望能縮短客戶端請求的參數(shù)名稱以節(jié)省流量時(shí),可以這樣配置:

array(
    'isRememberMe' => array('name' => 're', 'type' => 'boolean', 'default' => true),
)

(3)異常返回

對于客戶端參數(shù)不合法時(shí),以異常失敗返回,而不是隱性地轉(zhuǎn)換,是因?yàn)楹笈_(tái)接口往往需要手動(dòng)對傳遞的參數(shù)進(jìn)行人工的驗(yàn)證,而不是希望得到隱性轉(zhuǎn)換的值。即當(dāng)客戶端參數(shù)傳遞不對時(shí),我們需要明確提示說:參數(shù)非法。

1.12.9 擴(kuò)展你的參數(shù)

當(dāng)PhalApi提供的參數(shù)規(guī)則不能滿足接口參數(shù)的規(guī)則驗(yàn)證時(shí),除了使用callable類型進(jìn)行擴(kuò)展外,還可以擴(kuò)展PhalApi_Request_Formatter接口來定制項(xiàng)目需要的類型。

一如既往,分兩步:

  • 1、擴(kuò)展實(shí)現(xiàn)PhalApi_Request_Formatter接口
  • 2、在DI注冊你的類型

下面以大家所熟悉的郵件類型為例,說明擴(kuò)展的步驟。

首先,我們需要一個(gè)實(shí)現(xiàn)了郵件類型驗(yàn)證的功能類:

<?php

class Common_MyFormatter_Email implements PhalApi_Request_Formatter {

    public function parse($value, $rule) {
        if (!preg_match('/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/', $value)) {
            throw new PhalApi_Exception_BadRequest('郵箱地址格式錯(cuò)誤');
        }

        return $value;
    }
}  

然后,注冊一下:

DI()->_formatterEmail = 'Common_MyFormatter_Email';

溫馨提示:在DI中手動(dòng)注冊服務(wù)時(shí),名稱的格式為: 下劃線("_") + 統(tǒng)一前綴("formatter") + 參數(shù)類型(全部小寫后,首字母大寫);
若需要實(shí)現(xiàn)自動(dòng)注冊,擴(kuò)展的類名格式須為:

class PhalApi_Request_Formatter_{類型名稱} implements PhalApi_Request_Formatter { ...

系統(tǒng)已自動(dòng)注冊的格式化服務(wù)有:

  • _formatterArray 數(shù)組格式化服務(wù)
  • _formatterBoolean 布爾值格式化服務(wù)
  • _formatterCallable 回調(diào)格式化服務(wù)
  • _formatterDate 日期格式化服務(wù)
  • _formatterEnum 枚舉格式化服務(wù)
  • _formatterFile 上傳文件格式化服務(wù)
  • _formatterFloat 浮點(diǎn)數(shù)格式化服務(wù)
  • _formatterInt 整數(shù)格式化服務(wù)
  • _formatterString 字符串格式化服務(wù)

至此,便可使用自己定制的類型規(guī)則了,

array('name' => 'user_email', 'type' => 'email')

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號