Работа с Базами Данных

Сообщения
219
Реакции
184
Помог
3 раз(а)
Цели статьи научить вас:
1) Правильно подключаться к базе данных
2) Сохранять данные
3) Удалять данные
4) Обновлять данные
5) Получать данные
6) Дать общее понятие по работе с модулем

В данной статье мы будем использовать асинхронные запросы к Базе Данных (далее БД).
Почему их? Всё просто. Данный вид запросов не тормозит сервер, т.к. сервер не ждёт ответа, а дальше выполняет свою работу.
У этих запросов есть своя особенность: при каждом запросе идет подключение и отключение БД. В конце статьи будет весь код целиком с комментариями.
Итак, первым делом нам надо подключиться к БД, чтобы это сделать создадим переменную:
PHP:
new Handle:g_h_Sql             // сюда сохраним данные для входа в БД
Но для начала нужно запомнить данные для входа. В этом нам поможет следующая функция:
PHP:
native Handle:SQL_MakeDbTuple(const host[], const user[], const pass[], const db[], timeout=0);
/**
host – IP вашей БД.
User – логин,
pass – пароль,
db – название БД,
timeout – как долго будет ждать ответа сервер от БД. По дефолту 0. Данный параметр нам не нужен, оставляем как есть.
**/
Подключаться мы будем в форварде plugin_cfg. Именно здесь, чтобы дать всем плагинам прогрузиться и не нагружать в этот момент сервер еще больше.

Подготовим данные для входа
данные для входа я подставляю вот таким образом
PHP:
new const SQLx_host[] = "Ваш IP"            // IP БД
new const SQLx_db[] = "Ваше имя БД"        // имя БД
new const SQLx_user[] = "Ваш логин"        // пользователь БД
newconst SQLx_pass[] = "Ваш пароль"        // пароль к БД

g_h_Sql = SQL_MakeDbTuple(SQLx_host, SQLx_user, SQLx_pass, SQLx_db)
указываем кодировку utf8 для правильного отображения русского текста
PHP:
SQL_SetCharset(g_h_Sql, "utf8");
Отлично, если Вы всё сделали правильно, то Вам удалось подключиться к БД.

Теперь, когда мы убедились, что всё работает, можно и записывать данные в БД.
Данные можно записывать как во время игры, например, так работает плагин мута или бана, а можно сохранять данные при выходе игрока. Мы рассмотрим второй вариант.

Момент выхода игрока ловится форвардом client_disconnect.

Для записи в БД нам нужно определиться с ключом. Им может выступить ник игрока, стим или IP. Я предпочитаю стим.

Если вы решили использовать в качестве ключа Ник или же будете отправлять запрос в БД,
где могут быть символы "разбивающие” запрос (нарушающие синтаксис), то такие данные следует экранировать.
В этом Вам поможет стоковая функция mysql_escape_string. Подробное описание будет в конце статьи.

Внимание!
Распространённая ошибка. В БД записываются нулевые значения, что может привести к “слёту” данных игрока, т.е. их обнулению, чтобы это избежать делаем проверку

PHP:
if(iMyValue > 0 )
если всё окей, то идем дальше

Создадим необходимые массивы
PHP:
new szSteamId[35] // сюда запишем стим игрока
new szQuery[256] // сюда мы запишем запрос
// этой функцией мы узнаем стим игрока
get_user_authid(id, szSteamId, charsmax(szSteamId))

// сюда мы запишем сам запрос к БД
// INSERT INTO – оператор отвечающий за добовление строки в таблицу
// DataBase – название таблицы
// SteamID – поле куда запишется ключ
// Value – поле куда запишутся наши данные
formatex(szQuery, charsmax(szQuery), "INSERT INTO DataBase (SteamID, Value) VALUES('%s', '%d')", szSteamId, iMyValue)

// Функция SQL_ThreadQuery отправляет наш запрос в БД. Ради неё мы тут все собрались
// QueryHandler – название обработчика нашего запроса, т.е. куда придет ответ от БД.
// szQuery – Запрос, который будет отправлен
SQL_ThreadQuery(g_h_Sql, "QueryHandler", szQuery)
Теперь, когда мы сохранили данные, то их надо как-то получить обратно, верно?
В этом нам поможет всё та же функция SQL_ThreadQuery. Как я сказал выше, она основная, а все остальные подготавливают информацию для неё. Всё так или иначе закручено на ней.
Приступим.
Получать данные мы будем в форварде client_connect.
Здесь практически всё также как и при записи, поэтому расписывать всё так подробно не буду. Остановлюсь на подготовке запроса. Наглядно Вы можете увидеть в конце статьи.
PHP:
// SELECT * FROM – оператор, говорящий БД, что мы хотим получить данные
formatex(szQuery, charsmax(szQuery), "SELECT * FROM DataBase WHERE SteamID = '%s'", szSteamId)
Как вы видите появился новый параметр iData. Он передается в обработчик
Именно его, мы будем в дальнейшем использовать для фильтрования ответов от БД
PHP:
SQL_ThreadQuery(g_h_Sql, " QueryHandler", szQuery, iData, charsmax(iData))
Обновление данных происходит по тому же принципу, меняется лишь запрос в БД.
При обновлении используется оператор UPDATE.
SET говорит БД установить значение поля на указанное WHERE (где) стим равен указанному
PHP:
formatex(szQuery, charsmax(szQuery), "UPDATE DataBase SET  Value = '%d' WHERE SteamID= '%s'", iMyValue, szSteamId)
SQL_ThreadQuery(g_h_Sql, "QueryHandler", szQuery, iData, charsmax(iData))
Удаляются данные следующим образом:
Оператор DELETE говорит БД, что надо строку стереть, где стим равен указанному
PHP:
formatex(szQuery, charsmax(szQuery), "DELETE FROM DataBase WHERE SteamID = '%s'", szSteamId)
// выполняем запрос
SQL_ThreadQuery(g_h_Sql, "QueryHandler", szQuery)
Наконец-то добрались до самого вкусного – обработчика запроса. Как я и сказал выше, сюда приходит ответ от БД. Обработчик имеет следующий синтаксис.
// FailState – отсюда узнаем об успешности отправки запроса
// Query – массив для запроса, не очищайте его
// error – массив для ошибки, если будут
// errnum – количество ошибок
// data – тот волшебный параметр, который передается из SQL_ThreadQuery
// size – размер параметра
// QueryTime – время за которое пришёл ответ
PHP:
public QueryHandler(FailState, Handle:Query, error[], iErrNum, data[], size, Float:QueryTime)
Для начала нужно убедиться, что запрос был успешный.

Для общего понимания.
В FailState может прийти три значения:
#define TQUERY_CONNECT_FAILED -2 - неудачная попытка подключения
#define TQUERY_QUERY_FAILED -1 - ошибка в запросе
#define TQUERY_SUCCESS 0 - удачный запрос

проверяем всё ли в порядке
PHP:
if(FailState != TQUERY_SUCCESS)
{
      // если нет, то логируем
      log_amx("sql error: %d (%s)", iErrNum, error)
      // завершаем выполнение паблика
      return
}
Теперь можно рассказать о “волшебном параметре”. Я его использую для записи id игрока.

Сохраняем id игрока в переменную
PHP:
new id = data[0]
Проверяем, что Userid совпадал, чтобы убедиться, что игрок в запросе соответствует текущему игроку.
PHP:
if(data[1] != get_user_userid(id))
    return;
{
      //Проверяем, чтобы не пришел нулевой результат

if( SQL_NumResults(Query) > 0 )
{
    //чтобы считать результат нужно узнать номер колонки
    //для этого создадим переменную и запишем номер
    //нумерация колонок (столбцов) начинается с нуля
    //Можно самостоятельно подставить номер столбца либо воспользоваться функцией
    new iField = SQL_FieldNameToNum(Query, "Value")
    // Value – название колонки
    // Результат же нужно прочитать и куда то записать?
    new szTemp[35] // сюда мы запишем результат
    SQL_ReadResult(Query, iField, szTemp, charsmax(szTemp))

    //либо
    new iTemp = SQL_ReadResult(Query, 0);
    // В iTemp у нас сохранится число (int)
}

}
Ну вот и всё, ничего сложного надо только разобраться. Удачи в освоении.
Как и обещал, прикладываю код:
PHP:
#include <amxmodx>

const MAXPLAYERS = 32                // максимально количество игроков на сервере

const SQLx_host = "Ваш IP"            // IP БД
const SQLx_db = "Ваше имя БД"        // имя БД
const SQLx_user = "Ваш логин"        // пользователь БД
const SQLx_pass = "Ваш пароль"        // пароль к БД

new Handle:g_h_Sql // сюда сохраним данные для входа в БД
new g_iMoney[MAXPLAYERS + 1] // Глобальный массив для хранения значения

public plugin_init() register_plugin("DB Example", "0.1", "gyxoBka")

// Подключаться мы будем в форварде plugin_cfg
public plugin_cfg()
{
    g_h_Sql = SQL_MakeDbTuple(SQLx_host, SQLx_user, SQLx_pass, SQLx_db)      // кэшируем данные для входа
}

// ловим момент выхода игрока
public client_disconnect(id)
{
    //Создадим необходимые массивы
    new szSteamId[35] // сюда запишем стим игрока
    new szQuery[256] // сюда мы запишем запрос
    new iMyValue = 25 // для наглядности создим переменную, которую будем записывать в БД

    // этой функцией мы узнаем стим игрока
    get_user_authid(id, szSteamId, charsmax(szSteamId))
    // форматируем запрос в БД
    formatex(szQuery, charsmax(szQuery), "INSERT INTO DataBase  (SteamID, Value) VALUES('%s', '%d')", szSteamId, iMyValue)
    // Отправляем запрос
    SQL_ThreadQuery(g_h_Sql, "QueryHandler", szQuery)
}

// ловим момент входа игрока
public client_connect(id)
{
    new szSteamId[35] // сюда запишем стим игрока
    new szQuery[256] // сюда мы запишем запрос
    new iData[2]      // волшебный параметр

    iData[0] = id     // сохраняем id игрока
    iData[1] = get_user_userid(id);    // сохраняем userid

    // этой функцией мы узнаем стим игрока
    get_user_authid(id, szSteamId, charsmax(szSteamId))
    // форматируем запрос в БД
    formatex(szQuery, charsmax(szQuery), "SELECT * FROM DataBase WHERE SteamID = '%s'", szSteamId)
    // Отправляем запрос
    SQL_ThreadQuery(g_h_Sql, "QueryHandler", szQuery, iData, sizeof(iData))
}

// Обработчик ответа от БД
public QueryHandler(FailState, Handle:Query, error[], iErrNum, data[], size, Float:QueryTime) 
{
    if(FailState != TQUERY_SUCCESS)
    {
        // если нет, то логируем
        log_amx("sql error: %d (%s)", iErrNum, error)
    
        // завершаем выполнение паблика
        return
    }

    //Сохраняем для оптимизации id игрока
    new id = data[0]

    //Проверяем наш ли это игрок, если нет, то завершаем выполнение функции
    if(data[1] != get_user_userid(id))
        return;

    //Проверяем, чтобы не пришел нулевой результат

    if( SQL_NumResults(Query) > 0 )
    {
        //чтобы считать результат нужно узнать номер колонки
        // для этого создадим переменную и запишем номер

        new iField = SQL_FieldNameToNum(Query, "Value")

        // Value – название колонки
        // Результат же нужно прочитать и куда то записать?
        new szTemp[35] // сюда мы запишем результат

        //При помощи данной функции можно прочитать ответ как целое число, дробное или строку. Ниже пример чтения как строки
        SQL_ReadResult(Query, iField, szTemp, charsmax(szTemp))
        //Сохраняем значение в глобальный массив
        g_iMoney[id] = str_to_num(szTemp)
    }
}
Итак, стоковая функция mysql_escape_string и обещал.

PHP:
// dest[] – массив куда скопируются экранированные данные
// len – длина массива dest
// src[] – исходная строка для экранирования
stock mysql_escape_string(dest[], len, src[])
{
    copy(dest, len, src);

    replace_all(dest, len, "\", "\\");
    replace_all(dest, len, "\0", "\\0");
    replace_all(dest, len, "\r", "\\r");
    replace_all(dest, len, "\n", "\\n");
    replace_all(dest, len, "\x1a", "\Z");
    replace_all(dest, len, "'", "\'");
    replace_all(dest, len, "^"", "\^"");

    return PLUGIN_HANDLED;
}
Важно!
Массив dest должен быть в 2 раза больше массива src, чтобы избежать ошибок запросах или инъекции.
Как использовать?
Всегда нужно экранировать имя игрока для надежности. На этом примере и рассмотрим
PHP:
New UserName[32], UserNameSQL[64]

// получаем имя игрока
get_user_name(id, UserName, 31);
// экранируем имя игрока
mysql_escape_string(UserNameSQL, charsmax(UserNameSQL), UserName)
Далее смело готовим запрос и отправляем
 
Последнее редактирование:
Сообщения
48
Реакции
151
Код:
const SQLx_host = "Ваш IP"            // IP БД
const SQLx_db = "Ваше имя БД"        // имя БД
const SQLx_user = "Ваш логин"        // пользователь БД
const SQLx_pass = "Ваш пароль"        // пароль к БД
Чет ты попутал
 
Сообщения
2,750
Реакции
3,013
Помог
61 раз(а)
Kaido Ren, ты про скобки и объявление массива?
 
Последнее редактирование:
Сообщения
957
Реакции
1,185
Помог
52 раз(а)
wopox1337, начнем с того что хардкодить пароли не есть гуд)
 
Сообщения
2,750
Реакции
3,013
Помог
61 раз(а)
Sonyx, был показан пример реализации. Нюансы скриптер сам как хочет, так и завертит. Более подробное исполнение можно и в плагинах других найти.
 
Сообщения
2,750
Реакции
3,013
Помог
61 раз(а)
Kaido Ren, для pawn и его VM опишешь плюсы таковой реализации?
 
Сообщения
48
Реакции
151
wopox1337, какой реализации, ало? я говорю, что тут не массив создан, а обычная константная переменная, которая почему то содержит текст будто это массив
 
Сообщения
2,750
Реакции
3,013
Помог
61 раз(а)
Сообщения
2,491
Реакции
2,795
Помог
61 раз(а)
Kaido Ren, парсер бб кодов скушал

П.С.Для всех. если есть критика, то пишите сразу как лучше, а не участкы кода и Лол
 
Сообщения
48
Реакции
151
wopox1337, я лишь написал возможные варианты создания массива, а не предложил какой то более оптимизированный вариант
я знаю, что опечатка, именно с этой целью я и написал об этом!

fantom, где я написал лол или еще что подобное? вы прикалываетесь или че? я показал, что человек опечатку сделал, это даже не критика, боже.
 
Сообщения
584
Реакции
1,006
Помог
18 раз(а)
В дисконнект добавьте заметку. Бывает выход раньше авторизации из коннекта, т.е. в базу пойдет запрос с сохранением часто нулевых значений. Этим мы теряем данные.
 
Сообщения
90
Реакции
8
Не знаю правильно ли пишу в теме но у меня проблема нужно вселишь в бан листе изменить причину бана на рус а также ники забаненных, иероглифы отображает,что тока не побывал может кто нить ткнет носом
https://cs-exclusive.ru/csbans/bans/
 
Сообщения
957
Реакции
1,185
Помог
52 раз(а)
fire-dance, нужно анализировать:
  1. в самой БД текст читаем?
  2. версия АМХ 183?
Если оба ответа "да", тогда проблема в кодировке подключения веба, если один из ответов "нет", тогда копать в сторону сервера.
 

RockTheStreet

Саппорт года
Сообщения
1,743
Реакции
345
Помог
40 раз(а)
Обратите внимание, если вы хотите заключить сделку с этим пользователем, он заблокирован
Сообщения
219
Реакции
184
Помог
3 раз(а)
melfyk, не обязательно юзать "***ed"
 
Сообщения
957
Реакции
1,185
Помог
52 раз(а)
На 1.8.3 client_disconnected, не?)
если применять client_disconnected, то нет обратной совместимости с 182, когда в 183 client_disconnect deprecated, но полностью рабочая.
и помимо того -
  1. Статья для ознакомления с азами работы, каждый скриптер в адекватном состоянии может применить форварды, которые ему необходимы
  2. Статья не нацелена на 183 версию АМХ
 

RockTheStreet

Саппорт года
Сообщения
1,743
Реакции
345
Помог
40 раз(а)
Обратите внимание, если вы хотите заключить сделку с этим пользователем, он заблокирован
gyxoBka, Sonyx, Вы всегда одновременно отвечаете?))
Ну я понял, что это необязательно. Уточнил, чтобы у любителей 1.8.3 не было лишний вопросов.
 

Пользователи, просматривающие эту тему

Сейчас на форуме нет ни одного пользователя.
Сверху Снизу