Sử dụng Google Drive làm filesystem driver trong Laravel

157

Chào các bạn,

Chuyện là mình vừa “chuyển nhà” cho blog PhamBinh.net, chuyển từ VPS cung cấp bởi google sang VPS cung cấp bởi DigitalOcean. Lý do tại sao chuyển thì mình xin phép không chia sẻ trong bài viết này, nhưng nhờ dịp chuyển nhà mới này mình mới nhận ra rằng blog PhamBinh.net chưa có một phương thức backup dữ liệu nào hiệu quả cả.

Dữ liệu trên PhamBinh.net được chia làm 2 loại: một là dữ liệu lưu trong database mysql, hai là các file (như file ảnh) được upload lên trong lúc viết bài. Dữ liệu trong database thì mình có cài iTheme security để backup tự động hàng tuần, file backup được gửi vào mail. Nhưng các file upload thì có dung lượng lớn, nên không thể đính kèm vào mail được, mà phải có một “kho lưu trữ” riêng. Thời gian đầu thì cứ mỗi tuần, mình sẽ tự backup bằng tay rồi tải lên Google Drive để lưu trữ, nhưng chỉ làm được đâu đó vài lần thì bỏ do… lười.

Vì thế, lại tiện dịp chuyển sang VPS mới, nên mình cũng chịu chơi chế một tool tự động backup dữ liệu cho blog PhamBinh.net luôn. Cụ thể tool này được phát triển dựa trên Laravel framework, và cứ 12h đêm sẽ động export database, zip các file uploads và tải lên Google Drive.

Trong lúc chế tool này, mình phải tìm hiểu về cách sử dụng Google Drive làm filesystem driver cho Laravel. Nhận thấy việc tích hợp này được khá nhiều bạn quan tâm, nên mình đã viết bài để chia sẻ tới mọi người. Mời các bạn theo dõi.

I. Các bước thực hiện

Mình sẽ thực hiện trên Laravel 6xGoogle Drive API V3. Nhưng đối với Laravel 5x hay Laravel 7x thì cũng thực hiện tương tự.

Bước 1: Setup project laravel 6x và cài package cần thiết.

Chuẩn bị một project laravel 6x mới tinh

composer create-project --prefer-dist laravel/laravel blog "6.*"

Cài package nao-pon/flysystem-google-drive để có thư viện làm việc với Google Drive API.

composer require nao-pon/flysystem-google-drive:~1.1

Mình chỉ rõ phiên bản ~1.1 để làm việc với Google Drive API V3.

Bước 2: Cấu hình Laravel

Mở config/filesystems.php thêm thông tin cấu hình sau

// ...

'disks' => [
    //
    'google_drive' => [
        'driver' => 'google_drive',
        'clientId' => env('GOOGLE_DRIVE_CLIENT_ID'),
        'clientSecret' => env('GOOGLE_DRIVE_CLIENT_SECRET'),
        'refreshToken' => env('GOOGLE_DRIVE_REFRESH_TOKEN'),
        'folderId' => env('GOOGLE_DRIVE_FOLDER_ID'),
    ],
]

// ...

Mở .env thêm các thông tin cấu hình sau

GOOGLE_DRIVE_CLIENT_ID=
GOOGLE_DRIVE_CLIENT_SECRET=
GOOGLE_DRIVE_REFRESH_TOKEN=
GOOGLE_DRIVE_FOLDER_ID=

Mình sẽ hướng dẫn các bạn cách lấy giá trị của các biến env trên sau, trước tiên cứ cấu hình vào như vậy đã.

Bước 3: Custom Google Drive filesystem driver

Tạo file app/Providers/GoogleDriveServiceProvider.php với nội dung như sau

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Google_Client;
use Google_Service_Drive;
use League\Flysystem\Filesystem;
use Storage;
use Hypweb\Flysystem\GoogleDrive\GoogleDriveAdapter;

class GoogleDriveServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        Storage::extend('google_drive', function($app, $config) {
            $client = new Google_Client();
            $client->setClientId($config['clientId']);
            $client->setClientSecret($config['clientSecret']);
            $client->refreshToken($config['refreshToken']);
            $service = new Google_Service_Drive($client);

            $options = [];
            if (isset($config['teamDriveId'])) {
                $options['teamDriveId'] = $config['teamDriveId'];
            }

            $adapter = new GoogleDriveAdapter($service, $config['folderId'], $options);

            return new Filesystem($adapter);
        });
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Đoạn code trên giúp mình đăng ký với Laravel framework một filesystem driver tên là google_dive. Bạn có thể xem hướng dẫn về việc custom một filesystem driver cho laravel tại đây.

Mở config/app.php để đăng ký GoogleDriveServiceProvider vừa tạo như sau:

// ...

'providers' => [
    // ...

    App\Providers\GoogleDriveServiceProvider::class,

    //...
]

// ...

Bước 4: Lấy giá trị cho các biến env

Mình hướng dẫn các bạn cách lấy giá trị cho các biến env như đã nêu ở bước 2.

4.1 Lấy Client id và Client secret

Đăng nhập tài khoản Google của bạn, sau đó truy cập vào link sau: https://console.developers.google.com

Tại đây bạn tiến hành tạo một project theo chỉ dẫn như hình

Giao diện chụp ngày 07/05/2020

Sau khi nhấn New Project, một trang mới hiện ra, tại đây bạn điền các thông tin như hình dưới đây, sau đó nhấn Create để tạo project.

Giao diện chụp ngày 07/05/2020

Quá trình tạo project có thể diễn ra trong vài giây, bạn hãy kiên nhẫn chờ đợi. Sau khi tạo xong, hãy chọn Project vừa tạo như sau:

Giao diện chụp ngày 07/05/2020

Sau khi chọn project “My Storage”, bạn chọn sang tab Library và tìm kiếm “Google Drive” để tích hợp Google Drive API như hình bên dưới.

Giao diện chụp ngày 07/05/2020

Sau khi enable được Google Drive API, bạn chọn tiếp tab Credentials và nhấn vào nút Configure consent screen.

Giao diện chụp ngày 07/05/2020

Chọn External và nhấn Create

Giao diện chụp ngày 07/05/2020

Điền thông tin như hình dưới đây và nhấn Save

Giao diện chụp ngày 07/05/2020

Quay trở lại tab Credentials, nhấn vào nút “+ CREATE CREDENTIALS“, chọn OAuth client ID.

Giao diện chụp ngày 07/05/2020

Tiếp tục hoàn thiện các thông tin như hình dưới đây, sau đó nhấn Create.

Lư ý: Ở mục Authorized redirect URIs bạn phải điền là https://developers.google.com/oauthplayground (không có ký tự / ở cuối URL), thông tin này cực kỳ quan trọng ở bước tiếp theo.

Giao diện chụp ngày 07/05/2020

Sau khi hoàn thiện các bước trên, bạn sẽ nhận được Client IDClient secret như hình dưới đây.

Giao diện chụp ngày 07/05/2020

Bạn hãy sử dụng 2 thông tin này để cấu hình cho 2 biến env tương ứng là GOOGLE_DRIVE_CLIENT_IDGOOGLE_DRIVE_CLIENT_SECRET.

4.2 Lấy Refresh token

Đầu tiên, bạn hãy truy cập vào địa chỉ: https://developers.google.com/oauthplayground. Sau đó điền các thông tin như hình dưới đây.

Giao diện chụp ngày 07/05/2020

OAuth Client IDOAuth Client secre chính là hai thông tin mà bạn có ở bước trước. Sau đó nhấn vào Authorize APIs.

Google sẽ yêu cầu bạn chọn tài khoản để đăng nhập, và xác nhận một số quyền hạn nhất định, bạn cứ cho phép nhé.

Ở bước tiếp theo, bạn hãy chọn vào Auto-refresh the token before it expires, sau đó nhấn nút Exchange authorization code for tokens.

Giao diện chụp ngày 07/05/2020

Google sẽ chuyển bạn qua “Step 3”, nhưng bạn hãy nhấn quay trở lại “Step 2”, lúc này bạn sẽ nhìn thấy thông tin về Refresh token.

Giao diện chụp ngày 07/05/2020

Bạn hãy sử dụng giá trị này để thiết lập cho biến env GOOGLE_DRIVE_REFRESH_TOKEN.

4.3 Lấy Folder ID

Trên Google Drive, mình tạo ra thư mục có tên là My web storage, sau đó truy cập vào thư mục đó. Thì Folder ID chính là phần được khoanh đỏ như hình dưới đây.

Bạn hãy sử dụng thông tin này để thiết lập giá trị cho biến env GOOGLE_DRIVE_FOLDER_ID.

II. Cách sử dụng

2.1 Thao tác chung

Liệt kê nội dung bên trong folder

<?php

use Storage;

$googleDriveStorage = Storage::disk('google_drive');

// Đường dẫn tới thư mục muốn liệt kê nội dung
$dir = '/';
// Hoặc có thể liệt kê trong một sub-folders
// $dir = '/path-to-sub-folder'

// Có đọc nội dung bên trong các sub-folder của $dir hay không?
// Nên đặt là false để tránh phải liệt kê qua nhiều khi thư mục có nhiều file &amp; sub-folders
$recursive = false;

$contents = collect($googleDriveStorage->listContents($dir, $recursive));

dd($contents);

/*
Output

Illuminate\Support\Collection^ {#790
  #items: array:2 [
    0 => array:10 [
      "name" => "test.txt"
      "type" => "file"
      "path" => "1IRoI-_ploHPDHHCk8ap6EgDyoLTo0aBb"
      "filename" => "test"
      "extension" => "txt"
      "timestamp" => 1588994857
      "mimetype" => "text/plain"
      "size" => 12
      "dirname" => ""
      "basename" => "1IRoI-_ploHPDHHCk8ap6EgDyoLTo0aBb"
    ]
    1 => array:9 [
      "name" => "images"
      "type" => "dir"
      "path" => "1Y4pnI75f9d-KGT4tYRaLRMu9fxG4SBdB"
      "filename" => "images"
      "extension" => ""
      "timestamp" => 1588995638
      "size" => 0
      "dirname" => ""
      "basename" => "1Y4pnI75f9d-KGT4tYRaLRMu9fxG4SBdB"
    ]
  ]
}
*/

Lưu ý: Đường dẫn thư mục trên Google Drive đều được xác định thông qua thuộc tính ‘path’ (một string ngẫu nhiên và unique), chứ không xác định qua tên thư mục.

2.2 Thao tác với file

Tạo file trên google drive

<?php

use Storage;

$googleDriveStorage = Storage::disk('google_drive');
$googleDriveStorage->put('test.txt', 'Hello world');

// Hoặc tạo file trong một sub-folder
// $googleDriveStorage->put('path-to-sub-folder/test.txt', 'Hello world');

Đọc nội dung file từ google drive

<?php

use Storage;

$googleDriveStorage = Storage::disk('google_drive');

// Trước tiên cần lấy ra thông tin của file 'test.txt'
// trên google drive trước đã
$fileinfo = collect($googleDriveStorage->listContents('/', false))
    ->where('type', 'file')
    ->where('name', 'test.txt')
    ->first();

// Đọc nội dung file 'test.txt' mà mình đã tạo ở trên
$contents = $googleDriveStorage->get($fileinfo['path']);

dd($contents); // Hello world


Upload file từ local lên google drive

<?php

use Storage;

$googleDriveStorage = Storage::disk('google_drive');

// Về cơ bản, thao tác tải một file lên google drive
// cũng tương tự như việc tạo mới một file trên google drive
// Đầu tiên lấy ra nội dung của file đã.
$contents = file_get_contents('/path/to/file/on/local/test.txt');

// Tiếp theo thì tạo một file mới với nội dung vừa lấy được.
$googleDriveStorage->put('test.txt', $contents);

Download file từ google về local

<?php

use Storage;

$googleDriveStorage = Storage::disk('google_drive');

// Đầu tiên cần lấy ra thông tin của file 'test.txt'
// trên google drive trước đã
$fileinfo = collect($googleDriveStorage->listContents('/', false))
    ->where('type', 'file')
    ->where('name', 'test.txt')
    ->first();

// Đọc nội dung file 'test.txt' mà mình đã tạo ở trên
$contents = $googleDriveStorage->get($fileinfo['path']);

return response($contents)
    ->header('Content-Type', $fileinfo['mimetype'])
    ->header('Content-Disposition', "attachment; filename='" . $fileinfo['name']. "'");

Xóa file trên google drive

<?php

use Storage;

$googleDriveStorage = Storage::disk('google_drive');

// Đầu tiên cần lấy ra thông tin của file 'test.txt'
// trên google drive trước đã
$fileinfo = collect($googleDriveStorage->listContents('/', false))
    ->where('type', 'file')
    ->where('name', 'test.txt')
    ->first();

$googleDriveStorage->delete($fileinfo['path']);

2.3 Thao tác với folder

Tạo folder trên google drive

<?php

use Storage;

$googleDriveStorage = Storage::disk('google_drive');

$googleDriveStorage->makeDirectory('avatars');

// Hoặc tạo trong một sub-folder
// $googleDriveStorage->makeDirectory('/path-to-sub-folder/avatars');

Xóa folder trên google drive

<?php

use Storage;

$googleDriveStorage = Storage::disk('google_drive');

// Đầu tiên cần lấy ra thông tin của folder 'avatars'
// trên google drive trước đã
$folderinfo = collect($googleDriveStorage->listContents('/', false))
    ->where('type', 'dir')
    ->where('name', 'avatars')
    ->first();

$googleDriveStorage->deleteDirectory($folderinfo['path']);

Đổi tên folder trên google drive

<?php

use Storage;

$googleDriveStorage = Storage::disk('google_drive');

// Đầu tiên cần lấy ra thông tin của folder 'avatars'
// trên google drive trước đã
$folderinfo = collect($googleDriveStorage->listContents('/', false))
    ->where('type', 'dir')
    ->where('name', 'avatars')
    ->first();

// Đổi tên từ 'avatars' thành 'images'
$googleDriveStorage->move($folderinfo['path'], 'images');

2.4 Một số lưu ý về cách sử dụng

  • Google Drive cho phép bạn tạo các file hoặc folder có tên giống nhau, thế nên khi muốn tìm một file hay folder, các bạn phải chỉ rõ thuộc tính typename, sau đó chỉ lấy kết quả đầu tiên.
  • Google xác định đường dẫn tới file hoặc folder thông qua thuộc tính path, thuộc tính này chỉ có thể lấy được khi bạn đọc toàn bộ nội dung của folder. Vì vậy bạn chỉ nên lưu trữ một số lượng file nhất định trong một folder thôi, đừng lưu trữ quá nhiều kẻo lúc đọc sẽ tốn nhiều thời gian.
  • Vì là Google Drive, nên các thao tác trên diễn ra nhanh hay chậm còn phụ thuộc vào tốc độ internet của bạn.

Bạn có thể cấu hình biến env FILESYSTEM_CLOUD=google_drive.

<?php

use Storage;

// Nếu cấu hình biến env
// FILESYSTEM_CLOUD=google_drive
// Thì thay vì dùng thế này
// $googleDriveStorage = Storage::disk('google_drive');
// Bạn có thể dùng thế này

$googleDriveStorage = Storage::cloud();