# Работа с GDPSGhostCore через redis

### Структура данных ключей (Спецификация JSON Blob)

Тип данных в Redis: String (строка).&#x20;

{% hint style="info" %}
Шаблон ключа: `[SrvID]` (например `000S`).
{% endhint %}

Полный макет JSON-конфигурации (ConfigBlob)

{% code expandable="true" %}

```json
{
  "DBConfig": {
    "Host": "localhost",
    "Port": 3306,
    "User": "halgd_000S",
    "Password": "secure_password",
    "DBName": "gdps_000S"
  },
  "LogConfig": {
    "LogEnable": true,
    "LogDB": false,
    "LogEndpoints": false,
    "LogRequests": false
  },
  "ChestConfig": {
    "ChestSmallOrbsMin": 200,
    "ChestSmallOrbsMax": 400,
    "ChestSmallDiamondsMin": 2,
    "ChestSmallDiamondsMax": 10,
    "ChestSmallShards": [],
    "ChestSmallKeysMin": 1,
    "ChestSmallKeysMax": 6,
    "ChestSmallWait": 3600,
    "ChestBigOrbsMin": 2000,
    "ChestBigOrbsMax": 4000,
    "ChestBigDiamondsMin": 20,
    "ChestBigDiamondsMax": 100,
    "ChestBigShards": [],
    "ChestBigKeysMin": 1,
    "ChestBigKeysMax": 6,
    "ChestBigWait": 14400
  },
  "ServerConfig": {
    "SrvID": "000S",
    "SrvKey": "wiDTlkGFmherxIXH",
    "MaxUsers": 100,
    "MaxLevels": 500,
    "MaxComments": 1000,
    "MaxPosts": 1000,
    "HalMusic": true,
    "Locked": false,
    "TopSize": 100,
    "EnableModules": {
      "account": true,
      "levels": true,
      "comments": true,
      "scores": true
    }
  },
  "SecurityConfig": {
    "DisableProtection": false,
    "NoLevelLimits": false,
    "AutoActivate": true,
    "BannedIPs": []
  }
}
```

{% endcode %}

{% hint style="danger" %}
Если длина `Password` меньше 3 символов, Go-ядро отклонит пуш с ошибкой `"Empty blob"`.
{% endhint %}

{% hint style="info" %}
`ServerConfig.Locked` — главный "рубильник" сервера. Если флаг равен `true`, ядро принудительно обрывает сессию с этим GDPS.
{% endhint %}

### Внутренняя логика ядра Go при работе с Redis

Ядро взаимодействует с Redis через методы структуры `GlobalConfig`:

#### А. Чтение конфигурации (`LoadById`)

Вызывается ядром при каждом запросе от игрока к конкретному `SrvID`.

1. Устанавливается соединение с Redis: `rdb.ConnectBlob(*glob)`.
2. Выполняется команда: `GET [SrvID]`.
3. Происходит парсинг (демаршалинг) JSON в структуру Go.
4. Соединение закрывается: `rdb.DB.Close()`.
5. Логика блокировки: Проверяется свойство `conf.ServerConfig.Locked`. Если оно равно `true`, метод возвращает пустую структуру и ошибку `errors.New(".ignore")`. Ядро перестает отвечать на запросы этого сервера, имитируя отключение.

#### Б. Запись конфигурации (`PushById`)

Вызывается ядром (или административными утилитами) для обновления настроек.

1. Выполняется валидация безопасности (проверка длины пароля MySQL).
2. Структура упаковывается в JSON строку.
3. Выполняется команда: `SET [SrvID] [JSON_STRING] 0`. Значение записывается без TTL (Time-To-Live = О), то есть хранится вечно, пока не будет удалено принудительно.

{% hint style="warning" %}
Если в Redis по ключу `000S` лежал огромный конфиг, и выполнить `SET 000S '{"ServerConfig": {"Locked": true}}'` то старый конфиг **полностью сотрется**, а на его место запишется этот маленький обрубок из одного поля.
{% endhint %}

#### В. Крон-задачи и обслуживание (`MaintainTasks`)

Каждые сутки в `00:00` лидер кластера (определяемый через Consul) запускает глобальную профилактику:

1. Вызывается команда `KEYS *` для получения списка абсолютно всех зарегистрированных `SrvID`.
2. Для каждого `SrvID` запускается функция `RunSingleTask`.
3. `RunSingleTask` считывает конфиг из Redis, подключается к MySQL этого сервера и выполняет регламентные операции (подсчет скачиваний музыки, сброс дневных лимитов активности пользователей `ResetUserLimits()`, обновление кэша моделей уровней).

{% hint style="warning" %}
Команда `KEYS *` блокирует поток Redis. При росте количества серверов (более 5,000–10,000) её необходимо переписать внутри Go-кода на `SCAN` с курсором, чтобы избежать микро-фризов ядра во время ночной профилактики.
{% endhint %}

### Бонус: sql бд для GDPS

{% code expandable="true" %}

```sql
-- 1. Таблица пользователей (основная)
CREATE TABLE IF NOT EXISTS users
(
    uid               int(11)      NOT NULL AUTO_INCREMENT PRIMARY KEY,
    uname             varchar(16)  NOT NULL,
    passhash          varchar(128) NOT NULL,
    email             varchar(256) NOT NULL,
    role_id           int(4)       NOT NULL DEFAULT 0,

    stars             int(11)      NOT NULL DEFAULT 0,
    diamonds          int(11)      NOT NULL DEFAULT 0,
    coins             int(11)      NOT NULL DEFAULT 0,
    ucoins            int(11)      NOT NULL DEFAULT 0,
    demons            int(11)      NOT NULL DEFAULT 0,
    cpoints           int(11)      NOT NULL DEFAULT 0,
    orbs              int(11)      NOT NULL DEFAULT 0,

    regDate           DATETIME     NOT NULL,
    accessDate        DATETIME     NOT NULL,
    lastIP            varchar(64)           DEFAULT 'Unknown',
    gameVer           int(4)                DEFAULT 20,
    lvlsCompleted     int(11)               DEFAULT 0,
    special           int(11)      NOT NULL DEFAULT 0,
    protect_meta      JSON         NOT NULL DEFAULT '{"comm_time":0,"post_time":0,"msg_time":0}',
    protect_levelsToday int(10)    NOT NULL DEFAULT 0,
    protect_todayStars  int(10)    NOT NULL DEFAULT 0,

    isBanned          tinyint(1)   NOT NULL DEFAULT 0,
    blacklist         text         NOT NULL DEFAULT '',
    friends_cnt       int(11)      NOT NULL DEFAULT 0,
    friendship_ids    TEXT         NOT NULL DEFAULT '',

    iconType          TINYINT      NOT NULL DEFAULT 0,
    vessels           JSON         NOT NULL DEFAULT '{"clr_primary":0,"clr_secondary":0,"cube":0,"ship":0,"ball":0,"ufo":0,"wave":0,"robot":0,"spider":0,"trace":0,"death":0}',
    chests            JSON         NOT NULL DEFAULT '{"small_count":0,"big_count":0,"small_time":0,"big_time":0}',
    settings          JSON         NOT NULL DEFAULT '{"frS":0,"cS":0,"mS":0,"youtube":"","twitch":"","twitter":""}'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 2. Таблица уровней
CREATE TABLE IF NOT EXISTS levels
(
    id                   int(11)          NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name                 varchar(32)      NOT NULL DEFAULT 'Unnamed',
    description          varchar(256)     NOT NULL DEFAULT '',
    uid                  int(11)          NOT NULL,
    password             varchar(8)       NOT NULL,
    version              tinyint          NOT NULL DEFAULT 1,

    length               tinyint(1)       NOT NULL DEFAULT 0,
    difficulty           tinyint(2)       NOT NULL DEFAULT 0,
    demonDifficulty      tinyint(2)       NOT NULL DEFAULT -1,
    suggestDifficulty    float(3, 1)      NOT NULL DEFAULT 0,
    suggestDifficultyCnt int(11)          NOT NULL DEFAULT 0,

    track_id             mediumint(7)     NOT NULL DEFAULT 0,
    song_id              mediumint(7)     NOT NULL DEFAULT 0,
    versionGame          tinyint(3)       NOT NULL,
    versionBinary        tinyint(3)       NOT NULL,
    stringExtra          mediumtext       NOT NULL,
    stringLevel          longtext         NOT NULL,
    stringLevelInfo      mediumtext       NOT NULL,
    original_id          int(11)          NOT NULL DEFAULT 0,

    objects              int(11) UNSIGNED NOT NULL,
    starsRequested       tinyint(2)       NOT NULL,
    starsGot             tinyint(2)       NOT NULL DEFAULT 0,
    ucoins               tinyint(1)       NOT NULL,
    coins                tinyint(1)       NOT NULL DEFAULT 0,
    downloads            int(11) UNSIGNED NOT NULL DEFAULT 0,
    likes                int(11)          NOT NULL DEFAULT 0,
    reports              int(11) UNSIGNED NOT NULL DEFAULT 0,
    collab               TEXT             NOT NULL DEFAULT '',

    is2p                 tinyint(1)       NOT NULL DEFAULT 0,
    isVerified           tinyint(1)       NOT NULL DEFAULT 0,
    isFeatured           tinyint(1)       NOT NULL DEFAULT 0,
    isHall               tinyint(1)       NOT NULL DEFAULT 0,
    isEpic               tinyint(1)       NOT NULL DEFAULT 0,
    isUnlisted           tinyint(1)       NOT NULL DEFAULT 0,
    isLDM                tinyint(1)       NOT NULL DEFAULT 0,

    uploadDate           DATETIME         NOT NULL,
    updateDate           DATETIME         NOT NULL,
    FOREIGN KEY (uid) REFERENCES users(uid) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 3. Мап-паки / Наборы уровней
CREATE TABLE IF NOT EXISTS levelpacks
(
    id             int(11)      NOT NULL PRIMARY KEY AUTO_INCREMENT,
    packType       tinyint(1)   NOT NULL,
    packName       varchar(256) NOT NULL,
    levels         varchar(512) NOT NULL,

    packStars      tinyint(3)   NOT NULL DEFAULT 0,
    packCoins      tinyint(2)   NOT NULL DEFAULT 0,
    packDifficulty tinyint(2)   NOT NULL,
    packColor      varchar(11)  NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 4. Роли и права модерации
CREATE TABLE IF NOT EXISTS roles
(
    id          int(11)      NOT NULL PRIMARY KEY AUTO_INCREMENT,
    roleName    varchar(64)  NOT NULL DEFAULT 'Moderator',
    commentColor varchar(11) NOT NULL DEFAULT '0,0,255',
    modLevel    tinyint(1)   NOT NULL DEFAULT 1,
    privs       text         NOT NULL DEFAULT '{"cRate":0,"cFeature":0,"cEpic":0,"cVerCoins":0,"cDaily":0,"cWeekly":0,"cDelete":0,"cLvlAccess":0,"aRateDemon":0,"aRateStars":0,"aReqMod":0,"dashboardMod":0,"dashboardBan":0,"dashboardCreatePack":0}'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 5. Кастомная музыка
CREATE TABLE IF NOT EXISTS songs
(
    id        int(11)       NOT NULL PRIMARY KEY AUTO_INCREMENT,
    name      varchar(128)  NOT NULL DEFAULT 'Unnamed',
    artist    varchar(128)  NOT NULL DEFAULT 'Unknown',
    size      float(5, 2)   NOT NULL,
    url       varchar(1024) NOT NULL,
    isBanned  tinyint(1)    NOT NULL DEFAULT 0,
    downloads int           NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 6. Связи друзей
CREATE TABLE IF NOT EXISTS friendships
(
    id   int(12)    NOT NULL PRIMARY KEY AUTO_INCREMENT,
    uid1 int(11)    NOT NULL,
    uid2 int(11)    NOT NULL,
    u1_new tinyint(1) NOT NULL DEFAULT 1,
    u2_new tinyint(1) NOT NULL DEFAULT 1,
    FOREIGN KEY (uid1) REFERENCES users(uid) ON DELETE CASCADE,
    FOREIGN KEY (uid2) REFERENCES users(uid) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 7. Запросы в друзья
CREATE TABLE IF NOT EXISTS friendreqs
(
    id         int(12)      NOT NULL PRIMARY KEY AUTO_INCREMENT,
    uid_src    int(11)      NOT NULL,
    uid_dest   int(11)      NOT NULL,
    uploadDate DATETIME     NOT NULL,
    comment    varchar(512) NOT NULL DEFAULT '',
    isNew      tinyint(1)   NOT NULL DEFAULT 1,
    FOREIGN KEY (uid_src) REFERENCES users(uid) ON DELETE CASCADE,
    FOREIGN KEY (uid_dest) REFERENCES users(uid) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 8. Комментарии в профилях пользователей
CREATE TABLE IF NOT EXISTS acccomments
(
    id         int(12)      NOT NULL PRIMARY KEY AUTO_INCREMENT,
    uid        int(11)      NOT NULL,
    comment    varchar(128) NOT NULL,
    postedTime DATETIME     NOT NULL,
    likes      int(11)      NOT NULL DEFAULT 0,
    isSpam     tinyint(1)   NOT NULL DEFAULT 0,
    FOREIGN KEY (uid) REFERENCES users(uid) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 9. Комментарии под уровнями
CREATE TABLE IF NOT EXISTS comments
(
    id         int(12)      NOT NULL PRIMARY KEY AUTO_INCREMENT,
    uid        int(11)      NOT NULL,
    lvl_id     int(11)      NOT NULL,
    comment    varchar(128) NOT NULL,
    postedTime DATETIME     NOT NULL,
    likes      int(11)      NOT NULL DEFAULT 0,
    isSpam     tinyint(1)   NOT NULL DEFAULT 0,
    percent    tinyint(3)   NOT NULL,
    FOREIGN KEY (uid) REFERENCES users(uid) ON DELETE CASCADE,
    FOREIGN KEY (lvl_id) REFERENCES levels(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 10. Рекорды и статистика прохождений
CREATE TABLE IF NOT EXISTS scores
(
    id         int(12)    NOT NULL PRIMARY KEY AUTO_INCREMENT,
    uid        int(11)    NOT NULL,
    lvl_id     int(11)    NOT NULL,
    postedTime DATETIME   NOT NULL,
    percent    tinyint(3) NOT NULL,
    attempts   int(11)    NOT NULL DEFAULT 0,
    coins      tinyint(1) NOT NULL DEFAULT 0,
    FOREIGN KEY (uid) REFERENCES users(uid) ON DELETE CASCADE,
    FOREIGN KEY (lvl_id) REFERENCES levels(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 11. Личные сообщения
CREATE TABLE IF NOT EXISTS messages
(
    id         int(12)       NOT NULL PRIMARY KEY AUTO_INCREMENT,
    uid_src    int(11)       NOT NULL,
    uid_dest   int(11)       NOT NULL,
    subject    varchar(256)  NOT NULL DEFAULT '',
    body       varchar(1024) NOT NULL,
    postedTime DATETIME      NOT NULL,
    isNew      tinyint(1)    NOT NULL DEFAULT 1,
    FOREIGN KEY (uid_src) REFERENCES users(uid) ON DELETE CASCADE,
    FOREIGN KEY (uid_dest) REFERENCES users(uid) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 12. Квесты (Daily/Weekly/Задания)
CREATE TABLE IF NOT EXISTS quests
(
    id         int(12)     NOT NULL PRIMARY KEY AUTO_INCREMENT,
    type       tinyint(1)  NOT NULL,
    name       varchar(64) NOT NULL DEFAULT '',
    needed     int(7)      NOT NULL DEFAULT 0,
    reward     int(7)      NOT NULL DEFAULT 0,
    lvl_id     int(11)     NOT NULL DEFAULT 0,
    timeExpire DATETIME    NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 13. Логирование действий (Админ-панель/Модерация)
CREATE TABLE IF NOT EXISTS actions
(
    id        int(13)    NOT NULL PRIMARY KEY AUTO_INCREMENT,
    date      DATETIME   NOT NULL,
    uid       int(11)    NOT NULL,
    type      tinyint(1) NOT NULL,
    target_id int(11)    NOT NULL,
    isMod     tinyint(1) NOT NULL DEFAULT 0,
    data      JSON       NOT NULL DEFAULT '{}'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

{% endcode %}

### Бонус 2: python пример работы

{% code expandable="true" %}

```python
import json
import redis
import pymysql
from typing import Dict, Any, Optional

class GhostCoreBillingManager:    
    def __init__(self, redis_host: str = 'localhost', redis_port: int = 6379, 
                 redis_db: int = 0, redis_password: Optional[str] = None):
        # Инициализация Redis
        self.rdb = redis.Redis(
            host=redis_host, port=redis_port, db=redis_db, 
            password=redis_password, decode_responses=True
        )

    def _create_mysql_database_and_user(self, root_conn_params: dict, new_db_params: dict):
        """
        Подключается под root-правами к MySQL, создает базу данных, 
        нового пользователя и выдает ему права только на эту базу.
        """
        # Подключаемся к MySQL как root/admin
        connection = pymysql.connect(
            host=root_conn_params['host'],
            port=root_conn_params.get('port', 3306),
            user=root_conn_params['user'],
            password=root_conn_params['password'],
            autocommit=True
        )
        
        try:
            with connection.cursor() as cursor:
                # 1. Создаем базу данных для конкретного сервера
                cursor.execute(f"CREATE DATABASE IF NOT EXISTS `{new_db_params['db_name']}`;")
                
                # 2. Создаем отдельного пользователя для этой БД
                cursor.execute(f"CREATE USER IF NOT EXISTS '{new_db_params['user']}'@'%' IDENTIFIED BY '{new_db_params['password']}';")
                
                # 3. Даем ему полные права на созданную БД
                cursor.execute(f"GRANT ALL PRIVILEGES ON `{new_db_params['db_name']}`.* TO '{new_db_params['user']}'@'%';")
                cursor.execute("FLUSH PRIVILEGES;")
        finally:
            connection.close()

    def _import_sql_dump(self, db_params: dict, sql_file_path: str):
        """
        Подключается к уже созданной БД под учеткой нового пользователя 
        и импортирует туда структуру таблиц из .sql файла.
        """
        connection = pymysql.connect(
            host=db_params['host'],
            port=db_params.get('port', 3306),
            user=db_params['user'],
            password=db_params['password'],
            database=db_params['db_name'],
            autocommit=True
        )
        
        try:
            with connection.cursor() as cursor:
                with open(sql_file_path, 'r', encoding='utf-8') as f:
                    # Разделяем файл по точке с запятой, чтобы выполнять запросы поочередно
                    sql_commands = f.read().split(';')
                    for command in sql_commands:
                        if command.strip():
                            cursor.execute(command)
        finally:
            connection.close()

    def _generate_default_config(self, srv_id: str, db_config: dict, max_users: int, max_levels: int) -> dict:
        """Генерация ConfigBlob для Go-ядра на основе структуры из DOCS.md и кода"""
        return {
            "DBConfig": {
                "Host": db_config["host"],
                "Port": int(db_config.get("port", 3306)),
                "User": db_config["user"],
                "Password": db_config["password"],
                "DBName": db_config["db_name"]
            },
            "LogConfig": {
                "LogEnable": True,
                "LogDB": False,
                "LogEndpoints": False,
                "LogRequests": False
            },
            "ChestConfig": {
                # Лимиты сундуков выставим пожирнее, ориентируясь на пример из твоего DOCS.md
                "ChestSmallOrbsMin": 200, "ChestSmallOrbsMax": 400,
                "ChestSmallDiamondsMin": 2, "ChestSmallDiamondsMax": 10,
                "ChestSmallShards": [], "ChestSmallKeysMin": 1, "ChestSmallKeysMax": 6,
                "ChestSmallWait": 3600,
                "ChestBigOrbsMin": 2000, "ChestBigOrbsMax": 4000,
                "ChestBigDiamondsMin": 20, "ChestBigDiamondsMax": 100,
                "ChestBigShards": [], "ChestBigKeysMin": 1, "ChestBigKeysMax": 6,
                "ChestBigWait": 14400
            },
            "ServerConfig": {
                "SrvID": srv_id,
                "SrvKey": db_config.get("srv_key", "wiDTlkGFmherxIXH"),
                "MaxUsers": max_users,
                "MaxLevels": max_levels,
                "MaxComments": 1000,
                "MaxPosts": 1000,
                "HalMusic": True,
                "Locked": False
            },
            "SecurityConfig": {
                "DisableProtection": False,
                "NoLevelLimits": False,
                "AutoActivate": True,
                "BannedIPs": []
            }
        }

    def provision_new_server(self, srv_id: str, root_mysql_params: dict, 
                             new_mysql_params: dict, sql_dump_path: str,
                             max_users: int = 100, max_levels: int = 500) -> bool:
        """
        ГЛАВНЫЙ МЕТОД БИЛЛИНГА ДЛЯ СОЗДАНИЯ СЕРВЕРА "ПОД КЛЮЧ":
        1. Проверяет Redis на уникальность SrvID.
        2. Создает базу данных и пользователя в MySQL.
        3. Импортирует таблицы из дампа.
        4. Записывает конфигурацию в Redis.
        """
        if self.rdb.exists(srv_id):
            raise ValueError(f"Сервер с ID {srv_id} уже зарегистрирован в Redis!")

        print(f"[{srv_id}] Шаг 1: Создание базы данных и пользователя в MySQL...")
        self._create_mysql_database_and_user(root_mysql_params, new_mysql_params)
        
        print(f"[{srv_id}] Шаг 2: Импорт схемы таблиц из {sql_dump_path}...")
        self._import_sql_dump(new_mysql_params, sql_dump_path)
        
        print(f"[{srv_id}] Шаг 3: Генерация ConfigBlob и регистрация в Redis...")
        config_blob = self._generate_default_config(srv_id, new_mysql_params, max_users, max_levels)
        
        # Сохраняем в Redis (Go-ядро мгновенно увидит новый сервер)
        success = self.rdb.set(srv_id, json.dumps(config_blob))
        print(f"[{srv_id}] Сервер успешно поднят и активирован в GhostCore!")
        return success

    def suspend_server(self, srv_id: str) -> bool:
        """Блокировка за неуплату (Locked = True)"""
        data = self.rdb.get(srv_id)
        if not data: return False
        config = json.loads(data)
        config["ServerConfig"]["Locked"] = True
        return self.rdb.set(srv_id, json.dumps(config))

    def resume_server(self, srv_id: str) -> bool:
        """Разблокировка после оплаты (Locked = False)"""
        data = self.rdb.get(srv_id)
        if not data: return False
        config = json.loads(data)
        config["ServerConfig"]["Locked"] = False
        return self.rdb.set(srv_id, json.dumps(config))
```

{% endcode %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.fruit-cloud.ru/rabota-s-gdpsghostcore-cherez-redis.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
