Dans cet article, nous comparerons les étapes à suivre pour télécharger un fichier sur AWS S3 entre Documenso et l'exemple de téléchargement d'images AWS S3.
Nous commençons par l'exemple simple fourni par Vercel.
Vercel fournit un bon exemple pratique de téléchargement d'un fichier sur AWS S3.
Le fichier README de cet exemple propose deux options : soit vous pouvez utiliser un compartiment S3 existant, soit créer un nouveau compartiment. Comprendre cela aide
vous configurez correctement votre fonctionnalité de téléchargement.
C'est à peu près le moment où nous regardons le code source. Nous recherchons un élément d'entrée avec type=file. Dans le app/page.tsx, vous trouverez ce code ci-dessous :
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]) } }}
Il se passe beaucoup de choses dans la fonction handleSubmit. Nous devons analyser la liste des opérations dans cette fonction handleSubmit. J'ai écrit les commentaires dans cet extrait de code pour expliquer les étapes.
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/upload/route.ts a le code ci-dessous :
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 }) } }
La première requête dans handleSubmit était /api/upload et envoie le type de contenu et le nom de fichier comme charge utile. Il est analysé comme ci-dessous :
const { filename, contentType } = await request.json()
L'étape suivante consiste à créer un client S3, puis à créer une publication présignée qui renvoie l'URL et le champ. Vous utiliserez cette URL pour télécharger votre fichier.
Avec cette connaissance, analysons le fonctionnement du téléchargement dans Documenso et faisons une comparaison.
Commençons par l'élément d'entrée avec type=file. Le code est organisé différemment dans Documenso. Vous trouverez l'élément d'entrée dans un fichier nommé document-dropzone.tsx.
<input {...getInputProps()} /> <p className="text-foreground mt-8 font-medium">{_(heading[type])}</p>
Ici, getInputProps est renvoyé, useDropzone. Documenso utilise réagir-dropzone.
import { useDropzone } from 'react-dropzone';
onDrop appelle props.onDrop, vous trouverez une valeur d'attribut nommée onFileDrop dans upload-document.tsx.
<DocumentDropzone className="h-[min(400px,50vh)]" disabled={remaining.documents === 0 || !session?.user.emailVerified} disabledMessage={disabledMessage} onDrop={onFileDrop} onDropRejected={onFileDropRejected} />
Voyons ce qui se passe dans la fonction 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); } };
Il se passe beaucoup de choses mais pour notre analyse, considérons uniquement la fonction nommée putFile.
putPdfFile est défini dans 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 }); };
Cela appelle la fonction 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)); };
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, }; };
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 }; };
Vous ne voyez aucune requête POST dans Documenso. Il utilise une fonction nommée getSignedUrl pour obtenir l'URL, alors que
L'exemple de Vercel fait une requête POST vers la route api/upload.
L'élément d'entrée peut être facilement localisé dans l'exemple de Vercel car ce n'est qu'un exemple, mais Documenso est trouvé
utiliser React-dropzone et l'élément d'entrée est localisé en fonction du contexte commercial.
Chez Thinkthroo, nous étudions les grands projets open source et fournissons des guides architecturaux. Nous avons développé des composants réutilisables, construits avec tailwind, que vous pouvez utiliser dans votre projet.
Nous proposons des services de développement Next.js, React et Node.
Prenez rendez-vous avec nous pour discuter de votre projet.
https://github.com/documenso/documenso/blob/main/packages/lib/universal/upload/put-file.ts#L69
https://github.com/vercel/examples/blob/main/solutions/aws-s3-image-upload/README.md
https://github.com/vercel/examples/tree/main/solutions/aws-s3-image-upload
https://github.com/vercel/examples/blob/main/solutions/aws-s3-image-upload/app/page.tsx#L58C5-L76C12
https://github.com/vercel/examples/blob/main/solutions/aws-s3-image-upload/app/api/upload/route.ts
https://github.com/documenso/documenso/blob/main/packages/ui/primitives/document-dropzone.tsx#L157
https://react-dropzone.js.org/
https://github.com/documenso/documenso/blob/main/apps/web/src/app/(dashboard)/documents/upload-document.tsx#L61
https://github.com/documenso/documenso/blob/main/packages/lib/universal/upload/put-file.ts#L22
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!