Laravel Türkiye Discord Kanalı Forumda kod paylaşılırken dikkat edilmesi gerekenler!Birlikte proje geliştirmek ister misiniz?

Bu konu epey bir zamanımı aldı 1den fazla cihazda oturum açılmasını
Auth::logoutOtherDevices($request->password);
kullanarak engelledim. fakat bu net bir çözüm değil.Aynı cihazda da bu formu aynı anda tıklayarak veya ufak bir script yazarak form yaparak gönderebiliyoruz.

İlginç bir şekilde aynı anda milisaliseler içinde post atıldığı için throttle:1,1 bile bu şekilde bypass oluyor.Dilerseniz deneme yapın 🙂

Örn:

bakiyesinden 10 puan sileceğiz

•cihaz1 •cihaz2 •cihaz3
----istek ----istek-----istek (aynı anda forma tıklıyor)
Controllerda : $guncelBakiyeSorgu aynı anda serverda çalısıyor ve 20 puan var cevabını veriyor.
basit bir işlem ile 20-10 = 10 update yapılıyor. aynı anda 3 istekte işlem yapıyor.

Bu işlemin sonucunda kullanıcının puanı -10 puana düşmüş oluyor.

Umarım derdimi anlatabilmişimdir.Bu sorunu yaşayan ve çözebilen birileri varsa yardımcı olabilirler mi ?

    drWeb, başlığı Aynı formu 2 veya daha fazla cihazdan gönderilmesini engellemek olarak değiştirdi.

    Bildiğim kadarıyla throttle:1,1 zaten her cihaz için ayrı ayrı çalışır, throttle ile farklı cihazlardan gelen isteklerin aynı anda gelmesini engelleyemezsiniz. Aynı anda birden fazla cihazda oturum açamadığınıza emin misiniz?

      kursatcanciger evet geçmişte remember_token ile açılan oturumlar hala geçerli fakat kullanıcı tekrar login olursa diğer cihazlardan logout oluyor.

      mgsmus kötümser kilitleme yapamıyorum çünkü o cüzdan üzerinde hali hazırda başka işlemlerde yapılıyor.İyimser kilitleme çözüm gibi duruyor fakat asenkron bir ödeme sistemi kullandıgımdan bazı sorunlar var. Bana iyimser kilitleme ile ilgili temiz bir kod örneği yazma şansınız var mı ?

        drWeb Cache lock kullanırken yapılan işleme göre key adı vererek başka işlemleri etkilemesini engelleyebilirsiniz:

        $key = 'wallet:'.$request->user()->id; // wallet:5
        
        Cache::lock($ey, 10)->get(function () {
            // ...
        });

        Bu işlemi bir controller yöntemi içinde yaptığınızı düşünün. Sadece giriş yapmış kullanıcı (5), sadece o controller yöntemini kullanan bir işlem yaptığında kilitten etkilenecek.

        Veri tabanı düzeyinde ise transaction ve lock birlikte kullanacaksınız:

        DB::transaction(function() {
            $balance = Payment::sharedLock()
                ->sum('amount');
        
            //...
        });

        Böylece bakiye hesaplanırken satırların değişmesi engellenmiş olacak ve herhangi bir hata olursa transaction yapılan tüm veri tabanı işlemlerini geri alacak.

        Bu tür sistemlerle uğraşırken ayrıca firstOrCreate, updateOrCreate gibi race-condition riski taşıyan yöntemlerden de kaçınmanız lazım.

          benim alpinejs ile cozumum. Botuna tıklandıgında disabled olup butonuda istedigin gibi guncelleye bilirsin. Ben lutfen bekleyiniz seklinde buton yazısını değiştiriyorum.

            <form method="post" action="" class="" enctype="multipart/form-data"  x-data="{ buttonDisabled: false }"
              x-on:submit="buttonDisabled = true">
           <x-primary-button class="ml-4"   x-bind:disabled="buttonDisabled"
                              x-text="buttonDisabled ? 'Lütfen bekleyin...' : 'Kaydet'">
                                  {{ __('Kaydet') }}
                              </x-primary-button>

            aeneas Frontend ile yapacağınız her şey sadece bir makyajdan ibaret...

            mgsmus

                try {
                    // Kullanıcıya özgü bir kilidi oluştur
                    $lockKey = 'wallet:' .  Auth::user()->id;
            
                    // Kilidi oluştur ve en fazla 10 saniye boyunca bekleyerek diğer işlemleri engelle
                    $lock = Cache::lock($lockKey, 10);
                    $lock->block(10);
            
                    // Kilidi aldık, şimdi diğer işlemleri gerçekleştirebiliriz
            
                    return redirect()->back()->with('success', 'İşlem başarıyla gerçekleştirildi');
                } catch (LockTimeoutException $e) {
                    // Timeout (süre aşımı) durumunda yapılacak işlemler buraya eklenir.
            
                    return redirect()->back()->with('error', 'İşlem şu anda gerçekleştirilemiyor, lütfen daha sonra tekrar deneyin');
                } finally {
                    // Kilidi serbest bırak
                    optional($lock)->release();
                }

            Nerede hata yapıyorum aynı farklı tarayıcıdan cihazdan aynı kullanıcıya anda veya 1-2 saniye arayla post attığımda işlem başarıyla gerçekleşiyor.Hiç bir şekilde hata mesajı almıyorum :/

              drWeb Sadece şu şekilde deneyin:

              $lockKey = 'wallet:' .  Auth::user()->id;
              
              $lock = Cache::lock($lockKey, 10);
              
              if ($lock->get()) {
              
                  // İşlemler...
                  
                  $lock->release();
              
                  return redirect()->back()->with('success', 'İşlem başarıyla gerçekleştirildi');
              
              } else {
                  return redirect()->back()->with('error', 'İşlem şu anda gerçekleştirilemiyor, lütfen daha sonra tekrar deneyin');
              }

                mgsmus
                Maalesef aynı anda veya 1-2 saniye gecikmeli de post atsam
                return redirect()->back()->with('success', 'İşlem başarıyla gerçekleştirildi');
                CACHE_DRIVER=file şeklinde ayarlı bununla alakası olabilir mi ?

                  bu arada feedback olması açısından az önce laravel.gen.tr site bildirimi -1 e düştü 😃

                  drWeb $lock = Cache::lock($lockKey, 10); yaptığınızda 10 saniye kilitleniyor. Kilitleyen istek $lock->get() yaptığında true, bekleyen ise false alıyor. $lock->release(); yaptığınızda ise kilidi açıyorsunuz, 10 sn geçerliliğini yitiriyor, yarıda kesiyorsunuz yani. Yapılan işlem çok kısa sürdüğü için, kilitle-aç şeklide hızlıca gerçekleştiği için diğer istek hemen kilidi sahipleniyor, o yüzden size sanki engellemiyormuş gibi geliyor ama aslında işlem aynı anda değil sırayla gerçekleşiyor. Sonuçta siz iki işlemin de gerçekleşmesini ama aynı anda gerçekleşmemesini istiyorsunuz. Bakiye örneğinden yola çıkarsak, iki istek aynı anda geldiğinde lock sayesinde bakiye sorgusu kilidi ilk sahiplenende 20, diğerinde bir öncekindeki harcama işleminden dolayı 10 verecek. Siz eğer içeride yeterli bakiye var mı kontrolü yapıyorsanız ve yoksa hata veriyorsa 3. istekte bakiye 0 olarak geleceği için ise hata verecek.

                  Eğer istekteki işlem bitsin bitmesin, verdiğim süre kadar işlem yapılmasını istemiyorum, rotayı kitlesin derseniz üstteki açıklamama göre bu durumda yapmanız gereken $lock->release(); kullanmamak çünkü mesela 10 saniye verdiyseniz kilit 10 saniye sonra otomatik açılacak.

                    mgsmus teşekkürler bu konuyu çözmüştüm bunu yazmak için foruma döndüğümde cevabınızı gördüm.Desteğiniz için teşekkür ederim.Bu sorunu yaşayıp çözüm arayan arkadaşlar için de yanıtınızı en iyi yanıt olarak işaretledim.

                    AtomicLocks kullandık fakat sharedLock kullanmadığımız takdirde ne gibi birgüvenlik sorunu yaşarız bu konuda bizi aydınlatır mısınız ?

                      drWeb Kasıt olmadan ya da kasıtlı yapılan işlemler sonucu kayıtların istenmeyen şekilde değişmesi bazı güvenlik sorunlarına yol açabilir. Bunları önceden kestirmek mümkün değil. Kullanıcı hakkı olmayan şeyler elde edebilir, bilgiler sızabilir vs...

                      Dikkat etmeniz gereken 2 nokta var:

                      1. Veri tabanı düzeyinde yapılan lock işlemini kullanıcı bazlı ayıramazsınız. Mesela aşağıdaki PostgreSQL'de bir örnek (MySQL için sadece BEGIN yerine START TRANSACTION yazıyorsunuz)
                        BEGIN;
                        SELECT * FROM users WHERE is_active = 1 FOR UPDATE;
                        COMMIT;
                        Burada users tablosunu uygulama genelinde işlem bitinceye kadar kitlemiş oluyorsunuz. Diğer tüm sorgu istekleri bunun bitmesini bekleyecek. Bunu isteyebilirsiniz de bakiye örneğindeki gibi istemeyebilirsiniz de. Buna göre hangisini kullanacağınızı siz seçeceksiniz.
                      2. Çok fazla paralel istek gelen bir yerde veri tabanı düzeyinde kitleme kullanırsanız büyük ihtimalle performans sorunu yaşayacaksınız, kitlenen tablo değil veri tabanı/uygulama olacak. O yüzden dikkat etmek lazım.