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)における、ファイル内容の記載方法が誤っていると判断しました。リファレンスを参照してもその事例が書かれていないため、そのあたりの記載方法をご教授いただけないでしょうか?
コメント
画像データを送信する場合は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);
本件、vbsでの実装をいったん保留し、GASでの実装で検討しましたが、同様の事象で解決できません。 GASのコードを載せましたが、マルチパート形式の記載箇所(wBodyへの編集、バイナリデータの挿入方法)に問題があるようです。 (前述同様、テキストファイルとしてのアップロードは成功します) 以下、ファイルアップロード時の結果です。 ーーーー { "status" : 400, "message" : "validation failed", "errors" : [ { "code" : "invalidParameter", "message" : "file format is different from the extension", "locationType" : "body", "location" : "/file" } ] } ーーーー 改めまして、どうかご教授いただきたくお願いいたします。
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); } }
// ---------------------------------------------------------- // ■ ファイルアップロードトークンを発行 // ---------------------------------------------------------- // [引数]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; } }
// ---------------------------------------------------------- // ■ ファイルをアップロード // ---------------------------------------------------------- // [引数] 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(); } }
// ---------------------------------------------------------- // ■ レコードを登録 // ---------------------------------------------------------- // [引数] 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(); }
お世話になります。 本件GoogleColabのPythonを用いて、Googleドライブにアップしたファイルをアップロードする形にしてみてはいかがでしょうか。 ローカル上のファイルとは異なってしまいますが ファイルの取扱は楽かと思います。ファイルそのものを取り扱えるので バイナリへの変換等も不要になるかとも思います。 ローカル下のファイル限定であればローカルでPythonを動かすのも良いかと思います。 以下にGoogleColabで動くPythonのサンプルコードを添付します。 どうしてもGASでなければならない場合は再度投稿ください。 コレでも動かない場合はファイルのほうに問題がある場合もあるので状況を教えていただけると幸いです。
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}")
情報が不足しておりました。 リクエストの結果が、以下でした。 ーーーー { "status" : 400, "message" : "validation failed", "errors" : [ { "code" : "invalidParameter", "message" : "file format is different from the extension", "locationType" : "body", "location" : "/file" } ] } ーーーー 上記にも書きましたが、テキストファイルであれば登録成功します。バイナリファイルが登録できません。