+4

Laravel + Google API: Blog ảnh dùng Google Drive làm nơi lưu trữ

Có một số bạn khi bắt đầu học làm web thường sẽ thích làm ra một trang web kinh khủng khiếp để cho nhiều người sử dụng (nhưng thục tế là làm giữa chừng thì bỏ hoặc làm xong chả ai dùng =)) )

Thật ra thì có nhiều lý do khiến bạn phải bỏ dở giữa chừng. Ví dụ như là do mất hết hứng làm lúc đầu, do không còn thời gian rảnh mà phải tập trung vào công việc khác, .etc..

Giả sử mà có làm xong đi nữa thì lại còn các vấn đề khác, nói đơn giản như là không thể deoloy để sử dụng vì không tìm được host phù hợp, không có khả năng duy trì host hoặc host phải trả chi phí quá cao cho dung lượng lưu trữ (với các bạn nào mà có ý định upload ảnh, video, file linh tinh lên thẳng host đó), bla bla

Nếu gặp trường hợp này thì bạn nên có một giải pháp thay thế, nói thẳng ra thì là bạn nên nghĩ đến việc dùng Google Drive như là một nơi lưu trữ miễn phí có dung lượng tương đối ổn với một web page tự làm "cho vui" 😄

Câu hỏi là làm thế nào để quản lý các file và stream chúng về với trang web của mình?

Bài viết này sẽ (phần nào) hướng dẫn các bạn làm công việc đó, nếu bạn có ý tưởng xịn hơn đừng quên comment ở phía dưới cho tác giả và mọi người cùng biết. Còn nếu bạn thích bài viết này, hay đơn giản là đồng cảm với việc làm một trang web rất là tâm huyết xong chả ai dùng thì hãy UP VOTE cho mình nhé T_T

Chuẩn bị

Vẫn như bài viết trước, thứ đầu tiên các bạn cần chuẩn bị là một bộ project "Goravel" như của mình:

Bắt đầu

Đầu tiên các bạn cần sửa lại quyền truy cập dữ liệu trong app/Console/Commands/GoogleGetToken.php

private function getClient()
{
    $client = new \Google_Client(); // khởi tạo Google Client
    $client->setApplicationName('Demo Laravel'); // đặt tên cho ứng dụng (không quan trọng lắm)

    // cài đặt xin quyền truy cập của token, ở đây tôi đang xin truy cập đến dữ liệu Calendar và Drive
    $client->setScopes([
        \Google_Service_Calendar::CALENDAR,
        \Google_Service_Drive::DRIVE,
        // thêm 2 dòng này vào
        \Google_Service_Drive::DRIVE_FILE,
        \Google_Service_Drive::DRIVE_METADATA,
    ]);
    $client->setAuthConfig(config('google-api.client_path')); // đường dẫn đến file credentials.json
    $client->setAccessType('offline'); // không rõ lắm nên cứ để vậy đi :">

    return $client;
}

Rồi sau đó chạy lệnh

php artisan google:get-token

để xin cấp lại token mới với những quyền mới vừa được thêm ở trên.

Nếu bạn bối rối quên mất cách lấy token thế nào thì bạn có thể đọc lại ở đây

Controller

Hãy khởi động nhẹ nhàng với một controller đơn giản thế này, chúng ta sẽ dần hoàn thiện nó ở bên dưới.

DriveController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Components\GoogleClient;
use Google_Service_Drive;

class BlogController extends Controller
{
    protected $client;

    public function __construct(GoogleClient $client)
    {
        $this->client = $client->getClient();
    }

    public function index()
    {
        return view('blog');
    }
}

View

Phần này vì mình lười nên sẽ tạo ra một view rất đơn giản, có một section để đăng các post mới và phần bên dưới là hiện ra tất cả các posts đã được tạo. Trông như thế này chẳng hạn:

Migration

Lần này chúng ta sẽ cần lưu lại các bài posts nên sẽ phải động đến CSDL rồi, đây là cấu trúc migration cho bảng Post của mình:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->text('content');
            $table->string('image')->nullable();
            $table->integer('user_id');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

Tạm thời chúng ta chưa xử lý đến ảnh, nên trường image cứ để null đã.

Model

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $fillable = [
        'content',
        'image',
    ];
}

Upload File

Tạm thời chưa quan tâm đến việc upload ảnh lên Drive, chúng ta cứ thực hiện lưu bài Post trước đã rồi tính

public function store(Request $request)
{
    Post::create($request->all());
}

Ez game 😄

Rồi giờ mới là đến phần chính, đó là xử lý upload ảnh lên Drive và stream ảnh đó về trang web của bạn và hiển thị ra View Mình thêm một private function có tên getImageID để làm nhiệm vụ upload sau đó trả về ID của ảnh vừa upload. Sau đó sửa lại store một chút để chúng ta có thể dễ dàng nhìn ra ảnh của mình đã được upload thành công hay chưa bằng cách để hiện luôn ra ID của file, còn công đoạn lưu vào DB tạm thời để sau.

public function store(Request $request)
{
    if ($request->hasFile('raw_image')) {
        $imageID = $this->getImageID($request->file('raw_image'));
        echo "File ID = ".$imageID;
        exit;
    }
    Post::create($request->all());
}

private function getImageID($image)
{
    $driveService = new Google_Service_Drive($this->client);

    try {
        $fileMetadata = new \Google_Service_Drive_DriveFile([
            'name' => time().'.'.$image->getClientOriginalExtension(),
        ]);
        $file = $driveService->files->create($fileMetadata, [
            'data' => file_get_contents($image->getRealPath()),
            'uploadType' => 'multipart',
            'fields' => 'id',
        ]);
        return $file->id;
    } catch (\Exception $e) {
        //
    }
}

Thử phát này: Hiện ra ID như vậy tức là đã upload thành công! Không tin bạn có thể vào trực tiếp Drive của mình để kiểm tra nhé 😄

Sau khi đã thử upload thành công, bạn lưu lại ID này vào field image trong bảng post nhé

Post::create(array_merge($request->all(), [
    'image' => $imageID
]));

Cơ bản vậy là xong phần upload, nhưng chưa phải xong hết đâu. Chúng ta cần đến với một công đoạn vô cùng quan trọng nữa nếu muốn stream ảnh về. Đó là phân quyền.

Mặc định mọi file bạn upload vào Drive thông qua API sẽ được set quyền chỉ owner, nếu bạn có stream ra ngoài thì người khác cũng không thể xem được. Vậy nên bạn cần cài đặt lại quyền hạn sử dụng cho file ảnh đó, và tất nhiên là dùng API để hệ thống thực hiện một các tự động, chứ ai lại chui vào Drive rồi tự set thì nói chuyện gì nữa nhở 😄

Phân quyền File

Các chế độ phân quyền:

  • user
  • group
  • domain
  • anyone Với 3 chế độ đầu tiên, chúng ta sẽ bắt buộc phải định nghĩa thêm một option đặc biệt để biết user | group | domain nào được quyền xem file. Mà với một trang blog có nhiều người dùng, điều này là không thể xác định cụ thể được, vậy nên ta sẽ chọn chế độ anyone tức là bất kỳ ai cũng có thể xem được ảnh này.

Sửa lại getImageID như sau:

private function getImageID($image)
{
    $driveService = new Google_Service_Drive($this->client);

    try {
        $fileMetadata = new \Google_Service_Drive_DriveFile([
            'name' => time().'.'.$image->getClientOriginalExtension(),
        ]);
        $file = $driveService->files->create($fileMetadata, [
            'data' => file_get_contents($image->getRealPath()),
            'uploadType' => 'multipart',
            'fields' => 'id',
        ]);
        // bắt đầu phân quyền
        $driveService->getClient()->setUseBatch(true);

        try {
            $batch = $driveService->createBatch();
            $userPermission = new \Google_Service_Drive_Permission([
                //dành cho mọi người
                'type' => 'anyone', // user | group | domain | anyone
                // và chỉ được quyền xem
                'role' => 'reader', // organizer | owner | writer | commenter | reader
            ]);
            $request = $driveService->permissions->create($file->id, $userPermission, ['fields' => 'id']);
            $batch->add($request, 'user');
            $results = $batch->execute();
            //test xong nhớ xóa dòng dưới này đi
            dd($results);
        } catch (\Exception $e) {

        } finally {
            $driveService->getClient()->setUseBatch(false);
        }
        
        return $file->id;
    } catch (\Exception $e) {
        //
    }
}

Thử lại nào Nếu thông báo trả về không phải là Exception và hiện như hỉnh trên thì tức là bạn đã cấp quyền thành công. Muốn chắc chắn bạn có thể vào Drive kiểm tra lại file đó một lần nữa 😄

Vậy là chúng ta đã hoàn thành công đoạn chính rồi, giờ chỉ cần show ảnh ra ngoài view là xong. Nhớ xóa bỏ dòng dd() trước nhé.

Hiện ảnh ra View

Quay lại Controller một chút, bạn cần thêm một constant hoặc đặt nó trong config tùy ý thích của bạn như sau:

const DRIVE_CONFIG_URL = ' https://docs.google.com/uc?id=';

Đó là đường dẫn dùng để stream ảnh từ Google. Và trong function index các bạn sửa lại như sau để lấy dữ liệu ra View nhé:

public function index()
{
    $data['posts'] = Post::all()->map(function($item) {
        return [
            'image' => self::DRIVE_CONFIG_URL.$item->image,
            'content' => $item->content
        ];
    });

    return view('blog', $data);
}

Cuối cùng là ở View:

@foreach($posts as $post)
<div class="item card p-0 mb-5">
    <div class="card-body p-2">
        <h5 class="card-title m-1">{{ $post['content'] }}</h5>
    </div>
    <img class="card-img-bottom" src="{{ $post['image'] }}">
</div>
@endforeach

Phần này thì các bạn cứ style tùy theo ý thích. Và cuối cùng là...

Kết quả

Vậy đó 😄

Tuy nhiên các bạn có thể thấy là tại sao ảnh không hiện ngay, mà phải mất một khoảng thời gian mới hiện ra, trông như một lỗi khó chịu nào ấy, điều này là do ảnh có dung lượng lớn và phải được tải thông qua server của Google, nên khi ảnh tải về trình duyệt xong mới có thể hiện ra được. Các bạn bắt buộc phải chấp nhận đánh đổi thôi chứ không có cách nào khác đâu, có thể host của các bạn ngon lành hơn thì quá trình tải này sẽ nhanh hơn 😄

Dù sao thì đây cũng chỉ là một ví dụ về cách sử dụng các API của Google Drive.

Mong là các bạn sẽ thấy có ích và cám ơn đã theo dõi bài viết! 😄


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí