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.
Mục lục
I. Các bước thực hiện
Mình sẽ thực hiện trên Laravel 6x và Google 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

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.

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:

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.

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.

Chọn External và nhấn Create

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

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

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.

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

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_ID và GOOGLE_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.

OAuth Client ID và OAuth 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.

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.

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 & 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
type
vàname
, 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();