Cách tải hình ảnh lên google drive bằng gapi và react

Nov 08 2020

Tôi đang cố tải một hình ảnh lên google drive. Tôi đã làm theo hướng dẫn tải lên tệp này, tôi có thể tải lên một tệp đơn giản nhưng khi tôi cố gắng tải lên một hình ảnh, tôi nhận được một hình ảnh bị hỏng. Trên thực tế, tệp được tải lên nhưng hình ảnh tôi muốn xem bị hỏng. Tôi biết rằng vấn đề nằm ở nội dung của yêu cầu nhưng tôi không biết ở đâu vì đó là một cách lạ (ranh giới, dấu phân cách, v.v.). Mọi thứ đều hoạt động tốt ngoại trừ nội dung của hình ảnh, vì vậy bạn chỉ cần nhìn vào phương pháp đăng tải hình ảnh lên. Đây là mã của tôi:

import { Upload } from 'antd';
import React, { Component } from 'react';

var SCOPE = 'https://www.googleapis.com/auth/drive.file';
var discoveryUrl = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';


class App extends Component {
  state = {
    name: '',
    googleAuth: '',
    body: ''
  }
  componentDidMount(){
    var script = document.createElement('script');
    script.onload=this.handleClientLoad;
    script.src="https://apis.google.com/js/api.js";
    document.body.appendChild(script);
  }


  initClient = () => {
    try{
      window.gapi.client.init({
          'apiKey': "apikey",
          'clientId': "clientid",
          'scope': SCOPE,
          'discoveryDocs': [discoveryUrl]
        }).then(() => {
          this.setState({
            googleAuth: window.gapi.auth2.getAuthInstance()
          })
          this.state.googleAuth.isSignedIn.listen(this.updateSigninStatus);  
         document.getElementById('signin-btn').addEventListener('click', this.signInFunction);
         document.getElementById('signout-btn').addEventListener('click', this.signOutFunction);

      });
    }catch(e){
      console.log(e);
    }
  }


  signInFunction =()=>{
    console.log(this.state.googleAuth)
    this.state.googleAuth.signIn();
    console.log(this.state.googleAuth)
    this.updateSigninStatus()
  }

  signOutFunction =()=>{
    this.state.googleAuth.signOut();
    this.updateSigninStatus()
  }

  updateSigninStatus = ()=> {
    this.setSigninStatus();
  }

  setSigninStatus= async ()=>{
    console.log(this.state.googleAuth.currentUser.get())
    var user = this.state.googleAuth.currentUser.get();
    console.log(user)
    if (user.wc == null){
      this.setState({
        name: ''
      });
    }
    else{
      var isAuthorized = user.hasGrantedScopes(SCOPE);
      if(isAuthorized){
        console.log('USER')
        console.log(user)
        this.setState({
          name: user.vt.Ad
        });

        const boundary = '-------314159265358979323846';
        const delimiter = "\r\n--" + boundary + "\r\n";
        const close_delim = "\r\n--" + boundary + "--";
        var fileName='mychat123.png';
        var contentType='image/png'
        var metadata = {
          'name': fileName,
          'mimeType': contentType
        };

          var multipartRequestBody =
            delimiter +  'Content-Type: application/json\r\n\r\n' +
            JSON.stringify(metadata) +
            delimiter +
            'Content-Type: ' + contentType + '\r\n';

            multipartRequestBody +=  + '\r\n' + this.state.body;
            multipartRequestBody += close_delim;

          console.log(multipartRequestBody);
          var request = window.gapi.client.request({
            'path': 'https://www.googleapis.com/upload/drive/v3/files',
            'method': 'POST',
            'params': {'uploadType': 'multipart'},
            'headers': {
              'Content-Type': contentType
            },
            'body': multipartRequestBody
          });

        request.execute(function(file) {
          console.log(file)
        });
      }
    }
  }

  getBase64(file) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = () => resolve(reader.result);
      reader.onerror = error => reject(error);
    });
  }
  

  handleChange = async file => {
    let image

    if (file.currentTarget) {
      image = file.currentTarget.currentSrc;
      console.log(file.currentTarget)
    } else {
      if (!file.file.url && !file.file.preview) {
        file.file.preview = await this.getBase64(file.file.originFileObj);
      }
      image = file.file.preview;
      console.log(file.file)
      this.setState({
        body: file.file.preview
      });
    }

    console.log(image)
    
  }

  handleClientLoad = ()=>{
    window.gapi.load('client:auth2', this.initClient);
  }

  render() {
    return (
      <div className="App">
        <Upload 
          style={{width: '100%', height: '200px' }} 
          listType="picture-card" 
          onChange={this.handleChange} >
          <div>
            <div style={{ marginTop: 8 }}>Subir imagen</div>
          </div>
        </Upload>
        <div>UserName: <strong>{ this.state.name}</strong></div>
        <button id="signin-btn">Sign In</button>
        <button id="signout-btn">Sign Out</button>
      </div>
    );
  }
}

export default App;

Tại sao tệp của tôi bị hỏng? Nó nói rằng đây không phải là một tệp PNG. Cách chính xác để tải hình ảnh lên ổ đĩa là gì? Cảm ơn bạn!!

Trả lời

2 Tanaike Nov 08 2020 at 23:30

Điểm sửa đổi:

  • Từ tập lệnh của bạn, có vẻ như đó this.state.bodylà dữ liệu base64. Trong trường hợp này, bắt buộc phải thêm Content-Transfer-Encoding: base64vào tiêu đề dữ liệu trong phần thân yêu cầu.
    • Và hãy cẩn thận với các ngắt dòng của nội dung yêu cầu multipart/form-data.
  • Khi bạn muốn sử dụng uploadType=multipart, vui lòng đặt multipart/form-data; boundary=###tiêu đề làm loại nội dung. Trong kịch bản của bạn, có vẻ như loại nội dung image/png.

Khi các điểm trên được phản ánh vào tập lệnh của bạn, nó sẽ trở thành như sau.

Tập lệnh đã sửa đổi:

Từ:
const boundary = '-------314159265358979323846';
const delimiter = "\r\n--" + boundary + "\r\n";
const close_delim = "\r\n--" + boundary + "--";
var fileName='mychat123.png';
var contentType='image/png'
var metadata = {
  'name': fileName,
  'mimeType': contentType
};

  var multipartRequestBody =
    delimiter +  'Content-Type: application/json\r\n\r\n' +
    JSON.stringify(metadata) +
    delimiter +
    'Content-Type: ' + contentType + '\r\n';

    multipartRequestBody +=  + '\r\n' + this.state.body;
    multipartRequestBody += close_delim;

  console.log(multipartRequestBody);
  var request = window.gapi.client.request({
    'path': 'https://www.googleapis.com/upload/drive/v3/files',
    'method': 'POST',
    'params': {'uploadType': 'multipart'},
    'headers': {
      'Content-Type': contentType
    },
    'body': multipartRequestBody
  });
Đến:
const boundary = '-------314159265358979323846';
const delimiter = "--" + boundary + "\r\n";
const close_delim = "\r\n--" + boundary + "--";
var fileName = 'mychat123.png';
var contentType = 'image/png';
var metadata = {'name': fileName,'mimeType': contentType};
var multipartRequestBody = delimiter +
'Content-Type: application/json\r\n\r\n' +
JSON.stringify(metadata) + "\r\n" +
delimiter +
'Content-Type: ' + contentType + '\r\n' +
'Content-Transfer-Encoding: base64\r\n\r\n' +
this.state.body +
close_delim;
console.log(multipartRequestBody);
var request = window.gapi.client.request({
  'path': 'https://www.googleapis.com/upload/drive/v3/files',
  'method': 'POST',
  'params': {'uploadType': 'multipart'},
  'headers': {'Content-Type': 'multipart/form-data; boundary=' + boundary},
  'body': multipartRequestBody
});
  • Trong quá trình tải lên API Drive, ở giai đoạn hiện tại, có vẻ như cả hai multipart/form-datavà đều multipart/relatedcó thể được sử dụng.

Ghi chú:

  • Trong câu trả lời này, nó giả sử rằng mã thông báo truy cập của bạn có thể được sử dụng để tải tệp lên Google Drive. Hãy cẩn thận điều này.

Tài liệu tham khảo:

  • Tải lên dữ liệu tệp

Thêm:

Từ câu trả lời của bạn, tôi nhận thấy rằng bạn muốn sử dụng fetchthay vì gapi.client.request. Trong trường hợp này, kịch bản mẫu như sau.

Đây là kịch bản của bạn trong bình luận của bạn.

const fd = new FormData();
fd.append("file", this.state.body);
fd.append("title", 'test.png');
const options = {
  method: 'POST',
  headers: { Authorization: "Bearer" + " " + window.gapi.auth.getToken().access_token },
  body: fd
};
await fetch("googleapis.com/upload/drive/v3/files", options)
.then(response => response.json())
.then(jsonResp => { console.log(jsonResp) });

Điểm sửa đổi:

  • Tại API Drive v3, thuộc tính để đặt tên tệp là name.
  • uploadType=multipart được yêu cầu để được sử dụng trong điểm cuối.
  • Dữ liệu base64 bắt buộc phải được chuyển đổi thành blob.
    • Đối với điều này, tôi đã giới thiệu câu trả lời này .

Khi các điểm trên được phản ánh vào tập lệnh của bạn, nó sẽ trở thành như sau.

Kịch bản mẫu:

// Reference: https://stackoverflow.com/a/16245768
// This method converts from base64 data to blob.
const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, {type: contentType});
  return blob;
}

const metadata = {name: 'test.png'};
const fd = new FormData();
fd.append('metadata', new Blob([JSON.stringify(metadata)], {type: 'application/json'}));
fd.append('file', b64toBlob(this.state.body, "image/png"));
fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart', {
  method: 'POST',
  headers: {Authorization: 'Bearer ' + window.gapi.auth.getToken().access_token},
  body: fd
})
.then(response => response.json())
.then(jsonResp => { console.log(jsonResp) });
poeticGeek Nov 21 2020 at 17:39

Tôi đã tạo một tùy chỉnh bên trên react-uploady , được gọi là drive-uploady , giúp tải lên google drive trở nên đơn giản hơn với tất cả sức mạnh của Uploady: hooks, UI component, process, v.v.

ví dụ:

import React from "react";
import DriveUploady from "drive-uploady";
import UploadButton from "@rpldy/upload-button";

export const App = () => {

    return <DriveUploady        
            clientId="<my-client-id>"
            scope="https://www.googleapis.com/auth/drive.file"
           >                
            <UploadButton>Upload to Drive</UploadButton>
        </DriveUploady>;
};

Đó là tất cả những gì bạn cần để hiển thị nút tải lên đăng nhập người dùng vào Drive và tải lên.