> 웹 프론트엔드 > JS 튜토리얼 > Documenso와 aws-smage-upload 예제의 Spload 기능 비교

Documenso와 aws-smage-upload 예제의 Spload 기능 비교

Mary-Kate Olsen
풀어 주다: 2025-01-03 01:41:39
원래의
496명이 탐색했습니다.

이 글에서는 Documenso와 AWS S3 이미지 업로드 예시를 통해 AWS S3에 파일을 업로드하는 단계를 비교해 보겠습니다.

Vercel에서 제공하는 간단한 예제부터 시작하겠습니다.

Comparison of Spload feature between Documenso and aws-smage-upload example

예제/aws-s3-이미지 업로드

Vercel은 AWS S3에 파일을 업로드하는 좋은 작업 예제를 제공합니다.

이 예의 README는 기존 S3 버킷을 사용하거나 새 버킷을 생성하는 두 가지 옵션을 제공합니다. 이를 이해하는 것이 도움이 됩니다

업로드 기능을 올바르게 구성했습니다.

소스코드를 살펴볼 차례입니다. type=file인 입력 요소를 찾고 있습니다. app/page.tsx에서 아래 코드를 찾을 수 있습니다:

return (
    <main>
      <h1>Upload a File to S3</h1>
      <form onSubmit={handleSubmit}>
        <input
         >



<h2>
  
  
  <strong>onChange</strong>
</h2>

<p>onChange updates state using setFile, but it does not do the uploading. upload happens when you submit this form.<br>
</p>

<pre class="brush:php;toolbar:false">onChange={(e) => {
  const files = e.target.files
  if (files) {
    setFile(files[0])
  }
}}
로그인 후 복사

제출 처리

handleSubmit 함수에서는 많은 일이 일어나고 있습니다. 이 handlerSubmit 함수의 작업 목록을 분석해야 합니다. 단계를 설명하기 위해 이 코드 조각 안에 설명을 작성했습니다.

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()

    if (!file) {
      alert('Please select a file to upload.')
      return
    }

    setUploading(true)

    const response = await fetch(
      process.env.NEXT_PUBLIC_BASE_URL + '/api/upload',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ filename: file.name, contentType: file.type }),
      }
    )

    if (response.ok) {
      const { url, fields } = await response.json()

      const formData = new FormData()
      Object.entries(fields).forEach(([key, value]) => {
        formData.append(key, value as string)
      })
      formData.append('file', file)

      const uploadResponse = await fetch(url, {
        method: 'POST',
        body: formData,
      })

      if (uploadResponse.ok) {
        alert('Upload successful!')
      } else {
        console.error('S3 Upload Error:', uploadResponse)
        alert('Upload failed.')
      }
    } else {
      alert('Failed to get pre-signed URL.')
    }

    setUploading(false)
  }
로그인 후 복사

API/업로드

api/upload/route.ts에는 아래 코드가 있습니다.

import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
import { S3Client } from '@aws-sdk/client-s3'
import { v4 as uuidv4 } from 'uuid'

export async function POST(request: Request) {
  const { filename, contentType } = await request.json()

  try {
    const client = new S3Client({ region: process.env.AWS_REGION })
    const { url, fields } = await createPresignedPost(client, {
      Bucket: process.env.AWS_BUCKET_NAME,
      Key: uuidv4(),
      Conditions: [
        ['content-length-range', 0, 10485760], // up to 10 MB
        ['starts-with', '$Content-Type', contentType],
      ],
      Fields: {
        acl: 'public-read',
        'Content-Type': contentType,
      },
      Expires: 600, // Seconds before the presigned post expires. 3600 by default.
    })

    return Response.json({ url, fields })
  } catch (error) {
    return Response.json({ error: error.message })
  }
}
로그인 후 복사

handleSubmit의 첫 번째 요청은 /api/upload였으며 콘텐츠 유형과 파일 이름을 페이로드로 전송했습니다. 아래와 같이 파싱됩니다:

const { filename, contentType } = await request.json()
로그인 후 복사

다음 단계는 S3 클라이언트를 생성한 다음 URL과 필드를 반환하는 미리 서명된 게시물을 생성하는 것입니다. 이 URL을 사용하여 파일을 업로드합니다.

이 지식을 바탕으로 Documenso에서 업로드가 어떻게 작동하는지 분석하고 비교해 보겠습니다.

Documenso에 PDF 파일 업로드

type=file인 입력 요소부터 시작하겠습니다. Documenso에서는 코드가 다르게 구성됩니다. document-dropzone.tsx라는 파일에서 입력 요소를 찾을 수 있습니다.

<input {...getInputProps()} />

<p className="text-foreground mt-8 font-medium">{_(heading[type])}</p>
로그인 후 복사

여기서 getInputProps는 useDropzone을 반환합니다. Documenso는 React-dropzone을 사용합니다.

import { useDropzone } from 'react-dropzone';
로그인 후 복사

onDrop이 props.onDrop을 호출하면 upload-document.tsx에서 onFileDrop이라는 속성 값을 찾을 수 있습니다.

<DocumentDropzone
  className="h-[min(400px,50vh)]"
  disabled={remaining.documents === 0 || !session?.user.emailVerified}
  disabledMessage={disabledMessage}
  onDrop={onFileDrop}
  onDropRejected={onFileDropRejected}
/>
로그인 후 복사

onFileDrop 함수에서 어떤 일이 일어나는지 살펴보겠습니다.

const onFileDrop = async (file: File) => {
    try {
      setIsLoading(true);

      const { type, data } = await putPdfFile(file);

      const { id: documentDataId } = await createDocumentData({
        type,
        data,
      });

      const { id } = await createDocument({
        title: file.name,
        documentDataId,
        teamId: team?.id,
      });

      void refreshLimits();

      toast({
        title: _(msg`Document uploaded`),
        description: _(msg`Your document has been uploaded successfully.`),
        duration: 5000,
      });

      analytics.capture('App: Document Uploaded', {
        userId: session?.user.id,
        documentId: id,
        timestamp: new Date().toISOString(),
      });

      router.push(`${formatDocumentsPath(team?.url)}/${id}/edit`);
    } catch (err) {
      const error = AppError.parseError(err);

      console.error(err);

      if (error.code === 'INVALID_DOCUMENT_FILE') {
        toast({
          title: _(msg`Invalid file`),
          description: _(msg`You cannot upload encrypted PDFs`),
          variant: 'destructive',
        });
      } else if (err instanceof TRPCClientError) {
        toast({
          title: _(msg`Error`),
          description: err.message,
          variant: 'destructive',
        });
      } else {
        toast({
          title: _(msg`Error`),
          description: _(msg`An error occurred while uploading your document.`),
          variant: 'destructive',
        });
      }
    } finally {
      setIsLoading(false);
    }
  };
로그인 후 복사

많은 일이 일어나고 있지만 분석을 위해 putFile이라는 함수만 고려해 보겠습니다.

putPdf파일

putPdfFile은 upload/put-file.ts에 정의되어 있습니다

/**
 * Uploads a document file to the appropriate storage location and creates
 * a document data record.
 */
export const putPdfFile = async (file: File) => {
  const isEncryptedDocumentsAllowed = await getFlag('app_allow_encrypted_documents').catch(
    () => false,
  );

  const pdf = await PDFDocument.load(await file.arrayBuffer()).catch((e) => {
    console.error(`PDF upload parse error: ${e.message}`);

    throw new AppError('INVALID_DOCUMENT_FILE');
  });

  if (!isEncryptedDocumentsAllowed && pdf.isEncrypted) {
    throw new AppError('INVALID_DOCUMENT_FILE');
  }

  if (!file.name.endsWith('.pdf')) {
    file.name = `${file.name}.pdf`;
  }

  removeOptionalContentGroups(pdf);

  const bytes = await pdf.save();

  const { type, data } = await putFile(new File([bytes], file.name, { type: 'application/pdf' }));

  return await createDocumentData({ type, data });
};
로그인 후 복사

putFile

putFile 함수를 호출합니다.

/**
 * Uploads a file to the appropriate storage location.
 */
export const putFile = async (file: File) => {
  const NEXT_PUBLIC_UPLOAD_TRANSPORT = env('NEXT_PUBLIC_UPLOAD_TRANSPORT');

  return await match(NEXT_PUBLIC_UPLOAD_TRANSPORT)
    .with('s3', async () => putFileInS3(file))
    .otherwise(async () => putFileInDatabase(file));
};
로그인 후 복사

putFileInS3

const putFileInS3 = async (file: File) => {
  const { getPresignPostUrl } = await import('./server-actions');

  const { url, key } = await getPresignPostUrl(file.name, file.type);

  const body = await file.arrayBuffer();

  const reponse = await fetch(url, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/octet-stream',
    },
    body,
  });

  if (!reponse.ok) {
    throw new Error(
      `Failed to upload file "${file.name}", failed with status code ${reponse.status}`,
    );
  }

  return {
    type: DocumentDataType.S3_PATH,
    data: key,
  };
};
로그인 후 복사

getPresignPostUrl

export const getPresignPostUrl = async (fileName: string, contentType: string) => {
  const client = getS3Client();

  const { getSignedUrl } = await import('@aws-sdk/s3-request-presigner');

  let token: JWT | null = null;

  try {
    const baseUrl = APP_BASE_URL() ?? 'http://localhost:3000';

    token = await getToken({
      req: new NextRequest(baseUrl, {
        headers: headers(),
      }),
    });
  } catch (err) {
    // Non server-component environment
  }

  // Get the basename and extension for the file
  const { name, ext } = path.parse(fileName);

  let key = `${alphaid(12)}/${slugify(name)}${ext}`;

  if (token) {
    key = `${token.id}/${key}`;
  }

  const putObjectCommand = new PutObjectCommand({
    Bucket: process.env.NEXT_PRIVATE_UPLOAD_BUCKET,
    Key: key,
    ContentType: contentType,
  });

  const url = await getSignedUrl(client, putObjectCommand, {
    expiresIn: ONE_HOUR / ONE_SECOND,
  });

  return { key, url };
};
로그인 후 복사

비교

  • Documenso에 POST 요청이 표시되지 않습니다. URL을 가져오기 위해 getSignedUrl이라는 함수를 사용하는 반면,

    vercel 예제는 API/업로드 경로에 대한 POST 요청을 만듭니다.

  • 입력 요소는 Vercel 예제에서 예시일 뿐이므로 쉽게 찾을 수 있지만 Documenso는 발견됩니다

    React-dropzone을 사용하고 입력 요소가 비즈니스 컨텍스트에 따라 위치합니다.

회사 소개:

Thinkthroo에서는 대규모 오픈소스 프로젝트를 연구하고 아키텍처 가이드를 제공합니다. 우리는 귀하의 프로젝트에서 사용할 수 있는 tailwind로 구축된 재사용 가능한 구성요소를 개발했습니다.

Next.js, React, Node 개발 서비스를 제공합니다.

귀하의 프로젝트에 대해 논의하려면 회의를 예약하세요.

Comparison of Spload feature between Documenso and aws-smage-upload example

참고자료:

  1. https://github.com/documenso/documenso/blob/main/packages/lib/universal/upload/put-file.ts#L69

  2. https://github.com/vercel/examples/blob/main/solutions/aws-s3-image-upload/README.md

  3. https://github.com/vercel/examples/tree/main/solutions/aws-s3-image-upload

  4. https://github.com/vercel/examples/blob/main/solutions/aws-s3-image-upload/app/page.tsx#L58C5-L76C12

  5. https://github.com/vercel/examples/blob/main/solutions/aws-s3-image-upload/app/api/upload/route.ts

  6. https://github.com/documenso/documenso/blob/main/packages/ui/primitives/document-dropzone.tsx#L157

  7. https://react-dropzone.js.org/

  8. https://github.com/documenso/documenso/blob/main/apps/web/src/app/(dashboard)/documents/upload-document.tsx#L61

  9. https://github.com/documenso/documenso/blob/main/packages/lib/universal/upload/put-file.ts#L22

위 내용은 Documenso와 aws-smage-upload 예제의 Spload 기능 비교의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿