MySQL Registracija [R41-2]

Započeo CADILAB, Maj 15, 2017, 21:56:48 POSLE PODNE

prethodna tema - sledeća tema

0 članova i 1 gost pregledaju ovu temu.


Pozdrav,
   Danas cu vam objasniti kako napraviti sistem registracije sa mysql pluginom.
   Za one koji ne znaju, mysql je sistem za upravljanje bazom(bazama) podataka.
   Na ovom forumu koliko ja znam jos uvek nema slicnog tutoriala a i "skripteri" nisu bas najbistriji pa cu se potruditi da vas bar nesto naucim.

   
   Tutorial sadrzi:

  • Spajanje sa databazom.
  • Konfigurisanje databaze.
  • Unosenje podataka u tablicu.
  • Ucitavanje podataka iz tablice.
  • Spremanje podataka u tablicu.
      
      
Prva stvar, definisemo mysql handle, tj, o ovome mozete misliti kao "stvarcica" koja omogucuje izravan pristup bazi koji koristimo za pohranu podataka i slicno.
      
[pawn]new MySQL:handle;[/pawn]

Kada smo to dodali potrebno je da napravimo enumator koji sadrzi korisnicke informacije tipa SQLID koji sluzimo kasnije za pohranu podataka, updejt podataka i slicno.
NAPOMENA: SQLID nije obavezan i nije opsta praksa, ali ukoliko imate dosta korisnika mysql ce raditi dosta brze sa pretragom preko integera nego brze (memory save).


[pawn]enum ENUM_PLAYER_DATA
{
   SQLID, // sqlid, unikatni user id
   Password[65], // password, za ovo koristimo SHA256 password hash koji je dodat u zadnjoj samp verziji
   Salt[11], // nasumicno generisano da modifikuje originalni hash,
   RegDate[65] // datum registracije, ovo je samo "test" da vam pokazem kako da uzmete nesto iz tablice
}
new PlayerInfo[MAX_PLAYERS][ENUM_PLAYER_DATA];[/pawn]

[pawn]#define DIALOG_LOGIN                        (0)
#define DIALOG_REGISTER                     (1)[/pawn]

Sledece, sada definisemo mysql informacije koje koristimo za konekciju sa databazom, ja cu ovde da stavim informacije sa localhosta, vi naravno izmenite po potrebi:

[pawn]#define MYSQL_HOST                          "localhost"
#define MYSQL_USER                          "root"
#define MYSQL_PASSWORD                      "" // po defaultu na localhostu nemate password, osim ukoliko ne koristite MAMP onda je password root
#define MYSQL_DATABASE                      "samp"[/pawn]

Sada kada smo napravili enumator i definisali dialoge i informacije za konekciju prelazimo na konekciju.

[pawn]public OnGameModeInit()
{
    handle = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE); // ovde sada koristimo onaj vec definisani handle i pokusavamo da se konektujemo sa vec definisanim informacijama

   if(mysql_errno(handle) != 0 || handle == MYSQL_INVALID_HANDLE) // ukoliko postoji eror kod konekcije sa databazom ispisujemo sledece i zatvaramo konzolu ->
   {
        print("Konekcija sa databazom nije uspesna, pogledajte da li su sve informacije ispravne.");
        SendRconCommand("exit");
        return true;
    }

   mysql_tquery(handle, "CREATE TABLE IF NOT EXISTS `players` ( `id` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(24) NOT NULL , `password` VARCHAR(65) NOT NULL , `salt` VARCHAR(11) NOT NULL , `regdate` DATETIME NOT NULL , PRIMARY KEY (`id`))");

   // Ovaj upit radi sledece:
   // Proverava da li postoji tabela players i ukoliko ne postoji kreira istu
   // id postavlja kao primary key i stavlja ga na auto increment sto znaci da prilikom svakog unosa u bazu id ce se sam podizati za jedan.
   // name postavlja kao string duzine 24 karaktera, mozete name staviti da bude i unique sto bi znacilo da je to ime jedinstveno i da se ne moze uneti drugi red sa istim imenom, ali meni je to nepotrebno zbog provere na konektu.
   // password stavljamo kao string isto duzine 65 karaktera
   // salt isto samo sto je duzina 11 karaktera
   // i na kraju regdate je datum registracije njega postavljamo kao DATETIME sto je u sustine vreme i datum.
   return true;
}[/pawn]

Ukoliko vam je ovo zadnje ipak zbunjujuce mozete i rucno ovo uraditi sa ovakvom strukturom:



Sledece sto radimo jeste na konekciji igraca proveravamo da li je igrac registrovan:

[pawn]public OnPlayerConnect(playerid)
{
   new query[128], // ovde definisemo string za query koji cemo formatirati i poslati kao upit
      name[MAX_PLAYER_NAME]; // uzimamo ime igraca u ovu varijablu
   
   GetPlayerName(playerid, name, sizeof(name)); // uzimamo ime
   
   mysql_format(handle, query, sizeof(query), "SELECT * FROM players WHERE name='%e' LIMIT 1", name); // ovaj upit uzima sve iz tablice sa vasim imenom, limit 1 znaci prekid pretrage na prvom nadjenom rezultatu
   mysql_tquery(handle, query, "CheckPlayerData", "i", playerid); // ovo je "threaded query" uzima sve ovo i salje ga na sledecu funkciju na proveru
   return true;
}[/pawn]

Sada treba da proverimo da li se u bazi nalazi neko sa ovim imenom, ako da saljemo ga na prijavu a u suprotnom na registraciju.

[pawn]forward CheckPlayerData(playerid);
public CheckPlayerData(playerid)
{
    if(cache_num_rows() > 0)
    {
        // cache_num_rows > 0 znaci da u tablici postoji bar jedan rezultat sa tim imenom
        // sada uzimamo password i salt iz tablice i saljemo igraca na prijavu
       
        cache_get_value(0, "password", PlayerInfo[playerid][Password], 65);
        cache_get_value(0, "salt", PlayerInfo[playerid][Salt], 11);
       
      ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Prijava", "Vas racun je pronadjen, molimo vas upisite lozinku da se prijavite:", "Dalje", "Izlaz");
   }
   else {
       ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_INPUT, "Registracija", "Vas racun nije pronadjen, molimo vas upisite lozinku da se registrujete:", "Dalje", "Izlaz");
   }
   return true;
}
[/pawn]
Sada kad smo to sve zavrsili idemo na potvrdu dialoga, tj, verifikaciju lozinke i prijavljivanje igraca na njegov racun kao i registraciju:

[pawn]public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
   switch(dialogid)
   {
       case DIALOG_LOGIN:
       {
           if(!response) return Kick(playerid);
          
           new buf[65], name[MAX_PLAYER_NAME];
           SHA256_PassHash(inputtext, PlayerInfo[playerid][Salt], buf, 65);
          
           if(strcmp(buf, PlayerInfo[playerid][Password]) == 0)
           {
               // Password se podudara sa unesenim
               // Sada treba da uzmemo ostale podatke
              
            GetPlayerName(playerid, name, sizeof(name));
              
               mysql_format(handle, query, sizeof(query), "SELECT * FROM players WHERE name = '%e' LIMIT 1", name);
               mysql_tquery(handle, query, "OnAccountLoad", "i", playerid);
              
               // sve smo zavrsili, u funkciji OnAccountLoad idete dalje
         }
         else
         {
             // ukoliko lozinka nije ispravna vracamo dialog
             ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Prijava", "Lozinka koju ste uneli nije ispravna, pokusajte ponovo:", "Dalje", "Izlaz");
         }
      }
      
      case DIALOG_REGISTER:
      {
          if(!response) return Kick(playerid);
         
          if(strlen(inputtext) < 6 || strlen(inputtext) > 20) // ako je lozinka kraca od 6 i duza od 20 karaktera vraca dialog
          {
              ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_INPUT, "Registracija", "Vas racun nije pronadjen, molimo vas upisite lozinku da se registrujete:", "Dalje", "Izlaz");
              return true;
         }
         
         // kao sto sam rekao salt je random generisan key, sada cemo ga randomizirati:
         
          for (new i = 0; i < 10; i++) {
             PlayerInfo[playerid][Salt] = random(100) + 50;
          }
         
          PlayerInfo[playerid][Salt][10] = 0;
          SHA256_PassHash(inputtext, PlayerInfo[playerid][Salt], PlayerInfo[playerid][Password], 65);

          // sada unosimo u bazu podatke igraca
          new query[256], name[MAX_PLAYER_NAME];
          GetPlayerName(playerid, name, sizeof(name));
         
          mysql_format(handle, query, sizeof(query), "INSERT INTO players SET \
                                                         name='%e',\
                                                         password='%e',\
                                                         salt='%e',\
                                                         regdate=NOW()", name, PlayerInfo[playerid][Password], PlayerInfo[playerid][Salt]);
                                                        
            mysql_tquery(handle, query, "OnPlayerRegister", "d", playerid);
      }
   }
   return true;
}[/pawn]

Ovde pravimo funkciju za login igraca

[pawn]forward OnAccountLoad(playerid);
public OnAccountLoad(playerid)
{
    if(cache_num_rows() > 0) {
   
        cache_get_value_int(0, "id", PlayerInfo[playerid][SQLID]);
        cache_get_value(0, "regdate", PlayerInfo[playerid][RegDate], 65);
       
        // ovde ispod dodajete spawn, novac i slicno
       
        printf("SQLID: %d | Register date: %s", PlayerInfo[playerid][SQLID], PlayerInfo[playerid][RegDate]);
       
        SendClientMessage(playerid, -1, "Dobrodosli nazad.");
    }
   return true;
}[/pawn]

Sada ovde uzimamo zadnji unesen id i dodelujemo ga igracu kako bi kasnije mogli updejtovati tablicu za njega i slicno

[pawn]forward OnPlayerRegister(playerid);
public OnPlayerRegister(playerid)
{
    PlayerInfo[playerid][SQLID] = cache_insert_id(); // uzimamo id igraca koji je unesen u bazu
    SendClientMessage(playerid, -1, "Registrovali ste se na server.");
   return true;
}[/pawn]

Zadnje, na disconectu spremamo podatke, ovo je samo princip, ja nisam napravio novac, skin ili slicno da cuvamo, ali pokazacu vam opet

[pawn]public OnPlayerDisconnect(playerid)
{
   new query[128];
   mysql_format(handle, query, sizeof(query), "UPDATE players SET money='%d', level='%d', skin='%d' WHERE id='%d'",
                                                       PlayerInfo[playerid][Money], PlayerInfo[playerid][Level], PlayerInfo[playerid][Skin],
                                                               PlayerInfo[playerid][SQLID]);
   mysql_tquery(handle, query);
   return true;
}[/pawn]

Naravno, dok ova metoda nije nepravila preporucuje se da se podatci updejtuju nakon updejta varijable u komandi, funkciji i slicno, pa evo vam i primer jedan za to:

[pawn]command(setskin, playerid, params[]) {
   new id,
       skin,
       query[128];
      
   if(sscanf(params, "ui", id, skin)) return SendClientMessage(playerid, -1, "Koristi: /setskin [player id] [skin]");
   if(skin < 0 || skin > 311) return SendClientMessage(playerid, -1, "Skin nije validan.");
   
   PlayerInfo[id][Skin] = skin;
   
   // sada updejtujemo igraca
   mysql_format(handle, query, sizeof(query), "UPDATE players SET skin='%d' WHERE id='%d'", PlayerInfo[id][Skin], PlayerInfo[id][SQLID]);
   mysql_tquery(handle, query);
   return true;
}
[/pawn]
To bi bilo to, nadam se da sam vam dobro sve objasnio, ovo je prvi tutorial koji pisem i potrudio sam se da vam objasnim sve sto bolje i krace.

Za kreiranje ove skripte smo koristili:
    - MySQL R41-2 (https://github.com/pBlueG/SA-MP-MySQL/releases)
   
   
Ukoliko budete imali nekih problema pisite slobodno, ovo nije filter skripta, ovo nije mod, ovo je tutorial, gledajte malo po netu kako mozda bolje mozete napraviti ovo,
ja ovo nisam testirao, nesto mi je doslo da napisem i evo napisao sam kroz pola sat

UPDATES:
           Obrisana caching metoda pullanja podataka.
           Threaded query kod logina.
Poslednja Izmena: Maj 05, 2019, 14:16:22 POSLE PODNE od Vasic

Dobar tut, mozda ce mi i valjat, pohvale, nabrzaka sam pregledao.  :)

Pohvale za trud, veoma koristan tutorijal ali tu ima 1 problem, malo ko na balkan sampu radi u MySql, ako ne i šire. Kod nas će MySql tek kasnije ući u modu, jer kasnimo u svemu hehe

"I choose to have faith, because without that, I have nothing."


Znam, bas sam zato i otvorio. Mozda se neko potrudi i procita.

Hmm slicno sam radio ja reg i log samo samo koristio mysql_pquery jest da i sam nzm koja je razlika


Dobar tut samo imam par zamerki
Zato si koristio cuvanje cache kad si mogao jednostavnije uraditi i ucitati podatke nisi morao cuvati cache.
Spremanje svih podataka na diskonektu ne preporucujem najbolje je da update tamo gde se podatci izmene manji je query i brze se zavrsi.
Tipa promenimo skin pSkin odma ispod saljemo query i update.

Sto se tice tquery i pquery(parelelni query-a)
tquery ide jedan iza drugog dakle zavrsis se jedan pa drugi itd...
dok kod pquery paralelnog query-a idu u isto vreme dakle pquery mozes koristiti jedino ako imas neki ogroman query.
Detaljno objasnjenje ti je tu http://forum.sa-mp.com/showpost.php?p=3154046&postcount=5021

CitatNeke metode nisu mozda najbolje, tipa cuvanje kesa za kasniju upotrebu ili slicno, ali updejtovacu tutorial vremenom, pozdrav!

Znam za cuvanje, tako je i pravilnije, upiti su brzi i nisu veliki, opet ovo nije nepravilan nacin, hvala sto si procitao, pozdrav.
Poslednja Izmena: Maj 15, 2017, 22:56:51 POSLE PODNE od Cadilab

Citat: Cadilab poslato Maj 15, 2017, 22:54:29 POSLE PODNE
CitatNeke metode nisu mozda najbolje, tipa cuvanje kesa za kasniju upotrebu ili slicno, ali updejtovacu tutorial vremenom, pozdrav!

Znam za cuvanje, tako je i pravilnije, upiti su brzi i nisu veliki, opet ovo nije nepravilan nacin, hvala sto si procitao, pozdrav.
Naravno, ja sam ti rekao par mojih zamerki da imas u vidu. Mozes i tako ali ne vidim svrhu zasto raditi na komplikovaniji nacin kad je na nacin koji sam objasnio mnogo brze i lakse. Jedino ako prebacujes neki mod na mysql pa onda je dosta lakse sacuvati sve na OPD nego ici redom :D.
Poslednja Izmena: Maj 15, 2017, 23:02:46 POSLE PODNE od gReeDy.aMx

tutorial updejtovan sada je "laksi"

Citat: gReeDy.aMx poslato Maj 15, 2017, 22:55:46 POSLE PODNE
Citat: Cadilab poslato Maj 15, 2017, 22:54:29 POSLE PODNE
CitatNeke metode nisu mozda najbolje, tipa cuvanje kesa za kasniju upotrebu ili slicno, ali updejtovacu tutorial vremenom, pozdrav!

Znam za cuvanje, tako je i pravilnije, upiti su brzi i nisu veliki, opet ovo nije nepravilan nacin, hvala sto si procitao, pozdrav.
Naravno, ja sam ti rekao par mojih zamerki da imas u vidu. Mozes i tako ali ne vidim svrhu zasto raditi na komplikovaniji nacin kad je na nacin koji sam objasnio mnogo brze i lakse. Jedino ako prebacujes neki mod na mysql pa onda je dosta lakse sacuvati sve na OPD nego ici redom :D.

updejtovao sam sad ima i to :D
Poslednja Izmena: Maj 15, 2017, 23:09:15 POSLE PODNE od Cadilab

Jos jedna zamerka zasto si koristio non-threaded query gore kad si mogao sa threaded queryem odraditi kod ucitavanja podataka.
Proticaj razliku izmedju non-threaded i threaded query-a gde ih koristiti i kako http://wiki.sa-mp.com/wiki/MySQL/R33#mysql_query (ZA SAMP).

Kod je ispravno napisan.
Svestan sam razlike


Nema potrebe, da je neka veca kolicina podataka pa i ajde, cela poenta threaded upita je da server nastavi sa syncovanjem ostalih stvari dok se query izvrsava, dok ovako treba da saceka koju milisekundu, sto ne predstavlja problem nikakav :P

Citat: Cadilab poslato Maj 15, 2017, 23:37:54 POSLE PODNE
Nema potrebe, da je neka veca kolicina podataka pa i ajde, cela poenta threaded upita je da server nastavi sa syncovanjem ostalih stvari dok se query izvrsava, dok ovako treba da saceka koju milisekundu, sto ne predstavlja problem nikakav :P
Nije ga preporucljivo koristiti gde ne moras, a ovde nisi morao zasto bi rizikovao kad ne moram :D
Predstavljalo bi problem kad bi uzimao vise milisekundi(5+) u sta bi se i sam uvjerio.
Dok se to ne zavrsi nista se nece desavati kao sto si i rekao.
I ti queryi nisu supportovani jos od R7 ili R8 verzije plugina.
Poslednja Izmena: Maj 15, 2017, 23:54:30 POSLE PODNE od gReeDy.aMx