首页 > 解决方案 > 谷歌选择器 + 谷歌云端硬盘 API

问题描述

作为Project Strobe的一部分,Google 计划将许多 Google Drive API 范围移动到“受限”,这意味着您需要独立的安全验证。

在同一篇文章中,他们建议使用 Google Picker 和 drive.file 范围来获取每个文件的访问权限。我已经成功实现了 Google Picker API 来访问文件并检索文件的元数据。但是,我如何通过此流程实际导入文件的内容(例如,实际 Google 工作表中的数据)?

谷歌选择器包装

import React from "react";
import PropTypes from "prop-types";
import loadScript from "load-script";

const GOOGLE_SDK_URL = "https://apis.google.com/js/api.js";

let scriptLoadingStarted = false;

class GoogleFilePicker extends React.Component {
  static propTypes = {
    children: PropTypes.node,
    clientId: PropTypes.string.isRequired,
    developerKey: PropTypes.string,
    scope: PropTypes.array,
    viewId: PropTypes.string,
    authImmediate: PropTypes.bool,
    origin: PropTypes.string,
    onChange: PropTypes.func,
    onAuthenticate: PropTypes.func,
    onAuthFailed: PropTypes.func,
    createPicker: PropTypes.func,
    multiselect: PropTypes.bool,
    navHidden: PropTypes.bool,
    disabled: PropTypes.bool,
    authToken: PropTypes.string,
  };

  static defaultProps = {
    onChange: () => {},
    onAuthenticate: () => {},
    onAuthFailed: () => {},
    scope: ["https://www.googleapis.com/auth/drive.file"],
    viewId: "DOCS",
    authImmediate: false,
    multiselect: false,
    navHidden: false,
    disabled: false,
    authToken: "",
  };

  constructor(props) {
    super(props);

    this.onApiLoad = this.onApiLoad.bind(this);
    this.onChoose = this.onChoose.bind(this);
  }

  componentDidMount() {
    if (this.isGoogleReady()) {
      // google api is already exists
      // init immediately
      this.onApiLoad();
    } else if (!scriptLoadingStarted) {
      // load google api and the init
      scriptLoadingStarted = true;
      loadScript(GOOGLE_SDK_URL, this.onApiLoad);
    } else {
      // is loading
    }
  }

  isGoogleReady() {
    return !!window.gapi;
  }

  isGoogleAuthReady() {
    return !!window.gapi.auth;
  }

  isGooglePickerReady() {
    return !!window.google.picker;
  }

  onApiLoad() {
    window.gapi.load("auth");
    window.gapi.load("picker");
  }

  doAuth(callback) {
    window.gapi.auth.authorize(
      {
        client_id: this.props.clientId,
        scope: this.props.scope,
        immediate: this.props.authImmediate,
      },
      callback
    );
  }

  onChoose() {
    if (
      !this.isGoogleReady() ||
      !this.isGoogleAuthReady() ||
      !this.isGooglePickerReady() ||
      this.props.disabled
    ) {
      return null;
    }

    // const token = window.gapi.auth.getToken();
    // const oauthToken = token && token.access_token;
    const oauthToken = this.props.authToken;

    if (oauthToken) {
      this.createPicker(oauthToken);
    } else {
      this.doAuth((response) => {
        if (response.access_token) {
          this.createPicker(response.access_token);
        } else {
          this.props.onAuthFailed(response);
        }
      });
    }
  }

  createPicker(oauthToken) {
    // this.props.onAuthenticate(oauthToken);

    if (this.props.createPicker) {
      return this.props.createPicker(window.google, oauthToken);
    }

    const googleViewId = window.google.picker.ViewId[this.props.viewId];
    const view = new window.google.picker.View(googleViewId);

    if (this.props.mimeTypes) {
      view.setMimeTypes(this.props.mimeTypes.join(","));
    }
    if (this.props.query) {
      view.setQuery(this.props.query);
    }

    if (!view) {
      throw new Error("Can't find view by viewId");
    }

    const picker = new window.google.picker.PickerBuilder()
      .addView(view)
      .setOAuthToken(oauthToken)
      .setDeveloperKey(this.props.developerKey)
      .setCallback(this.props.onChange);

    if (this.props.origin) {
      picker.setOrigin(this.props.origin);
    }

    if (this.props.navHidden) {
      picker.enableFeature(window.google.picker.Feature.NAV_HIDDEN);
    }

    if (this.props.multiselect) {
      picker.enableFeature(window.google.picker.Feature.MULTISELECT_ENABLED);
    }

    picker.build().setVisible(true);
  }

  render() {
    console.log(window.gapi);
    return (
      <div onClick={this.onChoose}>
        {this.props.children ? (
          this.props.children
        ) : (
          <button>Open google chooser</button>
        )}
      </div>
    );
  }
}

export default GoogleFilePicker;

调用包装器

我在下面的代码中调用上面的组件。检索到文档后,我将调用 copyFile 函数。当应用程序创建文档时,此功能按预期工作,但当应用程序外部的用户创建文件时,此功能无法正常工作。我知道 Google 打算将其用于 drive.google.file 范围,但文档暗示使用 Google Picker 应该可以解决此问题。

  <GoogleFilePicker
    clientId="CLIENT ID"
    developerKey="DEVELOPER KEY"
    scope={["https://www.googleapis.com/auth/drive.file"]}
    onChange={(data) => {
      if (data.docs) {
        copyFile(data.docs[0].id, `Template: ${data.docs[0].name}`, "");
      }
    }}
    onAuthenticate={(token) => console.log("oauth token:", token)}
    onAuthFailed={(data) => console.log("on auth failed:", data)}
    multiselect={true}
    navHidden={true}
    authImmediate={false}
    // mimeTypes={["image/png", "image/jpeg", "image/jpg"]}
    viewId={"DOCS"}
    authToken={auth.state.user.googleToken}
  >

标签: google-drive-apigoogle-sheets-apigoogle-picker

解决方案


我最近遇到了这个问题,我需要从谷歌云控制台获取应用程序 ID,并将其添加到带有.setAppId(ID_HERE). 这设置了它,以便 Google 可以设置应用程序和文件链接,然后当您使用该应用程序进行 API 调用时,它会知道它可以访问该文件。您应该能够在云控制台的同一项目中进行任何 API 请求设置;对我来说,我在同一个项目中启用了 Google Drive、Docs 和 Picker API。我使用选择器允许用户授予对模板文件的应用程序的访问权限,然后应用程序可以将该模板复制到用户驱动器,然后使用 Doc API 将他们的数据注入其中。

您可能还需要设置一个回调函数以在用户进行选择/取消选择器后执行。例如,我是这样实现的:

  var self = this;
  var filePickerCallback = (data) => {
    if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {
      var doc = data[google.picker.Response.DOCUMENTS][0];
      if (doc["id"] === getTemplateToFindInPicker()){
        self.startGoogleDocumentBuild();
      } else {
        self.showDocsDialog_(ERROR, WRONG FILE SELECTED);
      }
    }
    if(data[google.picker.Response.ACTION] == google.picker.Action.CANCEL){
      self.showPickerFlowErrorDialog();
    }
  }
  filePickerCallback.bind(this);

  let token =  getPickerAccessToken();
  var isMCAV = CODE_TO_CHECK_MCAVNESS;
  let name = NAME_BASED_ON_CONTEXT;

  // let's us choose the query string, what to search based on file name, so its very clear what the user needs to choose
  let view = new google.picker.View(google.picker.ViewId.DOCUMENTS).setQuery(name);
  var picker = new google.picker.PickerBuilder()
    .addView(view)
    .setTitle("Please choose: " + name)
    .setAppId(getAppIDs())
    .setOAuthToken(token)
    .setDeveloperKey(getDeveloperKey())
    .setCallback(filePickerCallback)
    .build();
  picker.setVisible(true);

推荐阅读