Храним "профили" в базе данных MySQL

zarufakis

Client
Регистрация
22.03.2019
Сообщения
2 014
Благодарностей
1 435
Баллы
113
Поделюсь с общественностью методом хранения профилей в базе данных, которым пользуюсь сам уже как год. Разницы особой с методом хранения с zpprofile не замечено, т. е. субъективно нет разницы, всё в конечном итоге едино.
Итак, приступим.

Введение

Долго пользуясь Зеннопостером, столкнулся с проблемой, когда создаваемые профили на одном сервере нужно перенести на другой, потому что там есть нужные условия для их обработки: свободные ресурсы, версия зенки, программа и т. д., затем их нужно перенести в третье место, потом еще куда-то. А если у кого-то хард помер и потерялись твои аки за год работы... Даже думать не хочется об этом.
В конечном итоге я пришел к методу хранения профилей в базе данных.

Перечислим очевидные плюсы хранения профилей в базе данных перед хранением в zpprofile:
  • Единая точка хранения
  • Возможность работать на нескольких серверах с единой базой профилей
  • Можно сохранять конкретные данные, не пересохраняя весь профиль
  • Удобная выборка по параметрам
  • Удобная репликация и резервное копирование
  • Можно создать лог изменений по каждой строке
  • Можно создать свою страницу аналитики и аудита

Ну и конечно минусы:
  • Единая точка отказа
  • Разная эмуляция железа (тут вопрос дискуссионный)
  • Нельзя хранить суперкуки и другие данные из профиля
  • Дополнительные расходы на хранение базы и бакупов


Практическая часть.

После того, как мы решили, что плюсы перевешивают минусы и предстоящие трудности нас не пугают, приступим к развертыванию.

1. Установка веб сервера и MySQL базы данных.
Для этого будем использовать простую и удобную для быстрого старта программу Laragon, там есть все что нам нужно

После установки и запуска попадаем в саму программу

137804


По умолчанию веб серер и сервер баз данных отключены, включаем их.
Нажимаем кнопку "База данных", открывается очень удобный клиент баз HeidiSQL, в ней нажимаем кнопку "Открыть" и попадаем в нашу базу, которой и будем управлять.
По умолчанию у пользователя root нет пароля, поэтому его нужно установить или создадим нового пользователя для управления нашей будущей базой..

Для начала создадим базу данных profile и пользователя для ее управлением user с паролем ZennoUser1 с возможностью подключаться к базе данных извне, что бы работать с единой базой с нескольких компов/серверов

SQL:
CREATE DATABASE profile;
CREATE USER 'user'@'%' IDENTIFIED BY 'ZennoUser1';
GRANT ALL PRIVILEGES ON profile.* TO 'user'@'%';
FLUSH PRIVILEGES;
Вставляем в поле "Запрос" и нажимаем синюю стрелочку.

138581


Если команды выполнены успешно, разрываем соедиение с базой и подключаемся снова, заменив root и пустой пароль на наши данные.

После успешного подключения мы видем базу 'profile' и можем ей управлять

2. Следующим шагом нужно создать структуру базы для хранения наших профилей.


SQL:
CREATE TABLE IF NOT EXISTS `profile` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `human_login` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `human_password` char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `human_email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `human_email_password` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
  `imap` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `human_secret_answer1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `human_secret_answer2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `human_birthday` date NOT NULL,
  `language` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'en' COMMENT 'ISO 639-1 code',
  `human_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `human_surname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `human_sex` tinyint(1) NOT NULL,
  `account_status` enum('Create','Valid','Captcha','Logout','Consent','Unknown') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'Create',
  `user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `cookies` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
  `proxy` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `lock_assigned_at` datetime DEFAULT NULL COMMENT 'Дата и время назначения лока',
  `label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_login` (`human_login`),
  KEY `idx_status_date` (`account_status`,`created_at`),
  KEY `idx_updated_at` (`updated_at`),
  KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Если в процессе вылезло предупреждение - ничего страшного, отключаемся от базы и подключаемся снова. После чего нам видна наша база с ее структурой.

137805


Ну а теперь приступим к тому, для чего все это и предназначено

3. Интеграция Zennoposter и базы данных.
Я не буду рассказывать как создавать подключение к базе, я все делаю кодом, так и проще и не нужно городить огород с кучей кубиков, зависимостями и внешними библиотеками. Задал переменные, выполнил запрос - готово.

Создаем подключение к базе данных в переменную:

C#:
string[] connectionParams = {
    "server=127.0.0.1",
    "port=3306",
    "user id=user",
    "password=ZennoUser1",
    "database=profile",
    "allowuservariables=True"
};

project.Variables["sql_server_connect"].Value = string.Join(";", connectionParams);

[INSERT]
Добавим данные из профиля в базу данных кодом:

C#:
// Скрипт вставляет данные текущего профиля в таблицу profile
string connectionString = project.Variables["sql_server_connect"].Value;

string birthday = project.Profile.BornYear + "-" + project.Profile.BornMonth + "-" + project.Profile.BornDay;
string sexValue = ((int)project.Profile.Sex).ToString();

string insertQuery =
   "INSERT INTO profile (" +
   "human_login, human_password, human_email, human_email_password, imap, human_birthday, language, human_name, human_surname, human_sex, user_agent, cookies" +
   ") VALUES (" +
    "'" + project.Profile.Login + "', " +
    "'" + project.Profile.Password + "', " +
    "'" + project.Profile.Email + "', " +
    "'" + project.Profile.EmailPassword + "', " +
    "'" + project.Variables["sql_imap"].Value + "', " +
    "'" + birthday + "', " +
    "'" + project.Profile.Language + "', " +
    "'" + project.Profile.Name + "', " +
    "'" + project.Profile.Surname + "', " +
    sexValue + ", " +
    "'" + project.Profile.UserAgent + "', " +
    "'" + project.Variables["cookies"].Value + "'" +
    ");";

string insertResult = ZennoPoster.Db.ExecuteQuery(
    insertQuery,
    null,
    ZennoLab.InterfacesLibrary.Enums.Db.DbProvider.MySqlClient,
    connectionString,
    " | ");
project.Variables["sql_insert_result"].Value = insertResult ?? string.Empty;
Выполнив шаблон 10 раз получаем 10 записей в базе данных.

Важно сохранить порядок полей, иначе, данные попадут не в ту колонку и будет каша.


[SELECT]
Теперь код для чтения случайной строки

C#:
string connectionString = project.Variables["sql_server_connect"].Value;
string query =
   "SELECT human_login, human_password, human_email, human_email_password, imap, human_birthday, language, human_name, human_surname, human_sex, user_agent, cookies " +
   "FROM profile ORDER BY RAND() LIMIT 1;";

string result = ZennoPoster.Db.ExecuteQuery(
    query,
    null,
    ZennoLab.InterfacesLibrary.Enums.Db.DbProvider.MySqlClient,
    connectionString,
    "-|-");

project.Variables["sql_profile"].Value = result ?? string.Empty;
Если в зеновском кубике работы с базой данных данные можно сразу сплитовать в перменные, то после чтения кодом нам нужно данные сплитовать самостоятельно. Я просто добавил свой код, т.к. зеновский кубик сплитует только по одному символу из перечисленных.
[КОД В ШАБЛОНЕ]

[UPDATE]
Обновление нужных полей


После того, как мы получили аккаунт из базы и у нас есть все его поля, но нас интересует больше всего ID, он уникальный по нему можно идентифицировать строку и "не промахнуться".

Смоделируем ситуацию
Получили аккаунт со статусом "Valid", но в процессе проверки выяснилось, что там есть капча и нужно поменять его статус. Выполняем код с командой, которая заменяет значение в ячейке `account_status` на "Captcha", не изменяя остальные:
C#:
var status = "Captcha";
var id = project.Variables["sql_id"].Value;
string connectionString = project.Variables["sql_server_connect"].Value;
string query = $"UPDATE `profile` SET `account_status` = '{status}' WHERE `id` = {id};";

var result = ZennoPoster.Db.ExecuteQuery(
    query,
    null,
    ZennoLab.InterfacesLibrary.Enums.Db.DbProvider.MySqlClient,
    connectionString,
    " | ");

project.Variables["sql_log"].Value = result;
Смотрим в базу. В строке загруженного акккаунта статус должен поменяться на "Captcha"

Если же аккаунт не поменял статус, а нагулялся и получил новые куки, то нужно их так же обновить в базе, не меняя остальные данные.

C#:
var cookies = project.Variables["sql_cookies"].Value;
var id = project.Variables["sql_id"].Value;
string connectionString = project.Variables["sql_server_connect"].Value;

string query = $"UPDATE `profile` SET `cookies` = '{cookies}' WHERE `id` = {id};";

var result = ZennoPoster.Db.ExecuteQuery(
    query,
    null,
    ZennoLab.InterfacesLibrary.Enums.Db.DbProvider.MySqlClient,
    connectionString,
    " | ");

project.Variables["sql_log"].Value = result;

[DELETE]
Ну а если аккаунт оказался полностью заблокирован и он нам в базе больше не нужен, то удаляем его:
C#:
var sql_id = project.Variables["sql_id"].Value;
string connectionString = project.Variables["sql_server_connect"].Value;

string query = $"DELETE FROM profile WHERE id = {sql_id };";

var result = ZennoPoster.Db.ExecuteQuery(
        query,
        null,
        ZennoLab.InterfacesLibrary.Enums.Db.DbProvider.MySqlClient,
        connectionString,
        " | ");
project.Variables["sql_log"].Value = result;
Это базовые запросы, которые закроют ваши 95% всего необходимого для управления аккаунтами.
Так же структура базы не является окончательной, вы в нее можете поместить те данные которые вам необходимо там хранить: разрешения, плагины, да в принципе любые данные профиля которые можно изменить в зенке, но тут базовый минимум, и я вам его дал.

Если пойти еще дальше, то можно в базе данных хранить содержимое всех файлов, которые в себе содержит профиль.
Если кто не знал, то zpprofile - это обычный zip архив с уникальным расширением

137806


В этом случае нужно полностью переделать структуру базы и логику работы, а профиль после чтения из базы сохранять как zpprofile в файловую систему и его уже загружать. Т.е. zpprofile тут выступает как промежуточное звено и не хранится на локальном компе, а выступает как Temp папка, после обновления в базе - удаляется. Но я считаю данную реализацию избыточной, и это тема отдельной работы.

Так же, за кадром остались вопросы:

Резервного копирования

Я используя S3 хранилище и по Cron (да у меня база на Linux, так надежнее) создаю архив с базой, аналитикой и конфигурационными скриптами выгружаю его.

Оптимизации
Когда количество строк переваливает за 100к, а количество одновременных запросов к базе нагружает сервер, то тут нужно заняться оптимизацией самого Mysql сервера, запросов и индексов. Можно без увеличения железа снизить нагрузку кратно.

Безопасности
На проде юзернейм и пароль подключения к базе должны быть сложными, порт изменить на не очевидный, конфиги с паролями хранится вне веб директории, все файлы и директории защищены от чтения.

❗Бонус

Смастерил PHP страницу аналитики. Страница простенькая, но основные показатели есть. Положите индекс вместо того, что лежит в папке, если нажать на кнопку "Корневая папка"

Надеюсь данное решение для вас будет полезным.
Ставьте спасибо и голос за работу

137809
 

Вложения

Последнее редактирование:

искра

Client
Регистрация
07.06.2014
Сообщения
293
Благодарностей
26
Баллы
28
Главный претендент на победу?
 

Кто просматривает тему: (Всего: 1, Пользователи: 0, Гости: 1)