範例圖文詳解!thinkphp搭建後端api介面

2022-03-08 19:00:16
本篇文章給大家帶來了關於的相關知識,其中主要介紹了關於怎樣搭建後端api介面的相關問題,包括了隱藏入口檔案、解決跨域問題以及異常捕捉等等相關方面,希望對大家有幫助。

推薦學習:《》

這段時間學習了一下簡單搭建一個api介面後端服務,現在記錄一下。

1、下載tp6

我使用的是整合環境phpstuday,安裝了composer,通過composer安裝tp6,thinkphp官網已經不再支援直接下載。

composer create-project topthink/think tp6

你也可以直接按照tp6看雲檔案的步驟來安裝tp6

在下載好的tp6目錄通過cmd命令視窗輸入

php think run

在瀏覽器中輸入127.0.0.1:8000,存取到如下頁面就安裝成功了
在這裡插入圖片描述

2、開啟錯誤偵錯

在開始之間,我們先開啟tp6的錯誤偵錯
1.找到config/app.php下的show_error_msg ,改成true
在這裡插入圖片描述
2.找到下面根目錄下的.example.env檔案,重新命名此檔案,把.example刪掉
在這裡插入圖片描述
檢視這裡面的程式碼,會發現,它開啟了app_debug偵錯
在這裡插入圖片描述
這樣我們就能看到完整的報錯資訊了,例如:
在這裡插入圖片描述

3、隱藏入口檔案

在第1節中,我們存取

http://127.0.0.1:8000

實際存取的是

http://127.0.0.1:8000/index.php/index/index

你也可以通過這樣的方式存取

http://127.0.0.1:8000/index/index

如果什麼都不填,預設存取的就是index控制器,在config/app.php檔案中有這樣的定義,你也可以修改預設的控制器
在這裡插入圖片描述
還有,不管存取任何控制器,如果沒有填方法,它都會存取控制器中的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. 危險

我這裡因為用的整合環境,選用的是apache伺服器,所以我只找了apache的隱藏入口檔案的方法,nginx的需要自己搜尋了。
現在我啟用apache伺服器,開的埠是4321
在這裡插入圖片描述

當我想通過

http://127.0.0.1:4321/index/index

去存取方法時,存取失敗
在這裡插入圖片描述
而我加上入口檔案存取時,存取成功

http://127.0.0.1:4321/index.php/index/index

在這裡插入圖片描述
實現隱藏index.php很簡單,只需要找到public目錄下的.htaccess檔案,新增如下程式碼就可以了。

<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>

現在存取

http://127.0.0.1:4321/index/index

存取成功
在這裡插入圖片描述

需要注意,在第一節中我們看到,執行了php think run 後,我們的專案目錄存取的是public目錄
在這裡插入圖片描述
官方檔案中也說在專案中應該只有public目錄是可以被外界存取的,所以如果有什麼需要存取的圖片、視訊等資源,應該放在此目錄下

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檔案
在這裡插入圖片描述
v1/User.php

<?phpnamespace app\controller\v1;use app\BaseController;class User extends BaseController{
    public function login()
    {
      return '我是v1';
    }}

v2/User.php

<?phpnamespace app\controller\v2;use app\BaseController;class User extends BaseController{
    public function login()
    {
      return '我是v2';
    }}

注意上面兩個檔案的名稱空間,就第一行程式碼,在哪個資料夾下,就寫到哪裡。
現在方法有了,我們還無法存取,需要使用路由,讓路由幫我們找對應的方法。

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

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

<?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版本為v1if ($v == null) $v = "v1";// 使用者Route::group('user', function () {
  Route::post('login', 'login');})->prefix($v.'.user/')->pattern(['id' => '\d+']);

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

鑑於以上我使用的是post請求,且要傳遞header,所以使用postman進行測試。
存取v1版本的介面時:
在這裡插入圖片描述
存取v12版本的介面時:
在這裡插入圖片描述

6、jwt token驗證

我用的是tp6看雲檔案收錄的外掛

composer require thans/tp-jwt-auth

該外掛的github地址-檔案
在開始之前可以看看檔案裡是怎麼操作的,我也是按照檔案來的

安裝完成後,該外掛所在的位置在根目錄下的vendor/thans/tp-jwt-auth
還會在根目錄下的config目錄下生成jwt.php檔案來記錄一些設定資訊
在這裡插入圖片描述
看這裡都是讀取的env中的引數,所以咱也在根目錄下的.env檔案中設定引數。
在根目錄下開啟cmd視窗,執行

php think jwt:create

會幫你在.env檔案中生成金鑰secret,紅色框中的是新增的內容
在這裡插入圖片描述
token的有效期為60秒,為了方便我們測試,我就不改了,如果你要改,可以在.env中新增,這樣就改成了1小時
在這裡插入圖片描述
這個外掛有三種方式【header,token,param】傳遞token,我就使用其中一個,也是最常用的一種,就是在【header】中傳遞token資訊,這個外掛預設驗證header中的token資訊需要傳遞的引數名為authorization,而在header中直接傳遞該引數tp6是獲取不到的,需要做一些設定,
在根目錄中的public目錄下的.htacccess檔案中新增

SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0

在這裡插入圖片描述
那麼現在開始測試:

(1).生成token

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

<?phpnamespace app\controller\v1;use app\BaseController;// 引入jwt外掛use thans\jwt\facade\JWTAuth;class User extends BaseController{
    public function login()
    {
      // 生成token
      $token = JWTAuth::builder(['uid' => 1,'name'=>'ceshi']);
      return $token;
    }}

在postman中測試
在這裡插入圖片描述

(2).驗證token

我使用的是路由中介軟體的方式驗證token,

① 寫一箇中介軟體

在根目錄下的app目錄中建立middleware目錄,在其下建立CheckToken.php檔案
app/middleware/CheckToken.php
在這裡插入圖片描述
檔案內容

<?phpnamespace app\middleware;use thans\jwt\facade\JWTAuth;use thans\jwt\exception\JWTException;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檔案中
在這裡插入圖片描述

③ 在路由檔案中使用中介軟體在這裡插入圖片描述
④ 建立對應的方法

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

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

在這裡插入圖片描述

⑤ 驗證一下

剛剛建立的token必然過期了,咱重新獲取一條
在這裡插入圖片描述

現在驗證一下,請求userinfo方法,並在header中新增引數Authorization,
注意:token值需要加上bearer ,bearer後的空格也要的。
在這裡插入圖片描述
過了一分鐘後,我們再來試一試
在這裡插入圖片描述
可以看到token驗證提示,該通過過期了,這個外掛成功了,並沒有繼續往下走,把之前的資訊返回。

(3).登出token

這個外掛在github中的檔案中沒有說到怎麼登出或刪除token,只有一個重新整理refresh和拉黑invalidate,我看了一下它的程式碼,重新整理方法中會呼叫拉黑方法,看到這個註釋,讓我激動了一下,雞兒!原來拉黑就是登出
在這裡插入圖片描述
這個拉黑的具體操作就是把你要登出的token儲存在原生的cookie中,預設的儲存時間是14天,14天后cookie會自己刪除的,你可以在根目錄下的runtime目錄下的cache目錄中找到對應的檔案,我就不測試這個方法了,我感覺這個操作好像沒什麼必要。

檔案內容形似這樣
在這裡插入圖片描述
至此token這節就結束了。

什麼?
你想改預設的token名稱?那你可得好好研究這個外掛了,看看怎麼改,改完了記得踢我一腳,讓我也看看,雖然我覺得一個Authorization已經夠用了。
在這裡插入圖片描述

7、統一的引數返回形式

實際開發中,後端返回給前端的引數往往都是這樣的。
在這裡插入圖片描述
所以我們需要對引數返回形式做個統一的處理
在app目錄下的common.php中定義的方法全域性都可呼叫,所以在這個檔案中定義此方法。

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

唉,這個時候,經驗的重要性就體現出來,我是個前端,而且在我自己看來,還算是個沒有工作經驗的前端,

  1. 不知道他們後端到底怎麼處理這個狀態碼,網上的東西越看越亂,狀態碼可以分為業務狀態碼和請求返回的狀態碼,我這裡就簡單了,就只有請求返回的狀態碼,雖然在返回的資料中也傳遞了這個碼,其實沒什麼用,小專案、不復雜的專案根本用不少業務狀態碼。
  2. 我看別人還會單獨分裝成功和失敗的方法,這個就看自己習慣了,我感覺好像沒啥必要。
  3. 終究是經驗不足,網上能參考的程式碼太少,還千篇一律,最可氣的是一篇文章居然可以在多個部落格網站上出現,別人抄的、複製的就算了,作者自己也發這麼多地方,真的搞笑,百度一下,跳出來全是同一個,標題也是一樣,很迷~。

呼叫就很簡單了,可以直接使用
我們還是改一下login方法

public function login(){
   $data = [
     ['id'=>1,'name'=>'傑森'],
     ['id'=>2,'name'=>'麥克']
   ];
   $code = 200;
   $msg = '獲取成功';
   return result($data, $msg, $code);
 }

在這裡插入圖片描述
返回結果
在這裡插入圖片描述
這樣好像沒有體現我們修改的請求的狀態碼,那我們把$code改成500,再來看看結果如何
在這裡插入圖片描述
咱已經成功的將改請求狀態變成了500
得嘞,現在再來回頭看看我們之前寫的檢查jwt的中介軟體,把返回的結果封裝一下
在這裡插入圖片描述
再去驗證一下看看,看著返回的結果就舒服多了,這特麼才是後端給前端返回的結果。
在這裡插入圖片描述
本小節結束,記錄一下一些常見的狀態碼,我在自己寫的時候就只用到了這些狀態碼

狀態碼描述
200請求成功
204請求成功,未返回實體,比如option請求,這玩意兒用不著呀
400錯誤的請求
401認證失敗,這個一般在token驗證那裡
403拒絕存取
404請求的資源不存在
422引數驗證錯誤
500伺服器錯誤

7、異常捕捉

異常捕捉(看雲檔案)內容挺多的,自己去百度吧,我就把我遇到過的常見的錯誤進行捕捉,其它的異常我也愛莫能助,不懂啊 !>_>!
我也就不自定義類了,直接在它給的預設的例外處理檔案裡寫了。
在這裡插入圖片描述

(1)引數驗證錯誤捕捉

我們先寫一個引數驗證的類,在app目錄下建立validate目錄,建立User.php檔案
在這裡插入圖片描述
app/validate/User.php

<?phpnamespace 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方法中新增這個捕捉丟擲就可以了。
在這裡插入圖片描述

// 1.引數驗證錯誤
 if ($e instanceof ValidateException) {
     return result($e->getError(), '引數驗證不通過', 422);
 }

現在在方法中一下,看看能否捕獲。
app/controller/v1/User.php
在這裡插入圖片描述
檢視結果,成功被捕獲到了,並丟擲了錯誤內容
在這裡插入圖片描述
如果驗證通過了,就會正常的走下去,則會顯示我return的測試內容
在這裡插入圖片描述

(2)未匹配到資源或方法的異常捕獲

我還沒找到方法,在我的預想中這個應該要做到能夠準確的反應未匹配到的原因。

// 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(),
    'tips_1' => '請檢查路徑是否是否填寫正確',
    'tips_2' => '請檢查請求型別是否正確',
  ];
  return result($data, '方法或資源未找到,請檢查', 404);}

下面就不寫了,太麻煩了,直接放全部程式碼

<?phpnamespace 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\exception\ErrorException;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);
    }}

本節結束,這裡面用的錯誤處理都是我在平常練習中遇到的錯誤,至於其他的沒有處理是因為我還沒碰到,碰到再說吧。為了給前端好的反饋,我們應該處理所有的異常的返回形式,不然,tp6預設返回頁面形式的,前端等於得不到相應了。至於這個自定義異常捕獲,應該有相應的外掛的吧,你要是感興趣可以去找找。

7、自動生成api檔案

之前我還很好奇,後端是怎麼搞出介面檔案的,都是自己錄入資料套模板的嗎?原來他麼的都是外掛做的,真他麼方便!!!

(1)安裝外掛

composer require hg/apidoc// 檔案// https://hgthecode.github.io/thinkphp-apidoc/guide/install/

你就照著外掛的檔案來就好了,不用跟著我。

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

下載最新的,放在public目錄下
在這裡插入圖片描述

(3)使用

具體設定你還得看檔案,我就直接照著最簡單的做了,
我就試一個,將app/controller/v1/User.php寫了註釋,它會讀註釋生成介面檔案

① 引入註釋

app/controller/v1/User.php

<?phpnamespace app\controller\v1;use app\BaseController;// 新增這句,註釋寫法為 @Apidoc\引數名(...)use hg\apidoc\annotation as Apidoc;/**
 * @Apidoc\Title("V1")
 * @Apidoc\Group("base")
 */class User extends BaseController{
    /**
      * @Apidoc\Title("登入")
      * @Apidoc\Url("v1.user/login")
      * @Apidoc\Tag("測試 基礎")
      * @Apidoc\Param("username", type="string",require=true, desc="使用者名稱" )
      * @Apidoc\Param("password", type="string",require=true, desc="密碼" )
      * @Apidoc\Returned("id", type="int", desc="新增使用者的id")
      */ 
    public function login()
    {
       return result(null, '成功', 200);
    }}
② 檢視效果

在這裡插入圖片描述
這個介面檔案這裡有點小問題,因為我們前面使用在header中新增api版本的方式控制請求的api版本,所以如果直接用/user/login是無法存取到控制器的,也就存取不到方法,必須得加上控制器所在位置的資訊,就在前面加上了v1,變成了v1.user/login。這種形式是通過控制器去存取的方法,顯然不理想,我想要達到的目標是不需要再裡面加上v1,這個還得好好研究研究,不然前面定義的路由不是跟這個介面檔案對不上了嗎?你們要是研究到了,記得踢我一腳哈 >_>!

#後記:當時只是練習一下我,我也沒深究,but其實這個apidoc它的官方檔案裡有設定項的,關於這個多應用/多版本的設定項,去apidoc的檔案去看吧,在config/apidoc.php修改apps的設定就可以了,然後就可以通過右上角的選擇框切換版本了

    // 設定應用/版本(必須設定)
    'apps'           => [
        [
            'title'=>'演示範例',
            'path'=>'app',
            'folder'=>'controller',
            'items'=>[
                ['title'=>'V1.0','path'=>'app\controller\v1','folder'=>'v1'],
                ['title'=>'V2.0','path'=>'app\controller\v2','folder'=>'v2']
            ]
        ],
    ],

總結

一個簡單的後端介面這樣應該就夠用了,以我之前做過的學校的課程設計的經驗來說的哈。我沒有用過別的後端語言,不過就現在感覺,php真好用,不虧是世界上最好的語言。

推薦學習:《》

以上就是範例圖文詳解!thinkphp搭建後端api介面的詳細內容,更多請關注TW511.COM其它相關文章!