losthost / user-balance
A simple lib to track users' balances
Installs: 9
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/losthost/user-balance
Requires
- losthost/db: ~3.5
Requires (Dev)
- phpunit/phpunit: ^11.5
README
Библиотека для учета балансов пользователей с системой чекпоинтов для высокой производительности.
Особенности
- Полная история всех операций с фильтрацией по датам и типам
- Высокая производительность за счет системы чекпоинтов
- Атомарные переводы между пользователями в одной транзакции БД
- Типобезопасность через константы вместо строк
- Поддержка больших чисел DECIMAL(36,18) для криптовалют
- Гибкая история операций с пагинацией и фильтрацией
Установка
composer require losthost/userbalance
Быстрый старт
use losthost\UserBalance\DBBalanceTransaction; // Автоматически создает таблицу при первом использовании // Пополнение баланса $tx = DBBalanceTransaction::transaction( 'user_123', 100.50, DBBalanceTransaction::TYPE_TOPUP, 'Пополнение' ); // Списание $tx = DBBalanceTransaction::transaction( 'user_123', -30, DBBalanceTransaction::TYPE_USAGE, 'Оплата услуги' ); // Перевод $result = DBBalanceTransaction::transfer( 'alice_id', 'bob_id', 50, 'Перевод' ); // Баланс $balance = DBBalanceTransaction::getBalance('user_123'); // История операций $history = DBBalanceTransaction::getHistory( 'user_123', new DateTime('-7 days'), // За последние 7 дней null, // До текущего момента [DBBalanceTransaction::TYPE_TOPUP, DBBalanceTransaction::TYPE_USAGE], 100, // Лимит 0 // Оффсет );
Основные методы
transaction() — Создание транзакции
DBBalanceTransaction::transaction( string $user_id, float $amount, string $type, // Используйте константы TYPE_* ?string $description = null ) : DBBalanceTransaction
transfer() — Атомарный перевод
DBBalanceTransaction::transfer( string $from_user, string $to_user, float $amount, // > 0 ?string $description = null ) : array ['out' => $tx_out, 'in' => $tx_in]
getBalance() — Получение баланса
DBBalanceTransaction::getBalance( string $user_id, ?string $datetime = null // Баланс на момент времени ) : float
getHistory() — История операций
DBBalanceTransaction::getHistory( string $user_id, ?DateTimeInterface $start = null, ?DateTimeInterface $end = null, ?array $types = null, // null = все кроме checkpoint int $limit = 100, int $offset = 0 ) : array // Массив ассоциативных массивов
checkpoint() — Создание чекпоинтов
DBBalanceTransaction::checkpoint( int $min_transactions // 0 = для всех пользователей ) : void
initBalance() — Инициализация баланса
DBBalanceTransaction::initBalance( string $user_id, float $amount, string $description = 'Initial balance' ) : void // Бросает исключение если пользователь уже существует
Типы операций
DBBalanceTransaction::TYPE_TOPUP // Пополнение DBBalanceTransaction::TYPE_USAGE // Списание DBBalanceTransaction::TYPE_TRANSFER_IN // Получение перевода DBBalanceTransaction::TYPE_TRANSFER_OUT // Отправка перевода DBBalanceTransaction::TYPE_CHECKPOINT // Чекпоинт (системный)
Важно: TYPE_TRANSFER_IN и TYPE_TRANSFER_OUT можно создавать только через метод transfer().
Чекпоинты для производительности
Библиотека использует чекпоинты для ускорения запросов баланса:
-- При запросе баланса считаются только транзакции после последнего чекпоинта SELECT SUM(amount) FROM transactions WHERE user_id = 'user1' AND id >= (SELECT MAX(id) FROM transactions WHERE user_id = 'user1' AND type = 'checkpoint')
Рекомендуется запускать в кроне:
// Раз в час создавать чекпоинты для пользователей с ≥1000 транзакций DBBalanceTransaction::checkpoint(1000);
Расширение функциональности
Отдельный учет бонусов
class DBBonus extends DBBalanceTransaction { // Автоматически создаст таблицу 'bonus' с той же структурой // Можно добавить свои поля в METADATA } // Использование DBBonus::transaction('user1', 100, DBBonus::TYPE_TOPUP, 'Бонус'); $bonus_balance = DBBonus::getBalance('user1');
Кастомные типы операций
class MyBalance extends DBBalanceTransaction { const TYPE_REFERRAL = 'referral'; const TYPE_PENALTY = 'penalty'; const METADATA = [ ...parent::METADATA, 'type' => 'ENUM("topup", "usage", "transfer_in", "transfer_out", "checkpoint", "referral", "penalty")', ]; }
Структура базы данных
CREATE TABLE transactions ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id VARCHAR(50) NOT NULL, amount DECIMAL(36,18) NOT NULL, type ENUM('topup', 'usage', 'transfer_in', 'transfer_out', 'checkpoint'), description TEXT, created_at DATETIME NOT NULL, INDEX idx_user_id (user_id), INDEX idx_user_type (user_id, type) );
Обработка ошибок
try { DBBalanceTransaction::transfer('user1', 'user2', -10); } catch (InvalidArgumentException $e) { // "Transfer amount must be positive" } try { DBBalanceTransaction::transaction('user1', 100, 'invalid_type'); } catch (InvalidArgumentException $e) { // "Invalid transaction type" } try { DBBalanceTransaction::initBalance('existing_user', 100); } catch (RuntimeException $e) { // "User already has transactions" }
Тестирование
composer test
33 теста, 85 assertions покрывают:
- Корректность расчетов баланса
- Атомарность переводов
- Работу чекпоинтов
- Фильтрацию истории операций
- Валидацию входных данных
- Создание таблиц при их отсутствии
- Проверку на работу во внешней транзакции
Производительность в продакшене
- Индексы:
(user_id, type)для быстрого поиска чекпоинтов - Чекпоинты: Запускайте
checkpoint(1000)раз в час - Архивация: Старые транзакции можно перемещать в архивную таблицу
- Мониторинг: Следите за количеством транзакций между чекпоинтами
Лицензия
MIT