🏗️ Bölüm 2: Temel Cron Sistemi Mimarisi
🎯 Sistem Hedefi
✅ Ana Amaçlar
-
Otomatik blog üretimi (manuel müdahale sıfır)
-
Draft havuzu yönetimi (asla tükenmez)
-
Queue sistemi kullanımı (arka plan işlem)
-
Log & monitoring (tam takip)
🔄 Sistem Akış Mantığı
1
Cron Job Tetiklenir
Laravel scheduler belirlenen saatlerde çalışır
↓
2
Draft Kontrolü
Sistemde kaç adet hazır draft var? (is_generated = false)
↓
3
Karar Noktası
• Draft > 10 → Rastgele draft seç, blog üret
• Draft ≤ 10 → Blog üret + 100 yeni draft üret
• Draft = 0 → Önce 100 draft üret, sonra blog
↓
4
Queue Job Tetiklenir
GenerateBlogFromDraftJob arka planda başlar
↓
5
Draft Kullanıldı Olarak İşaretle
is_generated = true, is_selected = true
↓
6
Otomatik Draft Yenileme (İsteğe Bağlı)
Draft havuzu 10'un altına düştüyse 100 yeni draft üret
↓
7
Log & Monitoring
İşlem kaydedilir, başarı/hata durumu loglanır
⚠️ Temel Sistemin Sorunları
🔴 Eksiklikler
-
Sabit Frekans: Her saat başı çalışır (tenant ayarı yok)
-
Settings Yok: blog_ai_enabled, daily_count kullanılmıyor
-
Tenant-Aware Değil: Tüm tenant'lar aynı frekans
-
Esneklik Yok: 1-8 arası seçim imkanı yok
⚙️ Bölüm 3: Settings Entegrasyonu & Tenant-Aware
🗂️ Settings Management Yapısı
SettingGroup (Central DB)
Setting grupları - Tüm tenant'lar için ortak tanımlar
Model: SettingGroup::class
Tablo: settings_groups (Central DB)
Connection: CentralConnection trait
Örnek: "Blog - Yapay Zeka" (ID: 18)
Kolonlar:
- id
- parent_id
- name (JSON: tr, en, ar)
- slug
- description (JSON)
- icon
- layout (JSON: tabs, fields, order)
Setting (Central DB)
Ayar tanımları - Hangi ayarlar var, tipleri ne?
Model: Setting::class
Tablo: settings (Central DB)
Connection: CentralConnection trait
Kolonlar:
- id
- group_id
- label (JSON: tr, en, ar)
- key (unique)
- type (text, number, checkbox, select, textarea)
- default_value
- options (JSON - select için)
Örnek Ayarlar:
• blog_ai_enabled (checkbox)
• blog_ai_daily_count (select: 1-8)
• blog_ai_auto_publish (checkbox)
SettingValue (Tenant DB)
Ayar değerleri - Her tenant'ın kendi değerleri
Model: SettingValue::class
Tablo: settings_values (Tenant DB)
Connection: Tenant connection (dynamic)
Kolonlar:
- id
- setting_id (Central setting'e referans)
- value (actual value)
Örnek Data:
• setting_id=130, value=1 (blog_ai_enabled=true)
• setting_id=131, value=4 (blog_ai_daily_count=4)
• setting_id=132, value=1 (blog_ai_auto_publish=true)
📊 Blog AI Settings (Group ID: 18)
| Setting Key |
Tip |
Varsayılan |
Kullanım |
blog_ai_enabled |
checkbox |
false |
Sistem aktif/pasif |
blog_ai_daily_count |
select (1-8) |
4 |
Günlük blog sayısı |
blog_ai_auto_publish |
checkbox |
true |
Otomatik yayınla |
blog_ai_topic_source |
select |
otomatik |
Konu kaynağı |
blog_ai_manual_topics |
textarea |
null |
Manuel konular |
blog_ai_professional_only |
checkbox |
false |
B2B profesyonel ton |
🔄 Tenant-Aware Cron Akışı
1
Tenant Loop
Tüm aktif tenant'ları döngüye al
↓
2
Tenant Context Switch
tenancy()->initialize($tenant) - Tenant DB'ye geç
↓
3
Settings Check
• blog_ai_enabled = true mı? → Hayırsa skip
• blog_ai_daily_count kaç? → Frekans hesapla
• blog_ai_auto_publish aktif mi? → Yayın durumu
↓
4
Schedule Matching
Şimdiki saat bu tenant'ın schedule'ında var mı?
↓
5
Blog Generation
Evetse → Blog üret, auto_publish ayarına göre kaydet
🎯 Bölüm 5: Final Karar & Saat Planlaması
⚙️ blog_ai_daily_count Settings
Final Konfigürasyon
Setting Key: blog_ai_daily_count
Label: "Günlük Blog Sayısı"
Type: select
Options: [1, 2, 3, 4, 5, 6, 8]
Default: 4
Description: "Her gün kaç blog yazısı üretilsin?
• SEO Optimal: 4 blog/gün (120/ay)
• Maksimum Güvenli: 8 blog/gün (240/ay)"
✅ Neden 1-8?
-
SEO Güvenli: 8 blog/gün = 240 blog/ay (sınırda ama güvenli)
-
Kalite: Her blog yeterli creativit ile oluşturulabilir
-
Performans: hourly() ile mükemmel uyum
-
Esneklik: 1 ile 8 arası geniş aralık
🕐 Saat Planlamaları
Günde 1 Blog
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Saatler: [0] → Gece yarısı
Günde 2 Blog
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Saatler: [0, 12] → Her 12 saatte
Günde 3 Blog
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Saatler: [0, 8, 16] → Her 8 saatte
Günde 4 Blog B2B OPTİMAL
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Saatler: [0, 6, 12, 18] → Her 6 saatte
Günde 5 Blog
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Saatler: [0, 5, 10, 15, 20] → ~Her 5 saatte
Günde 6 Blog
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Saatler: [0, 4, 8, 12, 16, 20] → Her 4 saatte
Günde 8 Blog SEO MAXİMUM
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Saatler: [0, 3, 6, 9, 12, 15, 18, 21] → Her 3 saatte
⚠️ Dikkat: Maksimum güvenli sınır - Daha fazla önerilmez!
🎯 Tenant Önerileri
| Tenant Tipi |
Günlük Blog |
Aylık Toplam |
SEO Durum |
| Yeni Site |
1-2 blog/gün |
30-60 blog/ay |
✅ Çok Güvenli |
| B2B Şirket |
3-4 blog/gün |
90-120 blog/ay |
✅ Optimal |
| E-Ticaret |
5-6 blog/gün |
150-180 blog/ay |
✅ Güvenli |
| İçerik Sitesi |
8 blog/gün |
240 blog/ay |
⚠️ Sınırda |
✅ İxtif (Tenant 2) İçin Strateji
-
İlk 3 Ay: 4 blog/gün (B2B optimal, 120/ay)
-
3-6 Ay Arası: 6 blog/gün (başarılı ise, 180/ay)
-
6+ Ay: 8 blog/gün (SEO gücü artarsa, 240/ay)
-
Asla: 8'den fazla yapma (spam riski başlar)
🛠️ Bölüm 6: Teknik İmplementasyon
1️⃣ Helper Function - getTenantSetting()
Tenant-Aware Setting Helper
// app/helpers.php veya app/Helpers/setting_helpers.php
if (!function_exists('getTenantSetting')) {
/**
* Tenant-aware setting değeri çek
*
* @param string $key Setting key (blog_ai_enabled)
* @param mixed $default Varsayılan değer
* @return mixed
*/
function getTenantSetting(string $key, $default = null)
{
// Cache key
$cacheKey = "setting_{$key}_tenant_" . tenant('id');
return Cache::remember($cacheKey, 3600, function () use ($key, $default) {
// Setting tanımını al (Central DB)
$setting = \Modules\SettingManagement\App\Models\Setting::where('key', $key)->first();
if (!$setting) {
return $default;
}
// Setting value'yu al (Tenant DB)
$settingValue = \Modules\SettingManagement\App\Models\SettingValue::on('tenant')
->where('setting_id', $setting->id)
->first();
if ($settingValue && $settingValue->value !== null) {
return $settingValue->value;
}
return $setting->default_value ?? $default;
});
}
}
2️⃣ Schedule Calculator
calculateActiveHours() - Saat Hesaplama
// app/Helpers/blog_helpers.php
if (!function_exists('calculateActiveHours')) {
/**
* Günlük blog sayısına göre aktif saatleri hesapla
*
* @param int $dailyCount 1-8 arası
* @return array Aktif saatler [0, 6, 12, 18]
*/
function calculateActiveHours(int $dailyCount): array
{
// Özel dağılımlar (optimize edilmiş)
$schedules = [
1 => [0],
2 => [0, 12],
3 => [0, 8, 16],
4 => [0, 6, 12, 18],
5 => [0, 5, 10, 15, 20],
6 => [0, 4, 8, 12, 16, 20],
8 => [0, 3, 6, 9, 12, 15, 18, 21],
];
return $schedules[$dailyCount] ?? [0];
}
}
3️⃣ Cron Command - GenerateTenantBlogs
app/Console/Commands/GenerateTenantBlogs.php
hour; // 0-23
$this->info("🚀 Cron Started - Hour: {$currentHour}");
// Tüm tenant'ları al (cache'den)
$tenants = Cache::remember('blog_cron_tenant_list', 3600, function () {
return \Stancl\Tenancy\Models\Tenant::all();
});
$processedCount = 0;
foreach ($tenants as $tenant) {
try {
// Tenant context'e geç
tenancy()->initialize($tenant);
// Settings kontrol
$enabled = getTenantSetting('blog_ai_enabled', false);
if (!$enabled) {
$this->line("⏭️ Tenant {$tenant->id}: Disabled");
continue;
}
// Daily count al
$dailyCount = getTenantSetting('blog_ai_daily_count', 4);
// Aktif saatleri hesapla (cache'den)
$cacheKey = "blog_cron_schedule_tenant_{$tenant->id}";
$schedule = Cache::remember($cacheKey, 86400, function () use ($dailyCount) {
return calculateActiveHours($dailyCount);
});
// Şimdiki saat schedule'da var mı?
if (!in_array($currentHour, $schedule)) {
$this->line("⏭️ Tenant {$tenant->id}: Not scheduled for this hour");
continue;
}
// Blog üret!
$this->generateBlog($tenant);
$processedCount++;
} catch (\Exception $e) {
$this->error("❌ Tenant {$tenant->id}: {$e->getMessage()}");
} finally {
tenancy()->end();
}
}
$this->info("✅ Completed: {$processedCount} blogs generated");
return self::SUCCESS;
}
private function generateBlog($tenant)
{
// Draft sayısını kontrol et
$availableDrafts = BlogAIDraft::where('is_generated', false)->count();
if ($availableDrafts === 0) {
// Önce draft üret
GenerateDraftsJob::dispatch(self::DRAFT_REGENERATION_COUNT)
->onQueue('blog-ai');
$this->warn("⚠️ Tenant {$tenant->id}: No drafts, generating first");
return;
}
// Rastgele draft seç
$selectedDraft = BlogAIDraft::where('is_generated', false)
->inRandomOrder()
->lockForUpdate()
->first();
if (!$selectedDraft) {
$this->error("❌ Tenant {$tenant->id}: Could not select draft");
return;
}
// Auto-publish ayarı
$autoPublish = getTenantSetting('blog_ai_auto_publish', true);
// Blog generation job
GenerateBlogFromDraftJob::dispatch($selectedDraft->draft_id, $autoPublish)
->onQueue('blog-ai');
$this->info("✅ Tenant {$tenant->id}: Blog job dispatched (Draft #{$selectedDraft->draft_id})");
// Draft havuzu kontrolü
$remainingDrafts = $availableDrafts - 1;
if ($remainingDrafts <= self::MINIMUM_DRAFT_THRESHOLD) {
GenerateDraftsJob::dispatch(self::DRAFT_REGENERATION_COUNT)
->onQueue('blog-ai');
$this->warn("⚠️ Tenant {$tenant->id}: Low draft pool, regenerating");
}
}
}
4️⃣ Kernel.php Schedule
app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
// Blog Auto Generation - Her saat başı
$schedule->command('generate:tenant-blogs')
->hourly() // 00:00, 01:00, 02:00, ..., 23:00
->withoutOverlapping(10) // Max 10 dakika çalışabilir
->runInBackground()
->appendOutputTo(storage_path('logs/blog-cron.log'))
->onSuccess(function () {
\Log::channel('daily')->info('🎉 Blog Cron: Success');
})
->onFailure(function () {
\Log::channel('daily')->error('❌ Blog Cron: Failed');
});
}
5️⃣ GenerateBlogFromDraftJob Güncelleme
Auto-Publish Parametresi Ekleme
// Modules/Blog/app/Jobs/GenerateBlogFromDraftJob.php
public function __construct(
public int $draftId,
public bool $autoPublish = true // YENİ PARAMETRE
) {}
public function handle()
{
// ... blog üretim logic ...
// Status belirleme
$status = $this->autoPublish ? 'published' : 'draft';
Blog::create([
'title' => $title,
'body' => $body,
'status' => $status, // Auto-publish ayarına göre
// ... diğer alanlar
]);
}