انتقل إلى المحتوى
HexaTransfer
العودة إلى المدونة
تعمق تقني

Create Your Own نقل الملفات Service: Dev Tutorial

أنشئ a نقل الملفات service from scratch. Backend API design, storage integration, and user interface development tutorial.

لبناء خدمة نقل ملفات خاصة بك، تحتاج إلى خمسة مكونات: واجهة أمامية تقطِّع الملفات وتُشفِّرها اختياريًا في المتصفح، وREST API تُنشئ جلسات رفع وتوقِّع مسبقًا روابط التخزين، وتخزين كائنات متوافق مع S3 (Cloudflare R2 أو Backblaze B2 أو MinIO)، وقاعدة بيانات بيانات وصفية صغيرة (SQLite أو Postgres)، وبروكسي عكسي بـTLS 1.3. باستخدام Node 22 وFastify وVite، يمكنك إطلاق خدمة بأسلوب WeTransfer في نحو 600 سطر من الكود واستضافة الكل بـ5-15 دولارًا شهريًا عند حجم مرور معتدل.

اختيار طبقة التخزين أولًا

اقتصاديات التخزين تصنع أو تكسر خدمة نقل الملفات. تفرض AWS S3 رسوم 0.09 دولار/جيجابايت خروجًا، مما يعني أن ملف شائع بحجم 2 جيجابايت مُشارَك 1,000 مرة يكلفك 180 دولارًا في النطاق الترددي وحده. تفرض Cloudflare R2 صفرًا على الخروج و0.015 دولار/جيجابايت تخزينًا. تفرض Backblaze B2 رسوم 0.01 دولار/جيجابايت خروجًا و0.006 دولار/جيجابايت تخزينًا. لخدمة نقل مجانية، R2 هو الاختيار الواضح. MinIO يتيح استضافة دلالات S3 ذاتيًا على أجهزتك، وهو مفيد إن كان لديك نطاق ترددي وأقراص خاملة.

استخدم URLsمُوقَّعة مسبقًا حتى يرفع العملاء وينزِّلوا مباشرةً من التخزين، دون المرور عبر خادمك. هذا يُبقي API مقيَّدًا بالمعالج لا بالنطاق الترددي.

رسم واجهة API

أبقِ API صغيرة. خمس نقاط نهاية تغطي 95% مما تحتاج:

  • POST /sessions — إنشاء جلسة رفع، إرجاع ID وURLs متعددة الأجزاء مُوقَّعة مسبقًا
  • POST /sessions/:id/complete — إنهاء رفع متعدد الأجزاء، إرجاع رابط المشاركة
  • GET /shares/:id — إرجاع بيانات وصفية للملف (الحجم، الانتهاء، التنزيلات المتبقية)
  • GET /shares/:id/download — إرجاع URL تنزيل مُوقَّعة مسبقًا
  • DELETE /shares/:id — السماح للمالك بالإلغاء المبكر (يتطلب رمز إلغاء)

قيِّد معدَّل POST /sessions بحزم — 20 طلبًا لكل IP في الساعة يكفي. جميع نقاط النهاية تُعيد JSON، وتستخدم Cache-Control: no-store، وتضبط Content-Security-Policy على صفحات HTML.

ربط الخلفية بـFastify

يمنحك Fastify التحقق من مخطط JSON والتسجيل المنطقي و30 ألف طلب/ثانية لكل نواة دون جهد. مثال مُبسَّط لـPOST /sessions:

fastify.post('/sessions', {
  schema: {
    body: {
      type: 'object', required: ['size'],
      properties: {
        size: { type: 'integer', minimum: 1, maximum: 10737418240 },
        contentType: { type: 'string', maxLength: 120 }
      }
    }
  }
}, async (req) => {
  const id = nanoid(16);
  const key = `blobs/${id}`;
  const upload = await s3.createMultipartUpload({
    Bucket: 'transfers', Key: key, ContentType: req.body.contentType
  });
  const partCount = Math.ceil(req.body.size / (8 * 1024 * 1024));
  const urls = await Promise.all(
    Array.from({ length: partCount }, (_, i) =>
      getSignedUrl(s3, new UploadPartCommand({
        Bucket: 'transfers', Key: key, UploadId: upload.UploadId, PartNumber: i + 1
      }), { expiresIn: 3600 }))
  );
  db.prepare('INSERT INTO sessions VALUES (?, ?, ?, ?)')
    .run(id, upload.UploadId, key, Date.now());
  return { id, uploadId: upload.UploadId, partUrls: urls };
});

سقف 10 جيجابايت، أجزاء بحجم 8 ميغابايت، صلاحية URL ساعة واحدة. اضبط حسب مزيج حجم مرورك.

بناء واجهة الرفع

Vite مع React ودون مكتبة واجهة يُوصِلك إلى سحب وإفلات يعمل في نحو 150 سطرًا. الجزء الحاسم هو عامل الرفع: أطلق Web Worker مخصصًا، مرِّر له مقبض File وروابط الأجزاء، واجعله يرفع كل جزء بتزامن ثلاثي. يظل الخيط الرئيسي مستجيبًا، وتصل تحديثات التقدم عبر postMessage.

// worker.js
self.onmessage = async ({ data: { file, partUrls } }) => {
  const partSize = 8 * 1024 * 1024;
  const etags = [];
  for (let i = 0; i < partUrls.length; i++) {
    const blob = file.slice(i * partSize, (i + 1) * partSize);
    const res = await fetch(partUrls[i], { method: 'PUT', body: blob });
    etags[i] = res.headers.get('etag');
    self.postMessage({ type: 'progress', done: i + 1, total: partUrls.length });
  }
  self.postMessage({ type: 'done', etags });
};

أضف حقل كلمة مرور تشتق مفتاحًا عبر PBKDF2 (600,000 تكرار، SHA-256) ومرِّر كل جزء عبر AES-256-GCM قبل PUT. هذا يحوِّل خدمتك من خدمة مُشفَّرة من جانب الخادم إلى خدمة E2EE.

تصميم مخطط قاعدة البيانات

SQLite عبر better-sqlite3 يتعامل مع عشرات الملايين من الصفوف دون اهتزاز. أبقِ المخطط نحيفًا:

CREATE TABLE sessions (
  id TEXT PRIMARY KEY, upload_id TEXT, object_key TEXT, created_at INTEGER
);
CREATE TABLE shares (
  id TEXT PRIMARY KEY, object_key TEXT, size_bytes INTEGER,
  content_type TEXT, expires_at INTEGER, max_downloads INTEGER,
  download_count INTEGER DEFAULT 0, revocation_token TEXT
);
CREATE INDEX idx_shares_expires ON shares(expires_at);

لا جدول مستخدمين، لا بريد إلكتروني، لا سجلات IP. هذا اختيار متعمد — يجعل الامتثال للـRGPD شبه بديهي. يحذف cron ليلي الصفوف المنتهية وكائنات S3 الخاصة بها:

const expired = db.prepare('SELECT object_key FROM shares WHERE expires_at < ?').all(Date.now());
for (const { object_key } of expired) {
  await s3.deleteObject({ Bucket: 'transfers', Key: object_key });
}
db.prepare('DELETE FROM shares WHERE expires_at < ?').run(Date.now());

تطبيق روابط المشاركة والتنزيلات

تبدو روابط المشاركة كـhttps://yourapp.com/f/abc123#k=<base64key> حين تُضيف تشفيرًا من جانب العميل. يُبقي الجزء المجزَّأ (#) المفتاح خارج سجلات الخادم. تجلب صفحة التنزيل /shares/abc123، وتحصل على URL S3 مُوقَّعة مسبقًا، وتدفق البايتات مباشرةً من R2، وتُفكِّك التشفير في المتصفح، وتسلِّم النتيجة إلى StreamSaver.js أو File System Access API.

اضبط Content-Disposition: attachment; filename*=UTF-8''encoded-name حتى يُشغِّل المتصفح حوار الحفظ بدلًا من عرض الملف في المكان. هذا مهم لملفات PDF وHTML وSVG، التي يمكنها جميعًا تنفيذ سكريبتات في سياق المتصفح.

التصلُّب ضد الإساءة

تستقطب خدمات نقل الملفات المسيئين. توقَّع ثلاث فئات: توزيع البرامج الضارة، واستضافة حمولات التصيد، والرفعات المنتهكة لحقوق الملكية. إجراءات الحماية حسب الأثر: قيِّد معدل الرفع لكل IP، واشترط حدًا أدنى للحجم (أقل من 10 كيلوبايت هو شبه دائمًا نفايات)، واحسب SHA-256 للنص المُشفَّر وتحقق منه مقابل قائمة رفض التجزئات المعروفة السيئة، وقدِّم نموذج إبلاغ عن الإساءة سهل الاستخدام. ClamAV على المحتوى المُفكَّك تشفيره يعمل فقط حين يكون المفتاح متاحًا، مما يُبطل E2EE — الإجابة الصحيحة هي حدود المعدل المستقلة عن المحتوى وعمليات الإيقاف السريع الاستجابة.

وقِّع كل URL مُوقَّعة مسبقًا بـTTL قصير (ساعة على الأكثر)، واربط الجلسات برمز مؤقت يُنشئه المتصفح مخزَّن في كوكي Secure; HttpOnly; SameSite=Strict.

النشر والتشغيل بتكلفة منخفضة

خادم Hetzner CX22 بـ10 دولارات (2 vCPU، 4 جيجابايت RAM) يشغِّل Fastify وSQLite وبروكسي Caddy العكسي ويتعامل مع آلاف المستخدمين النشطين. يمنحك Caddy TLS 1.3 تلقائيًا عبر Let's Encrypt. ضع Cloudflare أمامه لحماية DDoS وـWAF المجاني. راقب بـUptime Kuma (مجاني، مستضاف ذاتيًا) وسجِّل إلى stdout الملتقَط بجورنال.

للتكرار الجغرافي، انسخ SQLite مع Litestream إلى R2، وأشِر منطقةً ثانية إلى نفس مخزن الكائنات. التحويل على مستوى DNS. قصة DR كاملة تسع منديلًا.

النهج كله أعلاه يعكس طريقة بناء HexaTransfer — Fastify API، وتخزين R2، وWeb Crypto في المتصفح، وبيانات وصفية SQLite، بدون حسابات. جرِّبها على hexatransfer.com — مجانًا، بدون حساب، بحد أقصى 10 جيجابايت.

ما تتجنبه حتى تحتاج إليه

لا تبنِ حسابات أو فرقًا أو مجلدات في النسخة الأولى. لا تُضف توصيل البريد الإلكتروني. لا تُنفِّذ معاينة الفيديو أو مصغرات الصور (كلاهما يُجبر الخادم على رؤية النص الصريح). لا تُطلق تطبيقًا جوالًا قبل أن يكون الإصدار الويب صلبًا على متصفح Safari الجوال. زحف الميزات الذي أسقط مستنسخات نقل الملفات المبكرة كان دائمًا النمط ذاته: حسابات، ثم حصص تخزين، ثم دفع، ثم خطط فرق، وفجأةً أنت تنافس Dropbox بدلًا من إطلاق أداة نقل سريعة وخاصة. أبقِ السطح صغيرًا والتشفير محكمًا.

أرسل ملفات كبيرة بأمان مع تشفير من طرف إلى طرف

انقل ملفات حتى 10 جيجابايت مجاناً مع تشفير من طرف إلى طرف. لا حاجة لحساب. يتم تشفير ملفاتك في متصفحك قبل الرفع — لا أحد آخر يستطيع قراءتها.

إرسال ملف