Swagger 生成 PHP restful API 接口文檔

需求和背景

需求:javascript

爲客戶端同事寫接口文檔的各位後端同窗,已經在各類場合回憶了使用自動化文檔工具前手寫文檔的血淚史.
個人故事卻又不一樣,由於首先來講,我在公司是 Android 組負責人,屬於上述血淚史中催死人不償命的客戶端陣營.
但血淚史倒是相通的,沒有自動化文檔的日子,對接口就是開發流程中最低效的環節.
所以決定使用 swagger 搭建由php註釋生成文檔的流程.php

背景:html

咱們的 restful api 項目採用 phalcon 框架,總體結構很簡單,咱們只須要用 swagger 掃描 controller 目錄便可.
下簡稱咱們的 php api 項目爲 php_api_project.
服務器採用 nginx.前端

搭建

先說下最終的文檔生成流程會是什麼樣子,以便先有個總體的認識:
搭建完成後, 整個流程, 從文檔生成到前端展示, 大致以下:java

  1. 在php文件中寫 swagger 格式的 /* 註釋 /
  2. 用 swagger-php 內的 bin/swagger.phar 命令掃描 php controller 所在目錄, 生成 swagger.json 文件
  3. 將 swagger.json 文件拷貝到 swagger-ui 中 index.html 指定的目錄中
  4. 打開 swagger-ui 所在的 url, 就能夠看到文檔了. 文檔中的各個 api 能夠在該網址上直接訪問獲得數據.

實現此需求只須要 swagger 的以下兩個項目:
swagger-php: 掃描 php 註釋的工具. 內含一個不錯的例子.
swagger-ui: 用以將掃描工具生成的 swagger.json 文件內容展現在網頁上.nginx

首先將這兩個項目下載到本地:git

$ git clone https://github.com/swagger-api/swagger-ui.git
$ git clone https://github.com/zircote/swagger-php.git

文檔生成工具部署

說是部署,主要就是產生 bin/swagger 這個用來生成 swagger.json 文件的命令.
主要工做,就是用 composer 解決下依賴就能夠了.
由於國內直接用 composer 比較蛋疼,因此最好設置下國內的那個 composer 源.
這樣的話, 整個 文檔生成工具的部署 就是下面三行命令:github

$ cd swagger-php
$ composer config repo.packagist composer https://packagist.phpcomposer.com
$ composer update

只要中間不報錯,就算部署完成了. 完成後能夠生成一份文檔試一下.
swagger-php 項目下的 Examples 目錄下有一個示例php工程,裏面已經用 swagger 格式寫了各類接口註釋, 咱們來嘗試生成一份文檔.
執行下面命令:apache

$ cd swagger-php
$ mkdir json_docs
$ php ./bin/swagger ./Examples -o json_docs/

上面命令會掃描 Examples 目錄中的php文件註釋, 而後在 json_docs 目錄下生成 swagger.json 文件.
這個 swagger.json 文件就是前端 swagger-ui 用來展現的咱們的api文檔文件.json

NOTE: swagger-php 只是個工具,放在哪裏均可以.

前端 swagger-ui 部署:

部署方法很簡單,就三步:

1. 將 swagger-ui 項目中的 dist 文件夾拷貝到 php_rest_api 根目錄下.

NOTE1: 只須要拷貝dist這一個文件夾就能夠了.最好重命名下,簡單起見,這裏再也不重命名.
NOTE2: 咱們的項目根目錄和 nginx 配置的 root 是同一個目錄.其實不用放跟目錄,只要放到一個不用跨域就跨域訪問的目錄就能夠了. 爲啥有跨域問題? 後面會講.

2. 修改 dist 文件夾下的 index.html 文件,指定 swagger.json 所在目錄

只改一行就能夠.
簡單起見,這裏直接將 swagger.json 目錄指定在 dist 目錄下便可. 咱們這裏屢一下預設條件:
假設 php_api_project 項目的 host 是 api.my_project.com;
假設 php_api_project 項目在 nginx 中指定的 root 即爲其根目錄;
假設 swagger-ui 裏的 dist 文件夾放在上述根目錄中;
假設 swagger.json 文件就打算放在上述 dist 目錄下 (php_api_project/dist/swagger.json) ;
那麼 index.html 中把下面的片斷改爲這樣:

var url = window.location.search.match(/url=([^&]+)/);
      if (url && url.length > 1) {
        url = decodeURIComponent(url[1]);
      } else {
        <!-- 就是這行,改爲你生成的 swagger.json 能夠被訪問到的路徑便可 -->
        url = "http://api.my_project.com/dist/swagger.json";
      }

3. 拷貝 swagger.json 到上述目錄中.

# 把 swagger-php_dir 這個,換成你的 swagger-php 錄便可
cp swagger-php_dir/json_docs/swagger.json php_api_project/dist/

上述步驟完成後, 訪問 http://api.my_project.com/dis... 就能夠看到 Examples 那個小項目的 api 文檔了.

編寫 PHP 註釋

swagger-php 項目的 Example 中已經有了不少相關例子,照着複製粘貼就能夠了.
更具體的相關注釋規則的文檔,看這裏:
http://bfanger.nl/swagger-exp...

假設個人項目 controller 所在目錄爲 php_api_project/controller/, 那麼我只須要掃描這個目錄就能夠了,不用掃描整個 php 工程.

爲了在 swagger.json 中生成某些統一的配置, 創建 php_api_project/controller/swagger 目錄. 目錄存放一個沒有代碼的php文件,裏面只寫註釋.

我給這個文件取名叫 Swagger.php, 大致內容以下:

<?php

/**
 * @SWG\Swagger(
 *   schemes={"http"},
 *   host="api.my_project.com",
 *   consumes={"multipart/form-data"},
 *   produces={"application/json"},
 *   @SWG\Info(
 *     version="2.3",
 *     title="my project doc",
 *     description="my project 接口文檔, V2-3.<br>
之後你們就在這裏愉快的對接口把!<br>
之後你們就在這裏愉快的對接口把!<br>
之後你們就在這裏愉快的對接口把!<br>
"
 *   ),
 *
 *   @SWG\Tag(
 *     name="User",
 *     description="用戶操做",
 *   ),
 *
 *   @SWG\Tag(
 *     name="MainPage",
 *     description="首頁模塊",
 *   ),
 *
 *   @SWG\Tag(
 *     name="News",
 *     description="新聞資訊",
 *   ),
 *
 *   @SWG\Tag(
 *     name="Misc",
 *     description="其餘接口",
 *   ),
 * )
 */

如上所示,個人這個php文件一行php代碼也沒有,就只有註釋,爲了定義一些全局的swagger設置:

schemes: 使用協議 (能夠填多種協議)
host: 項目地址, 這個地址會做爲每一個接口的 url base ,拼接起來一期做爲訪問地址
consumes: 接口默認接收的MIME類型, 個人例子中的 formData 對應post表單類型. 注意這是項目默認值,在單個接口註釋裏能夠複寫這個值.
produces: 接口默認的回覆MIME類型. api接口用的比較多的就是 application/jsonapplication/xml.
@SWG\Info: 這個裏面填寫的東西,會放在文檔的最開頭,用做文檔說明.
@SWG\Tag: tag是用來給文檔分類的,name字段必須惟一.某個接口能夠指定多個tag,那它就會出如今多組分類中. tag也能夠不用在這裏預先定義就可使用,但那樣就沒有描述了. 多說無益,稍微用用就啥都明白了.

而後就是給每一個接口編寫 swagger 格式的註釋了.仍是舉個栗子吧:

/**
     * @SWG\Post(path="/user/login", tags={"User"},
     *   summary="登陸接口(用戶名+密碼)",
     *   description="用戶登陸接口,帳號可爲 用戶名 或 手機號. 參考(這個會在頁面產生一個可跳轉的連接: [用戶登陸注意事項](http://blog.csdn.net/liuxu0703/)",
     *   @SWG\Parameter(name="userName", type="string", required=true, in="formData",
     *     description="登陸用戶名/手機號"
     *   ),
     *   @SWG\Parameter(name="password", type="string", required=true, in="formData",
     *     description="登陸密碼"
     *   ),
     *   @SWG\Parameter(name="image_list", type="string", required=true, in="formData",
     *     @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Image")),
     *     description="用戶相冊. 好吧,沒人會在登陸時要求填一堆圖片信息.這裏是爲了示例 帶結構的數據, @SWG\Schema ,這個結構須要另行定義,下面會講."
     *   ),
     *   @SWG\Parameter(name="video", type="string", required=true, in="formData",
     *     @SWG\Schema(ref="#/definitions/Video"),
     *     description="用戶 呃... 視頻? 同上,爲了示例 @SWG\Schema ."
     *   ),
     *   @SWG\Parameter(name="client_type", type="integer", required=false, in="formData",
     *     description="調用此接口的客戶端類型: 1-Android, 2-IOS. 非必填,因此 required 寫了 false"
     *   ),
     *   @SWG\Parameter(name="gender", type="integer", required=false, in="formData",
     *     default="1",
     *     description="性別: 1-男; 2-女. 注意這個參數的default上寫的不是參數默認值,而是默認會被填寫在swagger頁面上的值,爲的是方便用swagger就地訪問該接口."
     *   ),
     * )
     */
    public function loginAction() {
        // php code
    } 

    /**
     * @SWG\Get(path="/User/myWebPage", tags={"User"},
     *   produces={"text/html"},
     *   summary="用戶的我的網頁",
     *   description="這不是個api接口,這個返回一個頁面,因此 produces 寫了 text/html",
     *   @SWG\Parameter(name="userId", type="integer", required=true, in="query"),
     *   @SWG\Parameter(name="userToken", type="string", required=true, in="query",
     *     description="用戶令牌",
     *   ),
     * )
     */
    public function myWebPageAction(){
        // php code
    }

規則簡單明瞭,看着代碼你們就都懂了.不懂的話,去看文檔吧...

上面 login 接口中用到了兩個有結構的數據, 一個是 image 類型的數組, 一個是 video 類型的結構.
(其實結構化的參數只能在 in="body" 時才能夠用,但這並不妨礙咱們爲了簡化問題,把結構化數據格式化爲 json 當字符串傳遞. 咱們只要將這種結構展示在文檔裏就能夠了)

這種有結構的東西 swagger 也能夠用 php 註釋定義:

<?php

/**
 * @SWG\Definition(type="object", @SWG\Xml(name="Image"))
 */
class Image {

    /**
     * @SWG\Property()
     * @var string
     */
    public $url;
    
    /**
     * @SWG\Property(format="int32")
     * @var int
     */
    public $height;
    
    /**
     * @SWG\Property(format="int32")
     * @var int
     */
    public $width;

}

<?php

/**
 * @SWG\Definition(type="object", @SWG\Xml(name="Video"))
 */
class Video {

    /**
     * @SWG\Property()
     * @var string
     */
    public $url;

    /**
     * @SWG\Property()
     * @var string
     */
    public $thumb_url;

    /**
     * @SWG\Property(format="int32")
     * @var int
     */
    public $length;

    /**
     * @SWG\Property(format="int64")
     * @var int
     */
    public $size;

}

這樣當這兩個類也被 swagger-php/bin/swagger 掃描到後,其餘地方就能夠正確引用到 Image 和 Video 爲名字的這兩個結構體了.

這樣作的好處是,在接口參數文檔中,這個結構會被展現出來,這樣客戶端同窗就知道該傳什麼結構了.

個人接口栗子裏都沒有寫 response 規則,是由於咱們用 json 做爲返回載體,返回錯誤碼也是包含在這個 json 結構體裏. 而且多數接口返回的json格式都很複雜,用 swagger 的 response 規則基本無法描述.

swagger 的 response 編寫規則是按照 http 的 response code 來的 (404, 401 等), 總之對咱們的接口來講,這套描述規則很差用.

所以我就直接捨棄了 response 描述, 直接用 swagger 就地請求接口看看返回了什麼就是. 再不行就把接口的返回信息在 description 裏大致描述一下.

文檔寫完後,就能夠調用 swagger-php/bin/swagger 命令生成 swagger.json, 再拷貝到 swagger-ui 中你指定的那個目錄中,就能夠訪問文檔了.

NOTE:

你們應該已經看出來了,其實接口的註釋不必定要寫在接口上,憑空寫註釋,同樣能生成文檔.因此沒必要糾結各個註釋放在什麼地方. 好比 swagger 總體定義, tag 定義等,寫在任意能夠被掃描到的 php 文件中就能夠了.

經常使用字段簡要說明

這裏只是本身理解加翻譯的簡要說明,更詳細的字段說明,仍是要去看文檔.再次貼出文檔:
http://bfanger.nl/swagger-exp...

接口描述 (@SWGGet, @SWGPost 等) 經常使用字段:

summary - string
接口的簡要介紹,會顯示在接口標頭上,不能超過120個字符

description - string
接口的詳細介紹

externalDocs - string
外部文檔連接

operationId - string
全局惟一的接口標識

consumes - [string]
接口接收的MIME類型

produces - [string]
接口返回的MIME類型,如 application/json

schemes -    [string]
接口所支持的協議,取值僅限: "http", "https", "ws", "wss"

parameters -    [Parameter Object | Reference Object]
參數列表

參數描述 (@SWGParameter) 經常使用字段:

name - string
參數名. 經過路徑傳參(in 取值 "path")時有注意事項,沒用到,懶得看了...

in - string
參數從何處來. 必填. 取值僅限: "query", "header", "path", "formData", "body"

description - string
參數描述. 最好別太長

type - string
參數類型. 取值僅限: "string", "number", "integer", "boolean", "array", "file"

required - boolean
參數是否必須. 經過路徑傳參(in 取值 "path")時必須爲 true.

default - *
默認值. 在你打算把參數經過 path 傳遞時規矩挺多,我沒用到.用到的同窗本身看文檔吧.

自動生成文檔

此段寫於 2018-04-27. 本文其餘段落成於 2017-01-12. 方法僅限 git 項目.
今天有朋友問 php swagger 的使用方法, 又審了一遍此文, 才發現沒有寫自動生成這部分.
每次更新項目還要手動生成文檔的話, 就太麻煩了. 這種操做豈能不自動化. 方法以下:

前提: 項目爲 git 項目. 項目部署以 git 爲基礎.
原理: 使用 git 的 hook 機制 在適當節點執行文檔生成命令.
時機: 有兩種: 若是使用 git 部署項目, 則能夠在部署時自動生成; 若是不是, 也能夠考慮在 git commit 時自動生成.
步驟:

首先找到須要被掃描 controller 所在目錄, 假設其爲 /project_path/app/controllers/.
而後找到 swagger 生成的 json 格式文檔應該放置的地方, 假設其爲 /project_path/swagger/docs/.
那麼生成文檔的命令以下:

php /path/to/swagger-php/bin/swagger /project_path/app/controllers/ -o /project_path/swagger/docs/

那麼直接把上面這句命令放在對應的項目中的 .git/hooks/post-update 腳本中便可, 如沒有則新建.

好吧, 上面那行命令是爲了一眼能夠看出這行命令是作什麼的. 但它有個壞處, 就是要把項目的絕對路徑寫死在腳本里, 這樣這個腳本就不具備通用性. 上面這行命令能夠更新爲下面的這行等價命令:

proj_root=$(readlink -f $(dirname $(git rev-parse --git-dir))) && \
php /path/to/swagger-php/swagger-php/bin/swagger $proj_root/app/controllers/ -o $proj_root/swagger/docs/

如此, 則每次該項目代碼執行 git pull 命令時, swagger 文檔將會自動更新.

遇到的問題

跨域問題:

swagger 牛X的地方就是它能夠在文檔上就地訪問接口並展現輸出,對於調試和對接口很是的方便.但若是不想將 swagger-ui 部署在接口項目下,那麼在 swagger-ui 就地訪問接口時,就會因跨域問題而請求不到結果. (Response Headers: no response from server; Response Body: no content).
這裏不講跨域問題怎麼解決,只是給遇到上面的問題的各位一個思路,知道錯誤是由跨域產生的,就好解決了.

登陸鑑權:

若是要將文檔放在公網,直接暴露本身的接口可不太合適. 所以要給文檔的訪問地址作鑑權.
由於安全性要求不高,而且公司較小開發組人員很少,我就直接用了 nginx 提供的 http basic auth 作了登陸鑑權.
老規矩,先貼官方文檔:
https://www.nginx.com/resourc...
這樣作鑑權很簡單,分分鐘搞定. 不詳述, 就兩步:

1. 首先用 htpasswd 命令,爲須要訪問文檔的同窗生成賬號名和密碼:

$ htpasswd -cb your/path/to/api_project_accounts.db admin password_for_admin
$ htpasswd -b your/path/to/api_project_accounts.db liuxu 123456
$ htpasswd your/path/to/api_project_accounts.db xiaoming

-c 選項表示若是帳號文件 ( api_project_accounts.db ) 不存在,則新建之. 所以建立第一個帳號時必定要加 -c, 以後建立則必定不要再加 -c.
-b 參數表示明文指定密碼,就是上面第一條和第二條命令中最後一個輸入 (password_for_admin, 123456) . 所以若是不想明文指定,能夠不加 -b, 像上面的第三條命令,就會和sudo命令同樣,讓你輸入一段看不見的密碼.
上面命令建立了三個帳號, admin, liuxu, xiaoming, 並將帳號密碼保存在 api_project_accounts.db 文件中.

2. 而後在 nginx 的項目配置中給本身的這個訪問地址開啓 http basic auth 就能夠了.

location /dist {
    auth_basic              "my api project login";
    auth_basic_user_file    your/path/to/api_project_accounts.db;
}

記得改完了重啓 nginx:

nginx -s reload

這樣一個簡單的登陸鑑權就創建起來了.

NOTE:

若是系統裏沒有 htpass 這個命令, 能夠安裝 apache 的 httpd 服務器軟件, htpass 這個命令就包含在其中:

yum install httpd

參考:

[1] swagger 官網
[2] swagger 項目地址
[3] swagger ui 項目地址
[4] swagger php 項目地址
[5] swagger 文檔參考地址

相關文章
相關標籤/搜索