開発情報・ナレッジ

投稿者: ShiningStar株式会社 2024年8月26日 (月)

登録フォームにてアップロードされたファイルをGoogleドライブへコピーするサンプルプログラム

SPIRALの登録フォームからアップロードされたファイルをAPIを用いてダウンロードし、
Googleドライブへアップロードするサンプルプログラムをご紹介いたします。


全体像

SPIRALのAPIを用いてDB内のファイルをダウンロードし、
そのファイルをGoogleドライブのAPIを用いてGoogleドライブへアップロードします。

GoogleDriveへファイルをアップロードするにはGoogleAPIのアクセストークンが必要なので取得します。

SPIRAL側の設定の全体としては通常通りファイルフィールドを持ったDBに、
登録フォームブロック、ページを用意しそこからファイルのアップロードをします。
その上で当該DBにフォームから新規登録が行われた際に非同期トリガが稼働する様にして、
非同期トリガのPHPにてファイルをGoogleドライブへアップロードする処理を記載します。

DB設定

ファイルフィールドを持つDBであれば利用可能です。
そのDBのファイルフィールドのIDとフィールド識別名を控えてください。
本サンプルはファイルフィールドに複数ファイルを登録した場合でも稼働いたします。

設定方法

Google側の設定(OAuth認証の作成)
Google側に任意のプロジェクトを作成していください。
その後Google側設定画面へアクセスしてください。
認証情報より「+認証情報を作成」をクリックして「OAuthクライアントID」をクリックしてください。
アプリケーションの種類は「ウェブアプリケーション」
承認済みのリダイレクトURIには「http://localhost/oauth2callback.php」と入力してください。
そのまま作成まで進み、モーダルに「クライアントID」と「クライアントシークレット」が表示されますので、必ず控えてください。

次に「OAuth同意画面」をクリックしてください。
「OAuth consent screen」では任意の情報を入力してください。
「スコープ」では何も入力しなくて大丈夫です。
「Test users」ではテストユーザにご自身のログインされているGoogleアカウント(Gmail)を入力してください。

ファイルをアップロードをするにはアクセストークンが必要です。
しかし、アクセストークンには有効期限等があるので継続的にファイルのアップロードが出来るように、
リフレッシュトークンを生成して、リフレッシュトークンからアクセストークンを都度生成する形にします。
設定値ファイルをPHPモジュールに作成

<?php
$client_id = 'クライアントIDを入力';
$client_secret = 'クライアントシークレットを入力';
$redirect_uri = 'http://localhost/oauth2callback.php'; 
            

モジュール名をconfig.phpとしてSPIRAL管理画面>アプリ管理>サイドバー>PHPモジュールより作成して上記を貼り付けてください。
ステップ1 リフレッシュトークンの取得

<?php
require_once "config.php";

$auth_code = ''; // 認証コードをここに入力

if (empty($auth_code)) {
    // 認証コードが入力されていない場合、認証URLを生成
    $auth_url = 'https://accounts.google.com/o/oauth2/auth?' . http_build_query([
        'client_id' => $client_id,
        'redirect_uri' => $redirect_uri,
        'response_type' => 'code',
        'scope' => 'https://www.googleapis.com/auth/drive',
        'access_type' => 'offline',
    ]);

    echo '認証URLをブラウザで開いて、認証コードを取得してください:';
    echo PHP_EOL . PHP_EOL;
    echo $auth_url . PHP_EOL;
} else {
    // 認証コードが入力されている場合、アクセストークンとリフレッシュトークンを取得
    $token_url = 'https://oauth2.googleapis.com/token';
    $post_fields = [
        'code' => $auth_code,
        'client_id' => $client_id,
        'client_secret' => $client_secret,
        'redirect_uri' => $redirect_uri,
        'grant_type' => 'authorization_code',
    ];

    $ch = curl_init($token_url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields));

    $response = curl_exec($ch);

    if ($response === false) {
        echo 'Error: ' . curl_error($ch);
        curl_close($ch);
        exit();
    }

    curl_close($ch);

    $token_data = json_decode($response, true);
    var_dump($token_data);
}
?>
 
            

上記プログラムをSPIRAL内アクション>PHP実行に設置して実行してください。
そうすると認証URLが出力されますので、そのURLをブラウザに貼り付けて遷移してください。
遷移するとエラーが表示されると思いますが、大丈夫です。
リダイレクトされた後URLに
http://localhost/oauth2callback.php?code=XXX&scope=YYYY
といった形でcodeのパラメータが付与されていますので、
このcodeパラメータ「XXX」を$auth_codeに代入して再度実行してください。
実行結果より「refresh_token」の値を控えてください。
作成したPHPモジュールを編集

<?php
$client_id = 'クライアントIDを入力';
$client_secret = 'クライアントシークレットを入力';
$redirect_uri = 'http://localhost/oauth2callback.php';

$refresh_token = "ステップ1で取得したリフレッシュトークンを入力"; 
            

$refresh_tokenの行を追記してください。
先ほど控えた値を$refresh_tokenの値に入力してください。
ステップ2 ファイルアップロード

<?php
//------------------------------
// 設定値
//------------------------------
define("API_URL", "https://api.spiral-platform.com/v1");
define("API_KEY", "");
define("APP_ROLE", "");
define("APP_ID", "");
define("DB_ID", "");
define("DELETE_FLG", 1); // ファイル削除フラグ(1: 削除する、0: 削除しない)
$fileFieldName = "file"; //当該DBのアップしたいファイルのファイルフィールド名
$fileFieldId = "1"; //当該DBのアップしたいファイルのファイルフィールドID
require_once "config.php";
// アップロード先ディレクトリのID
$google_drive_folder_id = "フォルダを指定する場合はGoogleドライブのURLの末尾フォルダIDを記入";

// Google APIの設定
$google_refresh_token = $refresh_token;
$google_client_id = $client_id;
$google_client_secret = $client_secret;
$google_token_endpoint = "https://oauth2.googleapis.com/token";
$google_drive_api_url = "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart";

$commonBase = CommonBase::getInstance();
$record = $SPIRAL->getRecord(); 
//------------------------------
// ファイル情報の取得
//------------------------------
$record = $record['item'];

$fileInfos = [];
if (isset($record[$fileFieldName])) {
    foreach ($record[$fileFieldName] as $file) {
        if (isset($file['fileKey']) && isset($file['fileName'])) {
            $download_url = "/apps/" . APP_ID . "/dbs/" . DB_ID . "/".$fileFieldId."/" . $record['_id'] . "/files/" . $file['fileKey'] . "/download";
            $fileInfos[] = [
                'fileName' => $file['fileName'],
                'fileKey' => $file['fileKey'],
                'url' => $download_url,
                'recordId' => $record['_id']
            ];
        }
    }
}

// Google アクセストークンの取得
$google_access_token = getGoogleAccessToken($google_refresh_token, $google_client_id, $google_client_secret, $google_token_endpoint);

// ファイルのダウンロードとGoogle Driveへのアップロード
foreach ($fileInfos as $fileInfo) {
    // SPIRALからファイルをダウンロード
    $apiResponse = $commonBase->apiCurlAction("GET", $fileInfo['url'], "", "", "not");
    // レスポンスがJSONかどうか確認
    $decodedResponse = json_decode($apiResponse, true);
    if (json_last_error() === JSON_ERROR_NONE) {
        // JSONでデコードできた場合、エラーチェック
        echo "ファイルのダウンロードに失敗しました。";
        // var_dump($decodedResponse); //ダウンロードに失敗した理由をデバッグする場合
        continue;
    } else {
        // JSONでなければファイルのバイナリデータとみなす
        $data = $apiResponse;
    }

    // Google Driveにアップロード
    $upload_headers = array(
        "Authorization: Bearer " . $google_access_token,
        "Content-Type: application/octet-stream",
        "Content-Length: " . strlen($data),
    );

    // ファイルメタデータの作成
    $metadata = [
    'name' => $fileInfo['fileName'],
    'parents' => [$google_drive_folder_id]
    ];

    $boundary = uniqid();
    $delimiter = "--" . $boundary;
    
    // アップロード用データの作成
    $post_data = $delimiter . "\r\n"
    . "Content-Type: application/json; charset=UTF-8\r\n\r\n"
    . json_encode($metadata) . "\r\n"
    . $delimiter . "\r\n"
    . "Content-Type: application/octet-stream\r\n\r\n"
    . $data . "\r\n"
    . $delimiter . "--";

    // Google Driveにアップロード
    $upload_headers = array(
        "Authorization: Bearer " . $google_access_token,
        "Content-Type: multipart/related; boundary=" . $boundary,
        "Content-Length: " . strlen($post_data),
    );

    $upload_curl = curl_init($google_drive_api_url);
    curl_setopt($upload_curl, CURLOPT_POST, 1);
    curl_setopt($upload_curl, CURLOPT_POSTFIELDS, $post_data);
    curl_setopt($upload_curl, CURLOPT_HTTPHEADER, $upload_headers);
    curl_setopt($upload_curl, CURLOPT_RETURNTRANSFER, 1);

    $upload_response = curl_exec($upload_curl);
    // var_dump($upload_response); //Googleドライブへのアップロードのレスポンスをデバッグする場合
    curl_close($upload_curl);

    //以下Googleドライブへのアップロードが成功した後の処理
    if(isset($uploadResult["id"])){
    // 削除フラグが立っている場合、SPIRALのDBからファイルを削除
        if (DELETE_FLG == 1) {
            $fileKey = ""; // 空の値を指定
            $recordId = $fileInfo['recordId'];
            $files = array();
            If (!empty($fileKey)) {
                $files[] = $fileKey ;
            }
            $UpdateData = array(
            $fileFieldName => $files,
            ); 
            $apiResponse = $commonBase->apiCurlAction("PATCH", "/apps/" . APP_ID . "/dbs/" . DB_ID . "/records/" . $recordId, $UpdateData);
            //var_dump($apiResponse); //ファイルの削除に成功しているかデバッグする場合
        }
    }
}

//------------------------------
// 共通モジュール
//------------------------------
class CommonBase {
    /**
     * シングルトンインスタンス
     * @var UserManager
     */
    protected static $singleton;

    public function __construct() {
        if (self::$singleton) {
            throw new Exception('must be singleton');
        }
        self::$singleton = $this;
    }
    /**
     * シングルトンインスタンスを返す
     * @return UserManager
     */
    public static function getInstance() {
        if (!self::$singleton) {
            return new CommonBase();
        } else {
            return self::$singleton;
        }
    }
    /**
     * V2用 API送信ロジック
     * @return Result
     */
    function apiCurlAction($method, $addUrlPass, $data = null, $multiPart = null, $jsonDecode = null) {
        $header = array(
            "Authorization:Bearer ". API_KEY,
            "X-Spiral-Api-Version: 1.1",
        );
        if($multiPart) {
            $header = array_merge($header, array($multiPart));
        } else {
            $header = array_merge($header, array("Content-Type:application/json"));
        }
        if(APP_ROLE){
			$header = array_merge($header, array("X-Spiral-App-Role: ".APP_ROLE));
		}
        // curl
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_URL, API_URL. $addUrlPass);
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        if ($method == "POST") {
            if ($multiPart) {
                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
            } else {
                curl_setopt($curl, CURLOPT_POSTFIELDS , json_encode($data));
            }
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        }
        if ($method == "PATCH") {
            curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        }
        if ($method == "DELETE") {
            curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        }
        $response = curl_exec($curl);
        if (curl_errno($curl)) echo curl_error($curl);
        curl_close($curl);
        if($jsonDecode){
			return $response;
		}else{
            return json_decode($response, true);
		}
    }
}

// Google アクセストークン取得関数
function getGoogleAccessToken($refreshToken, $clientId, $clientSecret, $tokenEndpoint) {
    $postData = array(
        'client_id' => $clientId,
        'client_secret' => $clientSecret,
        'refresh_token' => $refreshToken,
        'grant_type' => 'refresh_token'
    );

    $ch = curl_init($tokenEndpoint);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    curl_close($ch);

    $responseData = json_decode($response, true);
    return $responseData['access_token'];
}
?> 
            

上記プログラムをSPIRAL内の当該フォームブロックの登録先のDBの設定画面より、
トリガタブ>非同期アクション>PHPに設置し、設定値を編集し、有効化してください。
非同期アクションを作成する際には、経路条件を「一部の経路(フォーム)」にして頂くとフォームからの登録のみGoogleドライブへデータがアップされます。
他の非同期アクションが動いている場合等はご確認の上適切に設定してください。

GoogleドライブのフォルダIDについては、Googleドライブを開きアップロード先を想定されている任意のフォルダを開いた時のURL
https://drive.google.com/drive/folders/フォルダID
上記を確認し末尾の値を入力してください。

また本サンプルプログラムはSPIRALのファイルストレージ容量を節約する為に、
Googleドライブへアップが完了したファイルをSPIRALから削除する機能も実装しています。
削除を行いたい場合はdefine("DELETE_FLG", 1)を1にしてください。

Googleドライブへのアップロード成功時に削除以外の処理を行いたい場合は、
if (isset($uploadResult["id"]))の中の処理を変更してください。

うまく動作しない場合

Googleドライブ側でアップロードができているか確認する際に、
1分ほどラグがある可能性がありますので確認する際は少し時間を置いて確認してください。

SPIRALやGoogleドライブのAPIを実行した結果をdumpする様にコメントアウトで記載しています。
SPIRALのデータがダウンロード完了しているか、Googleドライブにアップロード成功しているか、ファイルの削除の更新APIがうまく動作しているか、
各種レスポンスを表示して確認してください。

その他

外部(Google)との連携になりますので、データの取扱や認証情報の取り扱いには十分お気をつけください。
特にクライアントIDやクライアントシークレット、認証キー等は他人に絶対に知られない様にしてください。
知られてしまうとGoogleアカウント上のデータが他人に操作、閲覧されてしまう可能性がございます。

解決しない場合はこちら コンテンツに関しての
要望はこちら