<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                這段時間學習了一下簡單搭建一個api接口后端服務,現在記錄一下。 本文主要內容 下載tp6 打開錯誤調試 隱藏入口文件 解決跨域問題-(使用全局中間件處理) 路由解決api版本控制 jwt token驗證-(使用路由中間件驗證) 統一的參數返回形式 異常捕捉 自動生成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 為什么要隱藏入口文件? 因為像這樣子http://127.0.0.1:4321/index.php/index/index訪問方法,這個index.php很不好看。 多余。 危險 我這里因為用的集成環境,選用的是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 ``` <?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文件代碼如下: ``` <?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 <liu21st@gmail.com> // +---------------------------------------------------------------------- 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,所以使用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控制器中寫了 ``` <?php namespace 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 文件內容 ``` <?php namespace 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中定義的方法全局都可調用,所以在這個文件中定義此方法。 ``` <?php use 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); } ``` 唉,這個時候,經驗的重要性就體現出來,我是個前端,而且在我自己看來,還算是個沒有工作經驗的前端, 不知道他們后端到底怎么處理這個狀態碼,網上的東西越看越亂,狀態碼可以分為業務狀態碼和請求返回的狀態碼,我這里就簡單了,就只有請求返回的狀態碼,雖然在返回的數據中也傳遞了這個碼,其實沒什么用,小項目、不復雜的項目根本用不少業務狀態碼。 我看別人還會單獨分裝成功和失敗的方法,這個就看自己習慣了,我感覺好像沒啥必要。 終究是經驗不足,網上能參考的代碼太少,還千篇一律,最可氣的是一篇文章居然可以在多個博客網站上出現,別人抄的、復制的就算了,作者自己也發這么多地方,真的搞笑,百度一下,跳出來全是同一個,標題也是一樣,很迷~。 調用就很簡單了,可以直接使用 我們還是改一下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 ``` <?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方法中添加這個捕捉拋出就可以了。 ``` // 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); } ``` 下面就不寫了,太麻煩了,直接放全部代碼 ``` <?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\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 ``` <?php namespace 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真好用,不虧是世界上最好的語言 ———————————————— 版權聲明:本文為CSDN博主「goms」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。 原文鏈接:https://blog.csdn.net/weixin_45042868/article/details/117674575
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看