Typescript、Azure Function、Axios - 从文件发布 FormData

Typescript, Azure Function, Axios - Posting FormData from File

我有一个 Azure 函数,它接收文件上传到端点,并试图将该文件重新发布到另一个使用 Axios 处理文件的端点微服务。当我尝试将 formData 添加到 ocrServicePromise 时,我收到一个错误 source.on is not a function.

有什么问题吗?

错误是: [10/26/2020 2:06:27 PM] 已执行 'Functions.ReceiveInboundFax'(失败,Id=0a7bb949-9ff8-4762-8c21-ba2177ba96e2) [10/26/2020 2:06:27 下午] System.Private.CoreLib:执行函数时出现异常:Functions.ReceiveInboundFax。 System.Private.CoreLib:结果:失败 [10/26/2020 2:06:27 PM] 异常:类型错误:source.on 不是函数 [2020 年 10 月 26 日 2:06:27 下午]堆栈:类型错误:source.on 不是函数

import { AzureFunction, Context, HttpRequest } from "@azure/functions"
import * as multipart from 'parse-multipart';
import axios from 'axios';
import { OcrStatusResponse } from "./classes/OcrStatusResponse";
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import gql from 'graphql-tag';
import fetch from 'node-fetch';
import { createHttpLink } from "apollo-link-http";

const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {

const body = req.rawBody;
// Retrieve the boundary id
const boundary = multipart.getBoundary(req.headers["content-type"]);
let logEntries = [];

if (boundary) {
    const files: File[] = multipart.Parse(Buffer.from(body), boundary);

    if (files && files.length > 0) {
                      
        var allUpdated = await Promise.all(files.map(async (file) => {

            //CallOcrService
            var ocrresult = await CallOcrService(context, file);                

            //Write to Hasura
            logEntries.push(await LogOcrRequest(context, ocrresult, file.name));

        }));
    }   
    
}    

context.res = {
    status: 200,            
    body: logEntries
}  

};

async function CallOcrService(context: Context,file : File) : Promise<OcrStatusResponse>{

const FormDataFile = require('form-data');
const formData = new FormDataFile();
formData.append("file", file)

const ocrServicePromise = await axios.post(process.env["Ocr_Service_Upload_Endpoint"],  formData, {
        headers: formData.getHeaders()
    })
    .then((response) => {
        const ocrSeriviceResponse = new OcrStatusResponse(response.data);

        ocrSeriviceResponse.status = response.data.state;
        
        return ocrSeriviceResponse;
    }, (error) => {
        context.log.error(error);

        throw new Error(error);
    }
);

return ocrServicePromise;
}

async function LogOcrRequest(context: Context, ocrResponse: OcrStatusResponse, filename: String){  

const headers = {
    "content-type": "application/json",
    "x-hasura-admin-secret": process.env["ATTACHMENT_API_ADMIN_SECRET"]
}

const client = new ApolloClient({
    link: createHttpLink({
    uri:
        process.env["ATTACHMENT_API_ENDPOINT"],
        headers:headers,        
        fetch:fetch
    }),
    cache: new InMemoryCache()
});

const ocr_deadline = dateAdd(new Date(), "minute", 20);

const mutationInsertObj = {
    date_time_received: new Date(),
    file_name_received: filename,
    ocr_job_id: ocrResponse.jobid,
    date_time_ocr_response_deadline : ocr_deadline,
    ocr_result_statuscheck_endpoint : ocrResponse.status_url,
    ocr_result : ocrResponse.status,
    ocr_document_endpoint: `${process.env["Ocr_Service_Upload_Document_Retrival_Endpoint"]}/${ocrResponse.jobid}/${ocrResponse.jobid}.pdf`
}

if(!ocrResponse.success){
    context.log.error(`Response was empty or did not receive a status url for ${filename}`);
    mutationInsertObj.ocr_result = "ERROR";
}

const insertMutation = gql `
    mutation InsertOcrRequest($ocrResponse: inbound_fax_insert_input!)  {
        insert_inbound_fax_one(
            object: $ocrResponse
        ) {
            inboundfaxid
            ocr_job_id
            ocr_result_statuscheck_endpoint
            ocr_result
            date_time_ocr_response_deadline
            ocr_document_endpoint
        }
    }
`;

const {errors,data} = await client.mutate({
    mutation: insertMutation,
    errorPolicy: 'all',
    variables: mutationInsertObj
});

if(errors!== undefined){
    const errorList = [];
    errorList.push(errors);
    return errorList;
}

return data;

}

function dateAdd(date, interval, units) {
if(!(date instanceof Date))
  return undefined;

var ret = new Date(date); 
var checkRollover = function() { if(ret.getDate() != date.getDate()) ret.setDate(0);};

switch(String(interval).toLowerCase()) {
  case 'year'   :  ret.setFullYear(ret.getFullYear() + units); checkRollover();  break;
  case 'quarter':  ret.setMonth(ret.getMonth() + 3*units); checkRollover();  break;
  case 'month'  :  ret.setMonth(ret.getMonth() + units); checkRollover();  break;
  case 'week'   :  ret.setDate(ret.getDate() + 7*units);  break;
  case 'day'    :  ret.setDate(ret.getDate() + units);  break;
  case 'hour'   :  ret.setTime(ret.getTime() + units*3600000);  break;
  case 'minute' :  ret.setTime(ret.getTime() + units*60000);  break;
  case 'second' :  ret.setTime(ret.getTime() + units*1000);  break;
  default       :  ret = undefined;  break;
}

return ret;
}



export default httpTrigger;

如果你使用包parse-multipart解析multipart/form-data,它只是return像{ filename: 'A.txt', type: 'text/plain', data: <Buffer 41 41 41 41 42 42 42 42> }那样的值,它不会returnFile 对象。详情请参考here

此外,包form-data只支持字符串、缓冲区和文件流。详情请参考here.

所以请更新如下

  1. 定义一个接口以从 parse-multipart
  2. 接收值
export interface FileInfo {
  filename: string;
  type: string;
  data: Buffer;
}
  1. 更新 HTTP 触发代码
import * as multipart from "parse-multipart";
import axios from "axios";
import { FileInfo } from "../entities/FileInfo";
import * as FormDataFile from "form-data";
const httpTrigger: AzureFunction = async function (
  context: Context,
  req: HttpRequest
): Promise<void> {
  context.log("HTTP trigger function processed a request.");
  const body = req.rawBody;
  // Retrieve the boundary id
  const boundary = multipart.getBoundary(req.headers["content-type"]);
  let logEntries = [];

  if (boundary) {
    const files: FileInfo[] = multipart.Parse(Buffer.from(body), boundary);

    if (files && files.length > 0) {
      var allUpdated = await Promise.all(
        files.map(async (file) => {
          await CallOcrService(context, file);
        })
      );
    }
  }

  context.res = {
    status: 200,
    body: "OK",
  };
};

async function CallOcrService(
  context: Context,
  file: FileInfo
): Promise<String> {
  const formData = new FormDataFile();
  formData.append("file", Buffer.from(file.data), {
    filename: file.filename,
    contentType: file.type,
  });

 // call you api
}