You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

12 KiB

راهنمای فنی مهاجرت دریافت و کش احادیث به دیتابیس SQLite آفلاین (Single-File Architecture)

مخاطب: توسعه‌دهنده فلاتر (تیم فرانت‌اند / موبایل)
هدف: حذف پردازش سنگین جیسون احادیث (بیش از ۳۳ مگابایت)، حذف کرش و هنگ رم در Hive، و پیاده‌سازی دریافت مستقیم فایل فشرده SQLite دیتابیس احادیث و تفکیک تفاسیر.


۱. خلاصه تغییرات و چرایی مهاجرت

مشکل قبلی:

  1. دانلود اندپوینت /api/hadis/sync/hadis/ حجمی معادل ۳۳ مگابایت جیسون خام داشت.
  2. پارس کردن این حجم از جیسون و اینسرت در Hive باعث افت فریم، فریز شدن UI و مصرف بیش از ۲۰۰ مگابایت رم در گوشی می‌شد.
  3. تفاسیر دسته‌بندی‌ها به ازای تک‌تک احادیث درون جیسون تکرار می‌شد (یک متن طولانی تفسیر ۱۰ بار در ۱۰ حدیث مختلف یک دسته کپی می‌شد!).

معماری جدید (سرور جنگو):

  1. فایل واحد دیتابیس آماده: سرور یک فایل پایگاه‌داده بهینه، ایندکس‌شده و فشرده با Gzip به نام hadis_offline.sqlite.gz (حجم حدود ۲ تا ۴ مگابایت) آماده کرده است که مستقیماً از طریق Nginx دانلود می‌شود.
  2. تفکیک کامل تفاسیر: تفاسیر دسته‌بندی در یک جدول مجزا قرار گرفته‌اند و متن‌های تکراری حذف شده‌اند.
  3. اندپوینت نسخه پایدار: اپلیکیشن نسخه دیتابیس محلی خود را با سرور مقایسه می‌کند و فقط در صورت تغییر نسخه، فایل دیتابیس را دانلود و جایگزین می‌کند.

۲. اندپوینت‌های جدید و به‌روزشده بک‌اند

الف) اندپوینت اختصاصی دریافت نسخه دیتابیس SQLite:

GET /api/hadis/sync/database/

پاسخ نمونه:

{
  "version": 2,
  "download_url": "https://dovodi.online/media/hadis_db/hadis_offline.sqlite.gz",
  "file_size": 2845620,
  "checksum_md5": "d41d8cd98f00b204e9800998ecf8427e",
  "hadis_count": 1050,
  "categories_count": 48,
  "is_active": true,
  "notes": "به‌روزرسانی احادیث ماه مبارک",
  "created_at": "2026-09-10T10:15:00Z"
}

ب) اندپوینت قبلی نسخه‌بندی (Backward Compatible):

اندپوینت /api/hadis/sync/version/ نیز حفظ شده و کلید sqlite_database را در بدنه خود برمی‌گرداند تا در صورتی که ساختار فعلی فلاتر به آن متصل است، کدی شکسته نشود.


۳. ساختار جداول درون فایل SQLite (hadis_offline.sqlite)

فایل دیتابیس دانلود شده شامل جداول استاندارد زیر است:

۱. جدول hadiths (احادیث)

نام ستون نوع داده توضیحات
id INTEGER (PK) شناسه حدیث
slug TEXT (INDEX) اسلاگ یکتای حدیث
category_id INTEGER (INDEX) شناسه دسته‌بندی
category_slug TEXT اسلاگ دسته
title TEXT عنوان چندزبانه (JSON)
title_narrator TEXT راوی عنوان (JSON)
text TEXT متن عربی حدیث
translation_json TEXT لیست ترجمه‌ها (JSON)
address TEXT آدرس منبع حدیث (JSON)
share_link TEXT لینک اشتراک‌گذاری
hadis_status_id INTEGER شناسه وضعیت
status_title TEXT عنوان وضعیت
status_color TEXT رنگ وضعیت
status_main_color_code TEXT کد رنگ اصلی
status_text TEXT متن وضعیت
tags_json TEXT لیست تگ‌ها
references_json TEXT لیست منابع و کتاب‌ها
reference_images_json TEXT تصاویر منابع
narrators_json TEXT سلسله راویان (Transmitters)
explanations_json TEXT توضیحات و شروح

۲. جدول category_interpretations (تفاسیر دسته‌بندی - تفکیک‌شده)

نام ستون نوع داده توضیحات
id INTEGER (PK) شناسه تفسیر
category_id INTEGER (INDEX) شناسه دسته‌بندی مربوطه
title TEXT عنوان تفسیر
slug TEXT اسلاگ تفسیر
narrator TEXT راوی تفسیر
text TEXT متن تفصیلی تفسیر
translation TEXT ترجمه
references_json TEXT کتاب‌ها و منابع تفسیر
links_json TEXT پیوندها

۳. جدول hadith_corrections (تصحیحات حدیث)

نام ستون نوع داده توضیحات
id INTEGER (PK) شناسه تصحیح
hadis_id INTEGER (INDEX) شناسه حدیث متصل
title TEXT عنوان
slug TEXT اسلاگ
narrator TEXT راوی
description TEXT شرح
translation TEXT ترجمه
references_json TEXT منابع تصحیح
images_json TEXT تصاویر مدارک
links_json TEXT پیوندها

۴. جدول categories (دسته‌ها)

شامل id, title, slug, source_type, sect_type.


۴. چرخه سناریوی دریافت و کش در فلاتر (Implementation Flow)

[Start App]
   │
   ▼
1. فراخوانی GET /api/hadis/sync/database/
   │
   ▼
2. آیا دیتابیس محلی وجود ندارد یا version سرور > version ذخیره شده در SharedPreferences است؟
   ├─► خیر: مستقیماً از SQLite محلی لود کن (آفلاین ۱۰۰٪ در ۰ ثانیه).
   │
   └─► بله:
        3. نمایش لودینگ / پروگرس‌بار دانلود دیتابیس
        4. دانلود فایل hadis_offline.sqlite.gz از Nginx با Dio (با قابلیت onReceiveProgress)
        5. بررسی سلامت فایل با هش MD5 (اختیاری ولی توصیه شده)
        6. آنزیپ کردن (decompress) فایل Gzip به hadis.sqlite در دایرکتوری getDatabasesPath()
        7. پاک کردن فایل موقت .gz
        8. ذخیره شماره نسخه جدید در SharedPreferences
        9. رفرش کردن کوبیت‌ها و آماده‌سازی نمایش

۵. راهنمای گام‌به‌گام پیاده‌سازی کدهای فلاتر

گام اول: وابستگی‌های مورد نیاز در pubspec.yaml

dependencies:
  sqflite: ^2.3.3+1
  path: ^1.9.0
  path_provider: ^2.1.3
  archive: ^3.6.1   # برای باز کردن فایل Gzip
  crypto: ^3.0.3    # برای بررسی هش MD5

گام دوم: ایجاد کلاس مدیریت دیتابیس SQLite

یک فایل در مسیر lib/src/features/dobodbi/hadith/data/datasources/local/hadith_database_helper.dart ایجاد کنید:

import 'dart:io';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:archive/archive.dart';

class HadithDatabaseHelper {
  static const String _dbFileName = 'hadis.sqlite';
  static Database? _database;

  static Future<Database> get database async {
    if (_database != null && _database!.isOpen) return _database!;
    _database = await _initDb();
    return _database!;
  }

  static Future<String> getDatabasePath() async {
    final dbFolder = await getDatabasesPath();
    return join(dbFolder, _dbFileName);
  }

  static Future<Database> _initDb() async {
    final path = await getDatabasePath();
    return await openDatabase(path, readOnly: true);
  }

  /// اکسترکت فایل فشرده دانلود شده مستقیماً در دایرکتوری دیتابیس
  static Future<void> replaceDatabaseWithGzip(File gzFile) async {
    // اگر دیتابیس باز است، ابتدا بسته شود
    if (_database != null && _database!.isOpen) {
      await _database!.close();
      _database = null;
    }

    final targetPath = await getDatabasePath();
    final bytes = await gzFile.readAsBytes();
    
    // Gzip decode
    final decompressedData = GZipDecoder().decodeBytes(bytes);

    // نوشتن فایل دیتابیس نهایی
    final targetFile = File(targetPath);
    await targetFile.writeAsBytes(decompressedData, flush: true);

    // راه‌اندازی مجدد اتصال
    _database = await openDatabase(targetPath, readOnly: true);
  }
}

گام سوم: سرویس دانلود و همگام‌سازی (Sync Manager)

در hadith_datasource.dart متد دانلود اضافه کنید:

Future<void> syncHadithDatabase({
  required String downloadUrl,
  required int targetVersion,
  required Function(int received, int total) onProgress,
}) async {
  final tempDir = await getTemporaryDirectory();
  final tempGzPath = '${tempDir.path}/hadis_temp.sqlite.gz';
  final tempFile = File(tempGzPath);

  // دانلود مستقیم فایل با قابلیت گزارش پیشرفت
  await _dio.download(
    downloadUrl,
    tempGzPath,
    onReceiveProgress: onProgress,
  );

  // اکسترکت در دیتابیس
  await HadithDatabaseHelper.replaceDatabaseWithGzip(tempFile);

  // حذف فایل تمپ
  if (await tempFile.exists()) {
    await tempFile.delete();
  }

  // ذخیره نسخه جدید در حافظه محلی
  final prefs = await SharedPreferences.getInstance();
  await prefs.setInt('hadith_db_version', targetVersion);
}

گام چهارم: کوئری گرفتن احادیث و تفاسیر از SQLite

در دیتاسورس محلی (HadithLocalDataSource):

۱. دریافت احادیث یک دسته‌بندی با صفحه‌بندی فوق‌سریع:

Future<List<HadithModel>> getHadithsByCategory(int categoryId, {int limit = 20, int offset = 0}) async {
  final db = await HadithDatabaseHelper.database;
  final results = await db.query(
    'hadiths',
    where: 'category_id = ?',
    whereArgs: [categoryId],
    limit: limit,
    offset: offset,
  );

  return results.map((row) => _mapRowToHadithModel(row)).toList();
}

۲. دریافت تفاسیر دسته‌بندی (تفکیک‌شده):

Future<List<HadithInterpretationModel>> getCategoryInterpretations(int categoryId) async {
  final db = await HadithDatabaseHelper.database;
  final results = await db.query(
    'category_interpretations',
    where: 'category_id = ?',
    whereArgs: [categoryId],
  );

  return results.map((row) => HadithInterpretationModel.fromSqliteRow(row)).toList();
}
Future<List<HadithModel>> searchHadiths(String keyword) async {
  final db = await HadithDatabaseHelper.database;
  final results = await db.rawQuery('''
    SELECT * FROM hadiths 
    WHERE text LIKE ? OR translation_json LIKE ?
    LIMIT 50
  ''', ['%$keyword%', '%$keyword%']);

  return results.map((row) => _mapRowToHadithModel(row)).toList();
}

۶. مزایای این تغییر برای اپ فلاتر

  1. کاهش مصرف اینترنت: حجم دانلود کل دیتابیس از ۳۳ مگابایت به کمتر از ۳ مگابایت می‌رسد (۹۰٪ صرفه‌جویی).
  2. زمان سینک صفر: دیگر نیازی به پارس کردن حلقه‌ای جیسون در فلاتر و ایجاد آبجکت‌های Hive نیست؛ به محض Unzip شدن، دیتابیس بلافاصله آماده استفاده است.
  3. مصرف رم بسیار ناچیز: به جای نگهداری تمام احادیث در رم (Boxهای Hive)، داده‌ها با صفحه‌بندی (LIMIT و OFFSET) بر اساس نیاز اسکرول کاربر از دیسک خوانده می‌شوند.
  4. توقف کامل کرش‌های دیتابیس آفلاین در نسخه‌های ضعیف اندروید/iOS.