質問

投稿者:cw1108
登録日:2024年5月1日(水)

SPIRAL API「ファイルをアップロード」に関する質問

SPIRAL API「ファイルをアップロード」に関する質問です。 body部に「Multipart/form-data」で以下のように指定し、リクエストしました。 ======= --WebKitFormBoundary Content-Disposition: form-data; name="fileUploadToken" c0a3XXXX81d1XXXXad98XXXXddafXXXX --WebKitFormBoundary Content-Disposition: form-data; name="file"; filename="sample.png" Content-Type: image/png data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAWCAYAA............ --WebKitFormBoundary-- === ファイル形式に問題があるとのことで、 試しに「Content-Disposition: 」の「filename="sample.png"」を「filename="sample.txt"」に変更して実行したところ、ファイルアップロードに成功しました(ただし、ファイルの中身はBASE64コードのテキスト)。 画像ファイル(pngやjpeg)における、ファイル内容の記載方法が誤っていると判断しました。リファレンスを参照してもその事例が書かれていないため、そのあたりの記載方法をご教授いただけないでしょうか?

更新日:2024年5月9日(木)
いいね

コメント

  • cw1108

    情報が不足しておりました。 リクエストの結果が、以下でした。 ーーーー { "status" : 400, "message" : "validation failed", "errors" : [ { "code" : "invalidParameter", "message" : "file format is different from the extension", "locationType" : "body", "location" : "/file" } ] } ーーーー 上記にも書きましたが、テキストファイルであれば登録成功します。バイナリファイルが登録できません。

    • いいね
    2024年5月1日(水)
  • 画像データを送信する場合はbase64エンコードされた文字列ではなくバイナリデータを送信する必要があります。 記載していただいた、 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAWCAYAA............ の箇所にbase64エンコードされた文字列ではなくバイナリデータを挿入してください。 PHPであればbase64_decodeを使ってバイナリデータへデコードしてしてください。 またデコードする際にimage/png;base64,の箇所は不要ですので削除してからデコードを行ってください。

    ナレッジサイト内共通モジュールを使ったサンプル
        // 画像データの準備
        $base64 = explode(',', $base64, 2)[1];
        $decodedData = base64_decode($base64);
    
        //ファイルアップロードトークン発行
        $apiUrlPass = "/apps/". APP_ID. "/dbs/". DB_ID. "/". $fileFieldId. "/files/uploadToken";
        $apiResponse = $commonBase->apiCurlAction("POST", $apiUrlPass);
        $fileUploadToken = $apiResponse["fileUploadToken"];
        //ファイルアップロード
        $apiUrlPass = "/apps/". APP_ID. "/dbs/". DB_ID. "/". $fileFieldId. "/files";
    
            // リクエストボディ(マルチパート形式)
            $requestBody = "--WebKitFormBoundary7MA4YWxkTrZu0gW\r\n";
            $requestBody .= "Content-Disposition: form-data; name=\"file\"; filename=\"". $fileName. "\"\r\n";
            $requestBody .= "Content-Type: image/png\r\n\r\n";
            $requestBody .= $decodedData. "\r\n";
            $requestBody .= "--WebKitFormBoundary7MA4YWxkTrZu0gW\r\n";
            $requestBody .= "Content-Disposition: form-data; name=\"fileUploadToken\"\r\n\r\n";
            $requestBody .= $fileUploadToken. "\r\n";
            $requestBody .= "--WebKitFormBoundary7MA4YWxkTrZu0gW--";
    
            $contentType = "Content-Type: multipart/form-data; boundary=WebKitFormBoundary7MA4YWxkTrZu0gW";
    
        $apiResponse = $commonBase->apiCurlAction("POST", $apiUrlPass, $requestBody, $contentType);
    • いいね
    2024年5月2日(木)
  • cw1108

    ご回答ありがとうございます。 上記アドバイスで対応をしてみました。 しかし、現在プログラムを「ExcelVBA」で実装しており、バイナリデータをそのままbody部に設定すると一部データが破損する為、上記対応では困難と結論付けました。 他の手法(PHP等)で対応する必要があるようです。視野を広げて調査します。 以下の目的を達成する為の、容易な手段の事例などありましたら是非ご共有いただければと思います。  ・ローカルPC上にある、大量の「ルール付けされた」画像ファイルをバッチで順次SPIRALにアップロードしたい

    • いいね
    2024年5月3日(金)
  • 現時点ではファイルを一括登録するAPIがないため、1つずつファイルをアップロードしていただくしか方法がありません。 APIを利用してアップロードを行う際には、1つのファイルにつき最低3回はAPIを使用する必要がありますので、APIの上限数にご注意ください。

    • いいね
    2024年5月8日(水)
  • cw1108

    本件、vbsでの実装をいったん保留し、GASでの実装で検討しましたが、同様の事象で解決できません。 GASのコードを載せましたが、マルチパート形式の記載箇所(wBodyへの編集、バイナリデータの挿入方法)に問題があるようです。 (前述同様、テキストファイルとしてのアップロードは成功します) 以下、ファイルアップロード時の結果です。 ーーーー { "status" : 400, "message" : "validation failed", "errors" : [ { "code" : "invalidParameter", "message" : "file format is different from the extension", "locationType" : "body", "location" : "/file" } ] } ーーーー 改めまして、どうかご教授いただきたくお願いいたします。

    GASでのコード
    function exampleUsage() {
      // 適切な引数を指定して関数を呼び出します
      var apiKey = "e8EHXXXXf5hsXXXXLFBCXXXXKOD6XXXX";
      var apId = "XXXXX";
      var dbId = "XXXXX";
      var flId = "X";
     
      // ■ ファイルアップロードトークンを取得
      var wResultJson = CallSpiralApi_IssueFileUploadToken(apiKey, apId, dbId, flId);
      var wResult = JSON.parse(wResultJson);
    
      if (wResult.fileUploadToken == "") {
        Browser.msgBox("ファイルアップロードトークン取得失敗\n\n" + wResultJson);
        return;
      } else {
        var wFileUploadToken = wResult.fileUploadToken;
      }
    
      // ■ ファイルアップロード
      var wBoundary = "WebKitFormBoundary";
      var wFileName = "sample.png";
      var wFilePath = "1rEWXXXX9zuxXXXXOyjnXXXXSVs_XXXXH"
      
      // ボディの作成
      wBody = "";
      wBody += "--" + wBoundary + "\r\n";
      wBody += "Content-Disposition: form-data; name=\"file\"; filename=\"" + wFileName + "\"\r\n";
      wBody += "Content-Type: image/png\r\n\r\n";
      wBody += DriveApp.getFileById(wFilePath).getBlob().getBytes() + "\r\n";
        
      wBody += "--" + wBoundary + "\r\n";
      wBody += "Content-Disposition: form-data; name=\"fileUploadToken\"\r\n\r\n";
      wBody += wFileUploadToken + "\r\n";
      
      wBody += "--" + wBoundary + "--";
      
      wResultJson = CallSpiralApi_UploadFile(apiKey, apId, dbId, flId, wBody);
      wResult = JSON.parse(wResultJson);
    
      if (wResult.fileKey == "") {
        Browser.msgBox("ファイルアップロード失敗\n\n" + wResultJson);
        return;
      } else {
        wFileKey = wResult.fileKey;
      } 
    
      // ■ レコードを登録(ファイルエリアに設定)
      var param = {};
      param["EmpNo"] = String(Math.floor(Math.random() * 1000));  // 必須項目かつユニークキーであるため、設定する必要あり
      var list = [];
      list.push(wFileKey);
      param["Image"] = list;  // Imageはフィールドの「識別名」とする
      wBody = JSON.stringify(param);
      
      wResultJson = CallSpiralApi_InsertRecord(apiKey, apId, dbId, wBody);
      wResult = JSON.parse(wResultJson);
     
      if (wResult.status !== "") {
        Browser.msgBox("レコード登録失敗\n\n" + wResultJson);
        return;
      } else {
        Browser.msgBox("登録成功しました\n" + wResultJson);
      }
        
    }
    API「ファイルアップロードトークンを発行」
    // ----------------------------------------------------------
    // 	■ ファイルアップロードトークンを発行
    // ----------------------------------------------------------
    //  [引数]pApiKey:APIキー
    //          pApID  :アプリのID
    //          pDbID  :DBのID
    //          pFeID  :フィールドのID
    //  [返値]実行結果(JSONフォーマット)
    // ----------------------------------------------------------
    function CallSpiralApi_IssueFileUploadToken(pApiKey, pApID, pDbID, pFlID) {
      
      var url = "https://api.spiral-platform.com/v1/apps/" + pApID + "/dbs/" + pDbID + "/" + pFlID + "/files/uploadToken";
      var headers = {
        "Authorization": "Bearer " + pApiKey,
        "Content-Type": "application/json; charset=UTF-8"
      };
      var options = {
        "method": "post",
        "headers": headers,
        "muteHttpExceptions": true // エラーハンドリングのためのオプション
      };
      
      var response = UrlFetchApp.fetch(url, options);
      //Logger.log(response.getContentText());
    
      // レスポンスが成功したかを確認する
      if (response.getResponseCode() >= 200 || response.getResponseCode() < 300) {
        var responseBody = response.getContentText();
         return responseBody;
      } else {
        // エラーレスポンスを返す
        var errorResponse = "Error: " + response.getResponseCode() + " - " + response.getContentText();
        Logger.log(errorResponse);
        return errorResponse;
      }
    }
    API「ファイルをアップロード」
    // ----------------------------------------------------------
    // ■ ファイルをアップロード
    // ----------------------------------------------------------
    //  [引数] pApiKey:APIキー
    //          pApID  :アプリのID
    //          pDbID  :DBのID
    //          pFeID  :フィールドのID
    //          pBody  :リクエストボディ(マルチパート形式)
    //  [返値]実行結果(JSONフォーマット)
    // ----------------------------------------------------------
    function CallSpiralApi_UploadFile(pApiKey, pApID, pDbID, pFlID, pBody) {
      var wBoundary = "WebKitFormBoundary";
    
      var url = "https://api.spiral-platform.com/v1/apps/" + pApID + "/dbs/" + pDbID + "/" + pFlID + "/files";
      var headers = {
        "Authorization": "Bearer " + pApiKey,
        "Content-Type": "multipart/form-data; boundary=" + wBoundary
      };
      var options = {
        "method": "post",
        "headers": headers,
        "payload": pBody,
        "muteHttpExceptions": true // エラーハンドリングのためのオプション
      };
      
      var response = UrlFetchApp.fetch(url, options);
      
      // レスポンスが成功したかを確認する
      if (response.getResponseCode() >= 200 || response.getResponseCode() < 300) {
        return response.getContentText();
      } else {
        // エラーレスポンスを返す
        return "Error: " + response.getResponseCode() + " - " + response.getContentText();
      }
    }
    
    API「レコードを登録」
    // ----------------------------------------------------------
    // ■ レコードを登録
    // ----------------------------------------------------------
    //  [引数] pApiKey:APIキー
    //          pApID  :アプリのID
    //          pDbID  :DBのID
    //          pBody  :リクエストボディ(マルチパート形式)
    //  [返値]実行結果(JSONフォーマット)
    // ----------------------------------------------------------
    function CallSpiralApi_InsertRecord(pApiKey, pApID, pDbID, pBody) {
    
    var url = "https://api.spiral-platform.com/v1/apps/" + pApID + "/dbs/" + pDbID + "/records";
      
      // HTTPリクエストのオプションを設定
      var options = {
        "method": "post",
        "headers": {
          "Authorization": "Bearer " + pApiKey,
          "Content-Type": "application/json; charset=UTF-8"
        },
        "payload": pBody,
        "muteHttpExceptions": true // エラーハンドリングのためのオプション
      };
        
      // HTTPリクエストを送信し、レスポンスを取得
      var response = UrlFetchApp.fetch(url, options);
        
      // レスポンスの内容を返す
      return response.getContentText();
    }
    • いいね
    2024年5月9日(木)
  • お世話になります。 本件GoogleColabのPythonを用いて、Googleドライブにアップしたファイルをアップロードする形にしてみてはいかがでしょうか。 ローカル上のファイルとは異なってしまいますが ファイルの取扱は楽かと思います。ファイルそのものを取り扱えるので バイナリへの変換等も不要になるかとも思います。 ローカル下のファイル限定であればローカルでPythonを動かすのも良いかと思います。 以下にGoogleColabで動くPythonのサンプルコードを添付します。 どうしてもGASでなければならない場合は再度投稿ください。 コレでも動かない場合はファイルのほうに問題がある場合もあるので状況を教えていただけると幸いです。

    GoogleColab上Pythonコード
    import requests
    import os
    import time
    from google.colab import drive
    
    # Google Driveをマウント
    drive.mount('/content/drive')
    
    # 設定値
    API_URL = "https://api.spiral-platform.com/v1"
    API_KEY = ""  # 環境変数からAPIキーを取得
    APP_ROLE = ""
    DB_ID = ""
    APP_ID = ""
    SPIRALPDF_PATH = "/content/drive/My Drive/Images"  # Imagesフォルダのパス
    FILE_TYPES = ['png']  # 対象のファイルタイプ
    REQUESTS_PER_MINUTE_LIMIT = 600  # リクエスト上限数
    
    class CommonBase:
        _instance = None
    
        def __new__(cls):
            if cls._instance is None:
                cls._instance = super(CommonBase, cls).__new__(cls)
            return cls._instance
    
        def api_curl_action(self, method, add_url_pass, data=None, multi_part=None, json_decode=None):
            headers = {
                "Authorization": f"Bearer {API_KEY}",
                "X-Spiral-Api-Version": "1.1",
            }
            if multi_part:
                headers.update({multi_part: None})
            else:
                headers["Content-Type"] = "application/json"
    
            if APP_ROLE:
                headers["X-Spiral-App-Role"] = APP_ROLE
    
            url = API_URL + add_url_pass
            if method in ["POST", "PATCH", "DELETE"]:
                if multi_part:
                    response = requests.request(method, url, headers=headers, files=data)
                else:
                    response = requests.request(method, url, headers=headers, json=data)
            else:
                response = requests.request(method, url, headers=headers)
    
            if json_decode:
                return response.text
            else:
                return response.json()
    
    common_base = CommonBase()  # シングルトンインスタンス取得
    processed_files = 0
    start_time = time.time()
    
    
    
    文字数の問題で分割
    for root, dirs, files in os.walk(SPIRALPDF_PATH):
        for file_name in files:
            if file_name.lower().endswith('png'):
                file_path = os.path.join(root, file_name)
                file_data = open(file_path, "rb").read()
    
                # ファイルアップロード
                file_field_id = 4
                api_url_pass = f"/apps/{APP_ID}/dbs/{DB_ID}/{file_field_id}/files/uploadToken"
    
                api_response = common_base.api_curl_action("POST", api_url_pass)
                file_upload_token = api_response["fileUploadToken"]
    
                api_url_pass = f"/apps/{APP_ID}/dbs/{DB_ID}/{file_field_id}/files"
                files = {
                    'file': (file_name, file_data, 'application/octet-stream'),
                    'fileUploadToken': (None, file_upload_token)
                }
    
                api_response = common_base.api_curl_action("POST", api_url_pass, data=files, multi_part='multipart/form-data')
                file_key = api_response["fileKey"]
    
                # データ挿入
                insert_data = {
                    "fileData": [file_key]
                }
                api_response = common_base.api_curl_action("POST", f"/apps/{APP_ID}/dbs/{DB_ID}/records", data=insert_data)
                print(file_name, api_response)
                processed_files += 1
    
                # リクエスト上限を超えないようにディレイ
                if processed_files % REQUESTS_PER_MINUTE_LIMIT == 0:
                    time.sleep(60 - (time.time() - start_time))
                    start_time = time.time()
    
    print(f"Processed files: {processed_files}")
    • いいね
    2024年5月9日(木)