برنامه نویسی

UploadThing: یک راه حل مدرن آپلود فایل برای برنامه های Next.js

مقدمه

UploadThing یک راه حل آپلود فایل منبع باز است که به طور خاص برای برنامه های Next.js طراحی شده است. این به توسعه دهندگان یک روش ایمن و کارآمد برای مدیریت آپلود فایل ارائه می دهد و در عین حال ویژگی هایی مانند اعتبار سنجی فایل، تبدیل و ادغام مستقیم با چارچوب های محبوب را ارائه می دهد.

بررسی اجمالی فنی

UploadThing در هسته خود از سه جزء اصلی تشکیل شده است:

  1. روتر فایل سمت سرور
  2. قطعات و قلاب های سمت مشتری
  3. نقاط پایانی API ایمن تایپ

نصب و راه اندازی اولیه

ابتدا بسته های مورد نیاز را نصب کنید:

npm install uploadthing @uploadthing/react
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

یک روتر فایل ایجاد کنید (معمولاً در app/api/uploadthing/core.ts):

import { createUploadthing, type FileRouter } from "uploadthing/server";

const f = createUploadthing();

export const uploadRouter = {
  // Example "profile picture upload" route - these can be named whatever you want!
  profilePicture: f(["image"])
    .middleware(({ req }) => auth(req))
    .onUploadComplete((data) => console.log("file", data)),

  // This route takes an attached image OR video
  messageAttachment: f(["image", "video"])
    .middleware(({ req }) => auth(req))
    .onUploadComplete((data) => console.log("file", data)),

  // Takes exactly ONE image up to 2MB
  strictImageAttachment: f({
    image: { maxFileSize: "2MB", maxFileCount: 1, minFileCount: 1 },
  })
    .middleware(({ req }) => auth(req))
    .onUploadComplete((data) => console.log("file", data)),

  // Takes up to 4 2mb images and/or 1 256mb video
  mediaPost: f({
    image: { maxFileSize: "2MB", maxFileCount: 4 },
    video: { maxFileSize: "256MB", maxFileCount: 1 },
  })
    .middleware(({ req }) => auth(req))
    .onUploadComplete((data) => console.log("file", data)),

  // Takes up to 4 2mb images, and the client will not resolve
  // the upload until the `onUploadComplete` resolved.
  withAwaitedServerData: f(
    { image: { maxFileSize: "2MB", maxFileCount: 4 } },
    { awaitServerData: true },
  )
    .middleware(({ req }) => auth(req))
    .onUploadComplete((data) => {
      return { foo: "bar" as const };
    }),
} satisfies FileRouter;

export type UploadRouter = typeof uploadRouter;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اینها مسیرهایی هستند که با کمک راهنما ایجاد می کنید createUploadthing. آنها را به عنوان “نقاط پایانی” برای آنچه کاربران شما می توانند آپلود کنند، در نظر بگیرید. یک شی با مسیرهای فایل یک مسیریاب فایل می سازد که در آن کلیدها (اسلاگ ها) در شی نام نقاط پایانی شما هستند.

پیکربندی مسیر:

را f تابع دو آرگومان می گیرد. اولین مورد می تواند آرایه ای از FileType، یا یک نقشه برداری رکورد هر کدام FileType با پیکربندی مسیر پیکربندی مسیر به کنترل دقیق تری اجازه می دهد، به عنوان مثال چه فایل هایی را می توان آپلود کرد و چه تعداد از آنها را می توان برای یک آپلود معین آپلود کرد. نحو آرایه به اعمال پیش‌فرض‌ها برای همه انواع فایل‌ها بازگشته است.

الف FileType می تواند هر نوع وب MIME معتبر ↗ باشد. به عنوان مثال: استفاده کنید application/json تا فقط فایل های JSON آپلود شوند.

علاوه بر این، می توانید هر یک از انواع سفارشی زیر را پاس کنید: image، video، audio، pdf یا text. اینها کوتاه نویسی هستند که به شما امکان می دهد نوع فایل را بدون تعیین نوع دقیق MIME مشخص کنید. در نهایت، وجود دارد blob که هر نوع فایلی را اجازه می دهد.

گزینه های مسیر:

استدلال دوم به f تابع یک شی اختیاری از گزینه های مسیر است. این پیکربندی‌ها تنظیمات کلی را ارائه می‌کنند که بر نحوه رفتار مسیر آپلود تأثیر می‌گذارند.

گزینه های مسیر موجود:

  • awaitServerData: (بولی، پیش‌فرض: نادرست) وقتی روی تنظیم شود true، مشتری منتظر سرور خواهد ماند onUploadComplete کنترل کننده برای اتمام و بازگشت داده ها قبل از اجرا onClientUploadComplete. این زمانی مفید است که قبل از ادامه عملیات سمت سرویس گیرنده باید از تکمیل پردازش سمت سرور اطمینان حاصل کنید.

مثال:

  const uploadRouter = {
    withServerData: f(
      { image: { maxFileSize: "2MB" } },
      { awaitServerData: true }
    )
      .middleware(({ req }) => ({ userId: "123" }))
      .onUploadComplete(async ({ metadata }) => {
        // This will complete before client-side callback
        const result = await processImage(metadata);
        return { processedUrl: result.url };
      }),
  }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

روش های مسیر

را f تابع یک شی سازنده را برمی گرداند که به شما امکان می دهد متدهای زیر را زنجیره بزنید:

ورودی

ورودی کاربر از مشتری را با استفاده از اعتبار سنجی طرحواره تأیید می کند. این روش تضمین می کند که هر داده اضافی ارسال شده در کنار آپلود فایل با مشخصات شما مطابقت دارد.

تایید کننده های پشتیبانی شده:

  • Zod (≥3)
  • اثر/شما (≥3.10، با محدودیت)
  • مشخصات طرحواره استاندارد (به عنوان مثال، Valibot ≥1.0، ArkType ≥2.0)

مثال با اعتبارسنجی پیچیده:

   f(["image"])
     .input(
       z.object({
         title: z.string().min(1).max(100),
         tags: z.array(z.string()),
         isPublic: z.boolean()
       })
     )
     .middleware(async ({ req, input }) => {
       // input is fully typed with:
       // { title: string; tags: string[]; isPublic: boolean }
       return { metadata: input };
     })
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

میان افزار

مجوز و برچسب گذاری ابرداده را کنترل می کند. این جایی است که شما بررسی های احراز هویت را انجام می دهید و هر گونه داده مورد نیاز برای فرآیند آپلود را آماده می کنید.

مثال با احراز هویت و فراداده جامع:

   f(["image"])
     .middleware(async ({ req, res }) => {
       const user = await currentUser();
       if (!user) throw new UploadThingError("Authentication required");

       // You can perform additional checks
       const userPlan = await getUserSubscriptionPlan(user.id);
       if (userPlan === "free" && await getUserUploadCount(user.id) > 10) {
         throw new UploadThingError("Free plan limit reached");
       }

       return { 
         userId: user.id,
         planType: userPlan,
         timestamp: new Date().toISOString()
       };
     })
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

خطای onUpload

هنگامی که یک خطای آپلود رخ می دهد تماس گرفته می شود. از این برای رسیدگی به خطاها و انجام هرگونه پاکسازی یا ثبت گزارش لازم استفاده کنید.

پارامترها:

  • error: UploadThingError (شامل پیام خطا و کد)
  • fileKey: رشته (شناسه منحصر به فرد برای آپلود ناموفق)

مثال:

   f(["image"])
     .onUploadError(async ({ error, fileKey }) => {
       await logger.error("Upload failed", {
         error: error.message,
         fileKey,
         code: error.code
       });
       await cleanupFailedUpload(fileKey);
     })
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

onUploadComplete

کنترل کننده نهایی برای آپلودهای موفق اینجاست که می توانید فایل آپلود شده را پردازش کرده و هر گونه عملیات لازم پس از آپلود را انجام دهید.

پارامترها:

  • metadata: داده های ارسال شده از میان افزار
  • file: شی UploadedFileData حاوی:

    • name: نام فایل اصلی
    • size: اندازه فایل بر حسب بایت
    • key: شناسه فایل منحصر به فرد
    • url: URL عمومی فایل آپلود شده

مثال با مدیریت جامع:

   f(["image"])
     .onUploadComplete(async ({ metadata, file }) => {
       // Store file reference in database
       await db.files.create({
         data: {
           userId: metadata.userId,
           fileName: file.name,
           fileSize: file.size,
           fileUrl: file.url,
           uploadedAt: metadata.timestamp
         }
       });

       // Trigger any post-upload processing
       await imageProcessor.optimize(file.url);

       // Return data to client if awaitServerData is true
       return {
         fileId: file.key,
         accessUrl: file.url,
         processedAt: new Date().toISOString()
       };
     })
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

آپلود فایل ها

UploadThing دو روش اصلی برای آپلود فایل ها ارائه می دهد: آپلودهای سمت مشتری و آپلودهای سمت سرور. هر رویکرد مزایای و موارد استفاده خاص خود را دارد.

آپلودهای سمت مشتری

آپلودهای سمت مشتری رویکرد پیشنهادی هستند زیرا چندین مزیت را ارائه می دهند:

  • کاهش هزینه های سرور (بدون هزینه ورودی/خروجی)
  • انتقال مستقیم فایل به UploadThing
  • اعتبار سنجی داخلی و ایمنی نوع

فرآیند به صورت زیر عمل می کند:

  1. مشتری درخواست آپلود را آغاز می کند
  2. سرور URL های تعیین شده را تولید می کند
  3. مشتری مستقیماً در UploadThing بارگذاری می کند
  4. سرور پس از تکمیل پاسخ تماس دریافت می کند

اجرای مثال با استفاده از React:

import { UploadButton } from "@uploadthing/react";

export default function UploadPage() {
  return (
    <UploadButton
      endpoint="imageUploader"
      onClientUploadComplete={(res) => {
        console.log("Files: ", res);
        alert("Upload Completed");
      }}
      onUploadError={(error: Error) => {
        alert(`ERROR! ${error.message}`);
      }}
    />
  );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

آپلودهای سمت سرور

آپلودهای سمت سرور زمانی مفید هستند که شما نیاز دارید:

  • فایل‌ها را قبل از آپلود پردازش یا اعتبارسنجی کنید
  • تولید فایل روی سرور
  • رسیدگی به الزامات امنیتی خاص

مثال با استفاده از UTApi:

import { UTApi } from "uploadthing/server";

const utapi = new UTApi();

async function uploadServerSideFile(file: File) {
  try {
    const response = await utapi.uploadFiles(file);
    return response.data;
  } catch (error) {
    console.error("Upload failed:", error);
    throw error;
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پیکربندی آپلود

هر دو آپلود مشتری و سرور از گزینه های پیکربندی مختلف پشتیبانی می کنند:

اعتبار سنجی فایل:

const uploadRouter = {
  strictImageUpload: f({
    image: {
      maxFileSize: "4MB",
      maxFileCount: 1,
      minFileCount: 1
    }
  })
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

فراداده سفارشی:

f(["image"])
  .middleware(({ req }) => ({
    userId: req.userId,
    uploadedAt: new Date().toISOString()
  }))
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بارگذاری تماس های برگشتی:

f(["image"])
  .onUploadComplete(async ({ metadata, file }) => {
    // Handle successful upload
    await db.images.create({
      data: {
        userId: metadata.userId,
        url: file.url,
        name: file.name
      }
    });
  })
  .onUploadError(({ error }) => {
    // Handle upload error
    console.error("Upload failed:", error);
  });
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

آپلودهای قابل ازسرگیری

UploadThing از آپلودهای مجدد برای فایل های بزرگ پشتیبانی می کند:

const upload = async (file: File, presignedUrl: string) => {
  // Get the current range start
  const rangeStart = await fetch(presignedUrl, { 
    method: "HEAD" 
  }).then((res) =>
    parseInt(res.headers.get("x-ut-range-start") ?? "0", 10)
  );

  // Continue upload from last successful byte
  await fetch(presignedUrl, {
    method: "PUT",
    headers: {
      Range: `bytes=${rangeStart}-`,
    },
    body: file.slice(rangeStart),
  });
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ملاحظات امنیتی

  1. امضای URL: همه URL های آپلود با کلید API شما امضا شده و دارای مهرهای زمانی انقضا هستند
  2. اعتبار سنجی فایل: اعتبار سنجی کامل را در مسیرهای فایل خود اجرا کنید
  3. احراز هویت: همیشه قبل از اجازه آپلود، از میان افزار برای احراز هویت کاربران استفاده کنید

نمونه پیکربندی امن:

const uploadRouter = {
  secureUpload: f(["image"])
    .middleware(async ({ req }) => {
      const user = await auth(req);
      if (!user) throw new Error("Unauthorized");

      const userQuota = await checkUserQuota(user.id);
      if (!userQuota.hasRemaining) {
        throw new Error("Upload quota exceeded");
      }

      return { userId: user.id };
    })
    .onUploadComplete(async ({ metadata, file }) => {
      await saveToDatabase(metadata.userId, file);
      await updateUserQuota(metadata.userId);
    })
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

کار با فایل ها

پس از آپلود موفقیت آمیز فایل ها در UploadThing، راه های مختلفی برای کار و دسترسی به این فایل ها وجود دارد. در اینجا یک راهنمای جامع در مورد عملیات فایل آورده شده است.

دسترسی به فایل های عمومی

فایل‌ها از طریق CDN UploadThing با استفاده از الگوی URL زیر ارائه می‌شوند:

https://.ufs.sh/f/
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر یک را تنظیم کرده اید customId در حین آپلود، می توانید با استفاده از:

https://.ufs.sh/f/
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مهم است: هرگز از URL های ارائه دهنده ذخیره سازی خام استفاده نکنید (به عنوان مثال، https://bucket.s3.region.amazonaws.com/). UploadThing ممکن است ارائه‌دهندگان یا سطل‌های ذخیره‌سازی را تغییر دهد و این URL‌ها را غیرقابل اعتماد کند.

راه اندازی بهینه سازی تصویر (مثال Next.js)

/** @type {import('next').NextConfig} */
export default {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: ".ufs.sh",
        pathname: "/f/*",
      },
    ],
  },
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مرجع UTApi

UploadThing API Helper برای استفاده در سمت سرور طراحی شده است. در حالی که یک رابط REST API را ارائه می دهد، عملکرد و ایمنی نوع پیشرفته ای را ارائه می دهد.

توجه: تماس‌های API خارجی معمولاً کندتر از پرس‌وجو از پایگاه داده شما هستند. توصیه می شود داده های فایل لازم را در پایگاه داده خود ذخیره کنید onUploadComplete() یا بعد از استفاده uploadFiles()، به جای تکیه بر API برای جریان داده اصلی.

سازنده

مقداردهی اولیه یک نمونه از UTApi:

import { UTApi } from "uploadthing/server";

export const utapi = new UTApi({
  // ...options
});
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

گزینه های پیکربندی:

  • واکشی: تابع واکشی سفارشی
  • نشانه: نشانه UploadThing شما (پیش‌فرض: env.UPLOADTHING_TOKEN)
  • logLevel: پرحرفی گزارش (خطا | هشدار | اطلاعات | اشکال زدایی | ردیابی)
  • logFormat: فرمت لاگ (json | logFmt | ساختاریافته | زیبا)
  • defaultKeyType: نوع کلید پیش فرض برای عملیات فایل ('fileKey' | 'customId')
  • apiUrl: URL API UploadThing (پیش‌فرض: https://api.uploadthing.com)
  • ingestUrl: UploadThing Ingest URL API

عملیات فایل

آپلود فایل ها

import { utapi } from "~/server/uploadthing";

async function uploadFiles(formData: FormData) {
  "use server";
  const files = formData.getAll("files");
  const response = await utapi.uploadFiles(files);
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

آپلود فایل ها از URL

const fileUrl = "https://test.com/some.png";
const uploadedFile = await utapi.uploadFilesFromUrl(fileUrl);

const fileUrls = ["https://test.com/some.png", "https://test.com/some2.png"];
const uploadedFiles = await utapi.uploadFilesFromUrl(fileUrls);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

حذف فایل ها

await utapi.deleteFiles("2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg");
await utapi.deleteFiles([
  "2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg",
  "1649353b-04ea-48a2-9db7-31de7f562c8d_image2.jpg",
]);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

لیست فایل ها

const files = await utapi.listFiles({
  limit: 500,  // optional, default: 500
  offset: 0    // optional, default: 0
});
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تغییر نام فایل ها

await utapi.renameFiles({
  key: "2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg",
  newName: "myImage.jpg",
});

// Batch rename
await utapi.renameFiles([
  {
    key: "2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg",
    newName: "myImage.jpg",
  },
  {
    key: "1649353b-04ea-48a2-9db7-31de7f562c8d_image2.jpg",
    newName: "myOtherImage.jpg",
  },
]);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

دریافت URL امضا شده

const fileKey = "2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg";
const url = await utapi.getSignedURL(fileKey, {
  expiresIn: 60 * 60, // 1 hour
  // or use time strings:
  // expiresIn: '1 hour',
  // expiresIn: '3d',
  // expiresIn: '7 days',
});
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ACL را به روز کنید

// Make a single file public
await utapi.updateACL(
  "2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg",
  "public-read"
);

// Make multiple files private
await utapi.updateACL(
  [
    "2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg",
    "1649353b-04ea-48a2-9db7-31de7f562c8d_image2.jpg",
  ],
  "private"
);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

دسترسی به فایل های خصوصی

برای فایل‌هایی که با کنترل‌های دسترسی محافظت می‌شوند، باید URL‌های تعیین‌شده کوتاه مدت ایجاد کنید. دو راه برای این کار وجود دارد:

با استفاده از UTApi:

import { UTApi } from "uploadthing/server";

const utapi = new UTApi();

async function getFileAccess(fileKey: string) {
  const signedUrl = await utapi.getSignedUrl(fileKey, {
    expiresIn: "1h" // Optional expiration time
  });
  return signedUrl;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با استفاده از REST API Endpoint:

async function requestFileAccess(fileKey: string) {
  const response = await fetch("/api/requestFileAccess", {
    method: "POST",
    body: JSON.stringify({ fileKey })
  });
  const { signedUrl } = await response.json();
  return signedUrl;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بهترین شیوه ها

مدیریت URL:

  • همیشه از URL های CDN ارائه شده توسط UploadThing استفاده کنید
  • کلیدهای فایل را به جای URL های کامل در پایگاه داده خود ذخیره کنید
  • URL های تعیین شده را بر اساس درخواست برای فایل های خصوصی ایجاد کنید

امنیت:

  • کنترل های دسترسی مناسب را در میان افزار خود پیاده کنید
  • از زمان انقضای کوتاه برای URL های تعیین شده استفاده کنید
  • قبل از ایجاد URLهای امضا شده، مجوزهای دسترسی به فایل را تأیید کنید

عملکرد:

  • از CDN برای تحویل بهینه فایل استفاده کنید
  • اجرای کش برای فایل هایی که اغلب به آنها دسترسی دارند را در نظر بگیرید
  • از تنظیمات مناسب بهینه سازی تصویر استفاده کنید

اجرای نمونه ای که این شیوه ها را ترکیب می کند:

const fileManager = {
  async getFileUrl(fileKey: string, userId: string) {
    // Check user permissions
    const hasAccess = await checkUserFileAccess(userId, fileKey);
    if (!hasAccess) {
      throw new Error("Unauthorized access");
    }

    // Get cached URL if available
    const cachedUrl = await cache.get(`file:${fileKey}`);
    if (cachedUrl) return cachedUrl;

    // Generate new signed URL
    const signedUrl = await utapi.getSignedUrl(fileKey, {
      expiresIn: "1h"
    });

    // Cache the URL (for slightly less than expiration time)
    await cache.set(`file:${fileKey}`, signedUrl, 50 * 60); // 50 minutes

    return signedUrl;
  },

  async deleteUserFile(fileKey: string, userId: string) {
    // Verify ownership
    const isOwner = await verifyFileOwnership(userId, fileKey);
    if (!isOwner) {
      throw new Error("Unauthorized deletion");
    }

    // Delete file
    await utapi.deleteFiles(fileKey);

    // Clean up database records
    await db.files.delete({
      where: { fileKey }
    });

    // Clear cache
    await cache.del(`file:${fileKey}`);
  }
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه گیری

UploadThing یک راه حل قوی و ایمن برای مدیریت آپلود فایل در برنامه های Next.js ارائه می دهد. نقاط قوت اصلی آن عبارتند از:

  • تجربه توسعه دهنده: API های ایمن نوع و ادغام بصری با اجزای React
  • انعطاف پذیری: پشتیبانی از آپلودهای سمت سرویس گیرنده و سرور با گردش کار قابل تنظیم
  • امنیت: اعتبار سنجی فایل داخلی، کنترل های دسترسی، و امضای امن URL
  • عملکرد: تحویل با پشتوانه CDN و بارگذاری مجدد برای فایل های بزرگ

چه در حال ساخت یک ویژگی آپلود تصویر ساده یا یک سیستم مدیریت فایل پیچیده باشید، UploadThing ابزارها و انعطاف‌پذیری لازم برای پیاده‌سازی مدیریت امن و کارآمد فایل در برنامه‌های شما را ارائه می‌دهد.

برای اطلاعات بیشتر و به‌روزرسانی‌ها، از اسناد رسمی UploadThing دیدن کنید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا