網站開發時,未採前後端分離時,都是自己做前後端 API,也沒啥文件管理,當工程師離職後,接手的人得必須重新看原始程式碼去理解之前工程師的邏輯,無形浪費接手的工程師很多時間,如果有良好的文件管理,接手的工程師就可以很快速的理解該 API 的用途。

安裝 DarkaOnLine/L5-Swagger 套件

# 根據 Laravel 版本安裝
composer require "darkaonline/l5-swagger:6.*"

產生設定檔

php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"

執行完上述步驟後就可以在瀏覽器輸入 http://localhost/api/documentation 可以看到套件已經安裝起來,但是出現錯誤訊息,因為還沒做好所有的定義。

修改 .env

# 設定 API Documentation Server 網址
L5_SWAGGER_CONST_HOST=https://localhost
# 自動更新文件
L5_SWAGGER_GENERATE_ALWAYS=true

在 Controller 中建立描述檔

app\Http\Controller\Controller.php 這邊主要放置 API Documentation 的 Info 、 Server 及 SecurityScheme,這樣就不用在每一個 Controller 都寫這段。

<?php
/**
 *  @OA\Info(
 *      version="1.0.0",
 *      title="Codinglab API Documentation",
 *      description="My API Documentation description",
 *      @OA\Contact(
 *          email="admin@mail.com"
 *      ),
 *      @OA\License(
 *          name="Apache 2.0",
 *          url="http://www.apache.org/licenses/LICENSE-2.0.html"
 *      )
 *  )
 *
 *  @OA\Server(
 *      url=L5_SWAGGER_CONST_HOST,
 *      description="API Server"
 *  )
 *
 *  @OA\SecurityScheme(
 *     type="http",
 *     description="Login with email and password to get the authentication token",
 *     name="Token based Based",
 *     in="header",
 *     scheme="bearer",
 *     bearerFormat="JWT",
 *     securityScheme="apiAuth",
 *  )
 */

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

修改 API\UserLoginController.php

<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Validator;
use App\User as UserEloquent;

use Illuminate\Support\Facades\Auth;

/**
 *  @OA\Tag(
 *      name="USER",
 *      description="OPERATIONS ABOUT USER"
 *  )
 */
class UserLoginController extends Controller
{
    /**
     * Create a new AuthController instance.
     *
     * @return void
     */
    public function __construct()
    {
        //除了 login 其餘 function 都要經過 api 的 middleware 檢查
        $this->middleware('api', ['except' => ['login']]);
    }

    /**
     * Get a JWT via given credentials.
     *
     * @return \Illuminate\Http\JsonResponse
     */
     /**
     *  @OA\Post(
     *      path="/api/login",
     *      summary="User Login",
     *      tags={"USER"},
     *      operationId="login",
     *
     *      @OA\Parameter(
     *          name="email",
     *          in="query",
     *          required=true,
     *          @OA\Schema(
     *               type="string"
     *          )
     *      ),
     *      @OA\Parameter(
     *          name="password",
     *          in="query",
     *          required=true,
     *          @OA\Schema(
     *              type="string"
     *          )
     *      ),
     *      @OA\Response(
     *          response=200,
     *           description="Success",
     *          @OA\MediaType(
     *               mediaType="application/json",
     *          )
     *      ),
     *      @OA\Response(
     *          response=401,
     *          description="Unauthenticated"
     *      ),
     *      @OA\Response(
     *         response=400,
     *         description="Bad Request"
     *      ),
     *  )
     **/
    public function login()
    {
        $credentials = request(['email', 'password']);

        //驗證
        $validator = Validator::make($credentials, [
            'email'   => 'required|email',
            'password' => 'required|min:4',
        ]);

        //驗證失敗返回訊息
        if ($validator->fails()) {
            return response()->json(['error' => '帳號密碼錯誤'], 400);
        }

        //透過Auth取得JWT token
        if (! $token = auth('api')->attempt($credentials)) {
            return response()->json(['error' => '授權失敗'], 401);
        }

        //將token拋出
        return $this->respondWithToken($token);
    }

    /**
     * Get the authenticated User.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    /**
     *  @OA\POST(
     *      path="/api/me",
     *      summary="GET Curent User Information",
     *      tags={"USER"},
     *      operationId="users",
     *      security={{ "apiAuth": {} }},
     *      @OA\Response(
     *          response=200,
     *          description="Success"
     *      ),
     *  )
     */
    public function me()
    {
        return response()->json(auth('api')->user());
    }

    /**
     * Log the user out (Invalidate the token).
     *
     * @return \Illuminate\Http\JsonResponse
     */
    /**
     *  @OA\POST(
     *      path="/api/logout",
     *      summary="Log the user out (Invalidate the token).",
     *      tags={"USER"},
     *      operationId="users",
     *      security={{ "apiAuth": {} }},
     *      @OA\Response(
     *          response=200,
     *          description="Success"
     *      ),
     *      @OA\Response(
     *          response=401,
     *          description="Unauthenticated"
     *      ),
     *  )
     */
    public function logout()
    {
        //找出登入者資料
        $user = auth('api')->user();

        //JWT RAW data
        // $payload = auth('api')->payload();

        //重新更新token
        // $newtoken = auth('api')->refresh();
        // return $this->respondWithToken($newtoken);

        //抓取是否有返回token
        // $token = request(['token']);

        //如果沒使用者存在則給出錯誤訊息
        if (!$user) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }
        //登出並清除token
        auth('api')->logout();
        return response()->json(['message' => 'Successfully logged out'], 200);
    }

    /**
     * Refresh a token.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function refresh()
    {
        return $this->respondWithToken(auth()->refresh());
    }

    /**
     * Get the token array structure.
     *
     * @param  string $token
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function respondWithToken($token)
    {
        return response()->json([
            'user' => auth('api')->user(), //將使用者資料一起帶出
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => auth('api')->factory()->getTTL() * 60
        ]);
    }

    /**
     *  @OA\Get(
     *      path="/api/users",
     *      tags={"USER"},
     *      summary="GET LIST OF USERS",
     *      operationId="users",
     *      @OA\Response(
     *          response=200,
     *          description="Success"
     *      )
     *  )
     */
    public function users()
    {
        $users = UserEloquent::all();
        return response()->json($users, 200);
    }
}

每次修改完註解必須執行產生器重新產生文件,如果覺得麻煩,可以直接在 .env 中設定 L5_SWAGGER_GENERATE_ALWAYS=true,這樣只要在瀏覽文件資料時 https://localhost/api/documentation 就會自動產生相關的文件。

php artisan l5-swagger:generate

產生出來的文件 api-docs.json

{
    "openapi": "3.0.0",
    "info": {
        "title": "API Documentation",
        "description": "My API Documentation description",
        "contact": {
            "email": "admin@mail.com"
        },
        "license": {
            "name": "Apache 2.0",
            "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
        },
        "version": "1.0.0"
    },
    "servers": [
        {
            "url": "https://localhost",
            "description": "API Server"
        }
    ],
    "paths": {
        "/api/login": {
            "post": {
                "tags": [
                    "USER"
                ],
                "summary": "User Login",
                "operationId": "login",
                "parameters": [
                    {
                        "name": "email",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "password",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {}
                        }
                    },
                    "401": {
                        "description": "Unauthenticated"
                    },
                    "400": {
                        "description": "Bad Request"
                    }
                }
            }
        },
        "/api/me": {
            "post": {
                "tags": [
                    "USER"
                ],
                "summary": "GET Curent User Information",
                "operationId": "users",
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                },
                "security": [
                    {
                        "apiAuth": []
                    }
                ]
            }
        },
        "/api/logout": {
            "post": {
                "tags": [
                    "USER"
                ],
                "summary": "Log the user out (Invalidate the token).",
                "operationId": "users",
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "description": "Unauthenticated"
                    }
                },
                "security": [
                    {
                        "apiAuth": []
                    }
                ]
            }
        },
        "/api/users": {
            "get": {
                "tags": [
                    "USER"
                ],
                "summary": "GET LIST OF USERS",
                "operationId": "users",
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "apiAuth": {
                "type": "http",
                "description": "Login with email and password to get the authentication token",
                "name": "Token based Based",
                "in": "header",
                "bearerFormat": "JWT",
                "scheme": "bearer"
            }
        }
    },
    "tags": [
        {
            "name": "USER",
            "description": "OPERATIONS ABOUT USER"
        }
    ]
}

可以直接在 API Documentation 中執行,如同 Postman 一樣,讓前端或介接的工程師直接做測試,相當方便。

參考資料:
About Swagger Specification | Documentation | Swagger
OpenAPI-Specification/3.0.2.md at master · OAI/OpenAPI-Specification (github.com)

最後修改日期: 2020 年 11 月 30 日