天天看點

使用thinkphp6搭建後端api接口流程

1、下載下傳tp6

首先搭建wamp,或lamp的環境安裝composer,通過composer安裝tp6,thinkphp官網已經不再支援直接下載下傳。

composer create-project topthink/think tp6
           

在下載下傳好的tp6目錄通過cmd指令視窗輸入(如果綁定了域名,直接用域名方面,忽略這段)

php think run
           

在浏覽器中輸入127.0.0.1:8000,通路到如下頁面就安裝成功了

2、打開錯誤調試

1.找到config/app.php下的show_error_msg ,改成true

// 顯示錯誤資訊
'show_error_msg'   => true,
           

2.找到下面根目錄下的.example.env檔案,重命名此檔案,把.example删掉

檢視裡面的代碼,會發現,它打開了app_debug調試

3、隐藏入口檔案

如果什麼都不填,預設通路的就是index控制器,在config/app.php檔案中有這樣的定義,你也可以修改預設的控制器

// 預設應用
'default_app'      => 'index',
           

還有,不管通路任何控制器,如果沒有填方法,它都會通路控制器中的index方法,如果index方法不存在,則提示錯誤資訊-方法不存在。

通過在項目根目錄中運作的php think run開啟的web服務,tp6幫我們做了隐藏入口檔案的操作,是以你可以通過第三種方式通路。但是我們這一節要說的就是隐藏入口,怎麼能用tp6自帶的web服務呢。是以要自己來。

我們在開發時,往往會在本地搭建WNMP等這樣的一套web解決方案,這就需要我們自己去隐藏入口檔案index.php

為什麼要隐藏入口檔案?

  1. 因為像這樣子http://127.0.0.1:4321/index.php/index/index通路方法,這個index.php很不好看。
  2. 多餘。
  3. 危險

實作隐藏index.php很簡單,隻需要找到public目錄下的.htaccess檔案,添加如下代碼就可以了。

Apache版本:

<IfModule mod_rewrite.c> #如果mode_rewrite.c子產品存在 則執行以下指令
  Options +FollowSymlinks -Multiviews
  RewriteEngine On #開啟 rewriteEngine
  # !-d 不是目錄或目錄不存在
  RewriteCond %{REQUEST_FILENAME} !-d 
  # !-f 不是檔案或檔案不存在
  RewriteCond %{REQUEST_FILENAME} !-f 

  RewriteRule ^(.*)$ index.php [QSA,PT,L]
  # 參數解釋
  # ^(.*)$: 比對所有的路口映射
  # QSA: (Query String Appending)表示保留參數入get傳值?xxx==xx;
  # PT: 把這個URL交給Apache處理;
  # L: 作為最後一條,遇到這條将不再比對這條之後的規則
</IfModule>
           

Nginx版本:

location / {
  if (!-e $request_filename) {
    rewrite  ^(.*)$  /index.php?s=/$1  last;
  }
}
           

4、解決跨域問題

在應用開發中,前後端都是分開獨立開發的,而前後端通常都會自己搭建一個web服務,運作在不同的端口上,在前端通路後端的接口時,會報跨域的錯誤。而這種跨域問題通常是要有後端來處理的,tp6有專門的中間件來做這個事情,真是太友善了,隻需要在app目錄下的middleware.php中添加該中間件,就實作了跨域通路。

<?php
// 全局中間件定義檔案
return [
    // 全局請求緩存
    // \think\middleware\CheckRequestCache::class,
    // 多語言加載
    // \think\middleware\LoadLangPack::class,
    // Session初始化
    // \think\middleware\SessionInit::class,
    // 跨域解決
    \think\middleware\AllowCrossDomain::class,
];

           

5、路由解決api版本控制

在app目錄中的container控制器中建立兩個檔案夾v1,v2,在其中都建立User.php檔案

使用thinkphp6搭建後端api接口流程

v1/User.php

<?php

namespace app\controller\v1;
use app\BaseController;

class User extends BaseController {
    public function login() {
        return '這是v1接口';
    }
}
           

v2/User.php

<?php

namespace app\controller\v2;
use app\BaseController;

class User extends BaseController {
    public function login() {
        return '這是v2接口';
    }
}
           

注意上面兩個檔案的命名空間,就第一行代碼,在哪個檔案夾下,就寫到哪裡。

現在方法有了,我們還無法通路,需要使用路由,讓路由幫我們找對應的方法。

至于路由的概念去文檔自己看。我這裡主要用路由組的方式,我覺得這個比資源路由好用,靈活。

在根目錄下的route目錄下的app.php檔案代碼如下:

// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <[email protected]>
// +----------------------------------------------------------------------
use think\facade\Route;

// api版本控制
$v = request()->header('Api-Version');
// 預設api版本為v1
if ($v == null) {
    $v = 'v1';
}
Route::group('user', function () {
    Route::post('login', 'login');
})->prefix($v.'.user/')->pattern(['id' => 'd+']);
           

以上代碼進行控制api版本的方式是,請求發起者在header中傳遞要通路的api的版本,這裡擷取到對應的版本,通路對應的方法。

鑒于以上我使用的是post請求,且要傳遞header,是以使用ApiPost / postman進行測試。

通路v1版本的接口時:

使用thinkphp6搭建後端api接口流程

通路v12版本的接口時:

使用thinkphp6搭建後端api接口流程

6、jwt token驗證

thinkphp常用的擴充插件請參考:http://sites.thinkphp.cn/1556332

composer require thans/tp-jwt-auth
           

安裝完成後,該插件所在的位置在根目錄下的vendor/thans/tp-jwt-auth

還會在根目錄下的config目錄下生成jwt.php檔案來記錄一些配置資訊

使用thinkphp6搭建後端api接口流程

看這裡都是讀取的env中的參數,是以咱也在根目錄下的.env檔案中配置參數。

在根目錄下打開cmd視窗,執行

php think jwt:create
           

會幫你在.env檔案中生成密鑰secret,紅色框中的是新增的内容

使用thinkphp6搭建後端api接口流程

token的有效期為60秒,為了友善我們測試,我就不改了,如果你要改,可以在.env中添加,這樣就改成了半小時

使用thinkphp6搭建後端api接口流程

這個插件有三種方式【header,token,param】傳遞token,我就使用其中一個,也是最常用的一種,就是在【header】中傳遞token資訊,這個插件預設驗證header中的token資訊需要傳遞的參數名為authorization,而在header中直接傳遞該參數tp6是擷取不到的,需要做一些設定,

在根目錄中的public目錄下的.htacccess檔案中添加(Nginx好像不需要添加也能正常擷取到Authorization)

<IfModule mod_rewrite.c> #如果mode_rewrite.c子產品存在 則執行以下指令
  Options +FollowSymlinks -Multiviews
  RewriteEngine On #開啟 rewriteEngine
  # !-d 不是目錄或目錄不存在
  RewriteCond %{REQUEST_FILENAME} !-d 
  # !-f 不是檔案或檔案不存在
  RewriteCond %{REQUEST_FILENAME} !-f 

  RewriteRule ^(.*)$ index.php [QSA,PT,L]
  # 參數解釋
  # ^(.*)$: 比對所有的路口映射
  # QSA: (Query String Appending)表示保留參數入get傳值?xxx==xx;
  # PT: 把這個URL交給Apache處理;
  # L: 作為最後一條,遇到這條将不再比對這條之後的規則
  # 增加下面這項,否則在header中會擷取不到Authorization
  SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
</IfModule>
           

那麼現在開始測試:

(1).生成token

我就在之前建立的v1/User.php控制器中寫了

<?php

namespace app\controller\v1;
use app\BaseController;
use thans\jwt\facade\JWTAuth;

class User extends BaseController {
    public function login() {
        $token = JWTAuth::builder(['uid' => 1, 'name' => '測試1']);
        return $token;
    }
}
           

在ApiPost / postman中測試

使用thinkphp6搭建後端api接口流程

(2).驗證token

我使用的是路由中間件的方式驗證token,

① 寫一個中間件

在根目錄下的app目錄中建立middleware目錄,在其下建立CheckToken.php檔案

app/middleware/CheckToken.php

<?php

namespace app\middleware;

use thans\jwt\exception\JWTException;
use thans\jwt\facade\JWTAuth;

class CheckToken {
    public function handle($request, \Closure $next) {
        // OPTIONS請求直接傳回
        if ($request->isOptions()){
            return response();
        }
        try {
            JWTAuth::auth();
        }catch (JWTException $e){
            return json($e->getMessage());
        }
        return $next($request);
    }
}
           

② 起别名

給該中間件起個别名,在根目錄下的config/middleware.php檔案中

<?php
// 中間件配置
return [
    // 别名或分組
    'alias'    => [
        'CheckToken' => app\middleware\CheckToken::class
    ],
    // 優先級設定,此數組中的中間件會按照數組中的順序優先執行
    'priority' => [],
];
           

③ 在路由檔案中使用中間件 route/app.php

Route::group('user', function () {
    Route::post('login', 'login');
    Route::post('userInfo', 'getUserInfo')->middleware('CheckToken');
})->prefix($v.'.user/')->pattern(['id' => 'd+']);
           

④ 建立對應的方法

在第三步中我們建立了一個getUserInfo()方法,現在在User.php檔案中建立

public function getUserInfo() {
   return json(['id'=>1, 'name'=> '啦啦啦']);
}
           

⑤ 驗證一下

剛剛建立的token必然過期了,咱重新擷取一條

現在驗證一下,請求userinfo方法,并在header中添加參數Authorization,

注意:token值需要加上bearer ,bearer後的空格也要的。

使用thinkphp6搭建後端api接口流程

過了一分鐘後,我們再來試一試

使用thinkphp6搭建後端api接口流程

可以看到token驗證提示,該通過過期了,這個插件成功了,并沒有繼續往下走,把之前的資訊傳回。

(3).重新整理token,會将舊token加入黑名單

(4).登出或拉黑token

這個拉黑的具體操作就是把你要登出的token儲存在本地的cookie中,預設的儲存時間是14天,14天後cookie會自己删除的,你可以在根目錄下的runtime目錄下的cache目錄中找到對應的檔案,我就不測試這個方法了,我感覺這個操作好像沒什麼必要。

(5).驗證Token是否為黑名單

7、統一的參數傳回形式

實際開發中,後端傳回給前端的參數往往都是這樣的。

使用thinkphp6搭建後端api接口流程

是以我們需要對參數傳回形式做個統一的處理

在app目錄下的common.php中定義的方法全局都可調用,是以在這個檔案中定義此方法。

<?php
use think\Response;

// 應用公共檔案
// 統一傳回資料格式
function result($data = [], string $msg = 'error', int $code = 200, string $type = 'json') {
    $result = [
        'code' => $code,
        'msg' => $msg,
        'data' => $data
    ];
    // 調用Response的create方法,指定code可以改變請求的傳回狀态碼
    return Response::create($result, $type)->code($code);
}
           

哪裡用的時候直接調用即可

以下為常用狀态碼:

200 請求成功

204 請求成功,未傳回實體,比如option請求

400 錯誤的請求

401 認證失敗,這個一般在token驗證那裡

403 拒絕通路

404 請求的資源不存在

413 請求實體太大

422 參數驗證錯誤

500 伺服器錯誤

8、異常捕捉

tp6異常捕獲 https://www.kancloud.cn/manual/thinkphp6_0/1037615

(1)參數驗證錯誤捕捉

我們先寫一個參數驗證的類,在app目錄下建立validate目錄,建立User.php檔案

app/validate/User.php

<?php

namespace app\validate;
use think\Validate;

class User extends Validate {
    protected $rule = [
        'name' => 'require|max:25',
        'age' => 'number|between:1,120',
        'email' => 'email'
    ];
    protected $message = [
        'name.require' => '名稱必須',
        'name.max'     => '名稱最多不能超過25個字元',
        'age.number'   => '年齡必須是數字',
        'age.between'  => '年齡隻能在1-120之間',
        'email'        => '郵箱格式錯誤',
    ];
}
           

tp6的異常捕捉分為兩種,自動和手動的,手動的就是通過try{}catch{}捕捉。tp6的異常捕捉大多是自動的,不過,比如我們現在要操作的參數驗證錯誤就需要自己去捕捉來抛出異常,我們此節的目的是統一捕捉這個錯誤,我就不用手動的了。

我們就在異常處理類的render方法中添加這個捕捉抛出就可以了。

使用thinkphp6搭建後端api接口流程
// 1.參數驗證錯誤
if($e instanceof ValidateException){
     return result($e->getError(), '參數驗證不通過', 422);
}
           

現在在方法中一下,看看能否捕獲。

app/controller/v1/User.php

使用thinkphp6搭建後端api接口流程
使用thinkphp6搭建後端api接口流程

如果驗證通過了,就會正常的走下去,則會顯示我return的測試内容

使用thinkphp6搭建後端api接口流程

(2)未比對到資源或方法的異常捕獲

<?php
namespace app;

use ParseError; // 文法錯誤
use TypeError;
use InvalidArgumentException;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\db\exception\PDOException; // 資料庫連接配接錯誤
use think\db\exception\DbException; // 資料庫模型通路錯誤,比如方法不存在
use think\exception\RouteNotFoundException;
use think\exception\ClassNotFoundException;
use think\exception\FuncNotFoundException;
use think\exception\FileException;
use think\exception\Handle;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use think\Response;
use Throwable;

/**
 * 應用異常處理類
 */
class ExceptionHandle extends Handle {
    /**
     * 不需要記錄資訊(日志)的異常類清單
     * @var array
     */
    protected $ignoreReport = [
        HttpException::class,
        HttpResponseException::class,
        ModelNotFoundException::class,
        DataNotFoundException::class,
        ValidateException::class,
    ];

    /**
     * 記錄異常資訊(包括日志或者其它方式記錄)
     *
     * @access public
     * @param Throwable $exception
     * @return void
     */
    public function report(Throwable $exception): void {
        // 使用内置的方式記錄異常日志
        parent::report($exception);
    }

    /**
     * Render an exception into an HTTP response.
     *
     * @access public
     * @param \think\Request $request
     * @param Throwable $e
     * @return Response
     */
    public function render($request, Throwable $e): Response {
        // 添加自定義異常處理機制
        // 請求異常
        if ($e instanceof HttpException && $request->isAjax()) {
            return response($e->getMessage(), $e->getStatusCode());
        }
        // 使用了錯誤的資料類型 或 缺失參數
        if ($e instanceof InvalidArgumentException || $e instanceof ErrorException) {
            $fileUrlArr = explode(DIRECTORY_SEPARATOR, $e->getFile());
            $data = [
                'err_msg' => $e->getMessage(),
                'file' => $fileUrlArr[count($fileUrlArr) - 1],
                'line' => $e->getLine()
            ];
            return result($data, '參數錯誤', 413);
        }
        // 1.參數驗證錯誤
        if ($e instanceof ValidateException) {
            return result($e->getError(), '參數驗證不通過', 422);
        }
        // 2.方法(控制器、路由、http請求)、資源(多媒體檔案,如視訊、檔案)未比對到,
        // 一旦在定義的路由規則中比對不到,它就會直接去比對控制器,但是因為在控制器中做了版本控制v1,v2這樣的,是以它是無法擷取對應控制器的
        // 是以都會直接走了HttpException的錯誤
        // 感覺好像也無所謂,反正是做api接口的,隻不過這樣就不好準确的提示資訊了
        // 到底這個請求時控制器找不到呢?還是方法找不到?還是請求類型(get,post)不對?
        if (($e instanceof ClassNotFoundException || $e instanceof RouteNotFoundException) || ($e instanceof HttpException && $e->getStatusCode() == 404)) {
            $data = [
                'err_msg' => $e->getMessage(),
                'tip_1' => '請檢查路徑是否填寫正确',
                'tips_2' => '請檢查請求類型是否正确',
            ];
            return result($data, '方法或資源未找到,請檢查', 404);
        }
        // 3.文法錯誤
        if ($e instanceof ParseError) {
            $fileUrlArr = explode(DIRECTORY_SEPARATOR, $e->getFile());
            $data = [
                'err_msg' => $e->getMessage(),
                'file' => $fileUrlArr[count($fileUrlArr) - 1],
                'line' => $e->getLine()
            ];
            return result($data, '伺服器異常-文法錯誤', 411);
        }
        // 4.資料庫錯誤
        if ($e instanceof PDOException || $e instanceof DbException) {
            $fileUrlArr = explode(DIRECTORY_SEPARATOR, $e->getFile());
            $data = [
                'err_msg' => $e->getMessage(),
                'file' => $fileUrlArr[count($fileUrlArr) - 1],
                'line' => $e->getLine()
            ];
            return result($data, '伺服器異常-資料庫錯誤', 412);
        }
        // 其他錯誤交給系統處理
        return parent::render($request, $e);
    }
}
           

9、自動生成api文檔

(1)安裝插件 https://hgthecode.github.io/thinkphp-apidoc/guide/install/

composer require hg/apidoc
           

(2)下載下傳對應的前端頁面

請根據你安裝的apidoc版本 點選下載下傳 對應的前端檔案

Apidoc版本 Github Gitee(國内推薦)
v3.1.0 - v3.1.5 v2.1.3 v2.1.3
v3.0.0 - v3.0.8 v2.0.11 v2.0.11

下載下傳完成後解壓,将apidoc檔案夾拷貝到你的項目 public 目錄下

打開浏覽器通路 http://你的域名/apidoc/ ,出現接口文檔頁面,表示安裝成功。

(3)使用

具體配置你還得看文檔,我就直接照着最簡單的做了,

我就試一個,将app/controller/v1/User.php寫了注釋,它會讀注釋生成接口文檔

① 引入注釋

app/controller/v1/User.php

<?php

namespace app\controller\v1;
use app\BaseController;
use thans\jwt\facade\JWTAuth;
use app\validate\User as UserValidate;
// 添加這句,注釋寫法為 @Apidoc參數名(...)
use hg\apidoc\annotation as Apidoc;

/**
 * @ApidocTitle("V1")
 * @ApidocGroup("base")
 */
class User extends BaseController {
    /**
     * @ApidocTitle("登入")
     * @ApidocUrl("v1.user/login")
     * @ApidocTag("測試 基礎")
     * @ApidocParam("username", type="string",require=true, desc="使用者名" )
     * @ApidocParam("password", type="string",require=true, desc="密碼" )
     * @ApidocReturned("id", type="int", desc="新增使用者的id")
     */
    public function login() {
        //資料驗證,batch開啟批量驗證
        validate(UserValidate::class)->batch(true)->check([
            'name' => 'dongsir',
            'email' => '[email protected]'
        ]);
        return result(null, '成功', 200);
    }
}
           

② 檢視效果

使用thinkphp6搭建後端api接口流程

關于這個多應用/多版本的配置項,去apidoc的文檔去看吧,在config/apidoc.php修改apps的配置就可以了,然後就可以通過右上角的選擇框切換版本了

// 設定應用/版本(必須設定)

'apps'           => [
        [
            'title'=>'示範示例',
            'path'=>'app',
            'folder'=>'controller',
            'items'=>[
                ['title'=>'V1.0','path'=>'appcontroller1','folder'=>'v1'],
                ['title'=>'V2.0','path'=>'appcontroller2','folder'=>'v2']
            ]
        ],
    ],