# راهنمای فنی مهاجرت دریافت و کش احادیث به دیتابیس 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: ```http GET /api/hadis/sync/database/ ``` **پاسخ نمونه:** ```json { "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` ```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` ایجاد کنید: ```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 get database async { if (_database != null && _database!.isOpen) return _database!; _database = await _initDb(); return _database!; } static Future getDatabasePath() async { final dbFolder = await getDatabasesPath(); return join(dbFolder, _dbFileName); } static Future _initDb() async { final path = await getDatabasePath(); return await openDatabase(path, readOnly: true); } /// اکسترکت فایل فشرده دانلود شده مستقیماً در دایرکتوری دیتابیس static Future 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` متد دانلود اضافه کنید: ```dart Future 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`): #### ۱. دریافت احادیث یک دسته‌بندی با صفحه‌بندی فوق‌سریع: ```dart Future> 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(); } ``` #### ۲. دریافت تفاسیر دسته‌بندی (تفکیک‌شده): ```dart Future> 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(); } ``` #### ۳. جستجوی فوق‌سریع متنی (Full Text Search): ```dart Future> 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.**