PHP WebShell

Текущая директория: /var/www/bitcardoApp/cron

Просмотр файла: cron_update_online_usd_prices.php

<?php
ini_set('display_errors', '1');
error_reporting(E_ALL);
date_default_timezone_set('Africa/Lagos');

require_once __DIR__ . '/../config/db_config.php';

function is_cli(): bool { return (PHP_SAPI === 'cli'); }
function out(string $msg): void { echo (is_cli() ? $msg.PHP_EOL : htmlspecialchars($msg)."<br>"); }

function table_has_column(mysqli $conn, string $table, string $column): bool {
    $sql = "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
            WHERE TABLE_SCHEMA = DATABASE()
              AND TABLE_NAME = ?
              AND COLUMN_NAME = ?
            LIMIT 1";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param("ss", $table, $column);
    $stmt->execute();
    $res = $stmt->get_result();
    $ok = $res && $res->num_rows > 0;
    $stmt->close();
    return $ok;
}

function norm_coin(string $coin): string {
    $coin = strtoupper(trim($coin));
    $map = [
        'USDT-TRC20' => 'USDT',
        'TRON'       => 'TRX',
    ];
    return $map[$coin] ?? $coin;
}

function curl_get_json(string $url, int $timeout = 25): array {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => $timeout,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => 0,
        CURLOPT_HTTPHEADER     => ['Accept: application/json', 'User-Agent: Bitcardo/1.0'],
    ]);
    $raw  = curl_exec($ch);
    $err  = curl_error($ch);
    $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($raw === false) throw new Exception("cURL error: {$err}");
    if ($code < 200 || $code >= 300) throw new Exception("HTTP {$code}: " . substr($raw, 0, 200));

    $data = json_decode($raw, true);
    if (!is_array($data)) throw new Exception("Invalid JSON: " . substr($raw, 0, 200));
    return $data;
}

function coingecko_map(): array {
    return [
        'BTC'  => 'bitcoin',
        'ETH'  => 'ethereum',
        'SOL'  => 'solana',
        'TRX'  => 'tron',
        'USDT' => 'tether',
    ];
}

function acquire_lock(mysqli $conn, string $name): bool {
    $name = $conn->real_escape_string($name);
    $res = $conn->query("SELECT GET_LOCK('{$name}', 0) AS got");
    $row = $res ? $res->fetch_assoc() : null;
    if ($res) $res->free();
    return (int)($row['got'] ?? 0) === 1;
}
function release_lock(mysqli $conn, string $name): void {
    $name = $conn->real_escape_string($name);
    @$conn->query("SELECT RELEASE_LOCK('{$name}')");
}

/**
 * online_coin_rates requires sell_rate/buy_rate (NOT NULL, no defaults)
 * so we always write them.
 */
function upsert_online_usd_price(mysqli $conn, string $coin, float $usdPrice, string $source): void {
    $coin = strtoupper(norm_coin($coin));
    $sell = $usdPrice;
    $buy  = $usdPrice;
    $m    = 0.00;

    $sql = "INSERT INTO online_coin_rates (coin, rate, margin_percent, sell_rate, buy_rate, source, meta, fetched_at)
            VALUES (?, ?, ?, ?, ?, ?, NULL, NOW())
            ON DUPLICATE KEY UPDATE
              rate = VALUES(rate),
              margin_percent = VALUES(margin_percent),
              sell_rate = VALUES(sell_rate),
              buy_rate = VALUES(buy_rate),
              source = VALUES(source),
              fetched_at = NOW()";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param("sdddds", $coin, $usdPrice, $m, $sell, $buy, $source);
    $stmt->execute();
    $stmt->close();
}

/**
 * Update usd_price; if missing row, insert it.
 */
function upsert_coin_rates_usd_price(mysqli $conn, string $coin, float $usdPrice): int {
    $coin = strtoupper(norm_coin($coin));

    if (!table_has_column($conn, 'coin_rates', 'usd_price')) return 0;

    $sql = "UPDATE coin_rates SET usd_price = ?, updated_at = NOW() WHERE UPPER(coin)=? LIMIT 1";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param("ds", $usdPrice, $coin);
    $stmt->execute();
    $affected = (int)$stmt->affected_rows;
    $stmt->close();

    if ($affected === 1) return 1;

    // Insert minimal row if it does not exist (prevents TRX affected=0 forever)
    $sql2 = "INSERT INTO coin_rates (coin, usd_price, updated_at)
             VALUES (?, ?, NOW())
             ON DUPLICATE KEY UPDATE usd_price=VALUES(usd_price), updated_at=NOW()";
    $stmt2 = $conn->prepare($sql2);
    $stmt2->bind_param("sd", $coin, $usdPrice);
    $stmt2->execute();
    $stmt2->close();

    return 1;
}

try {
    if (!table_has_column($conn, 'coin_rates', 'usd_price')) {
        out("usd_price missing in coin_rates. Adding...");
        $ok = $conn->query("ALTER TABLE coin_rates ADD COLUMN usd_price DECIMAL(20,8) NULL AFTER buy_rate");
        if (!$ok) throw new Exception("Failed to add usd_price: " . $conn->error);
        out("usd_price column added.");
    }

    $lock = 'bitcardo_online_rates_lock';
    if (!acquire_lock($conn, $lock)) {
        out("SKIP: Another update is running (lock busy).");
        exit;
    }

    $map = coingecko_map();
    $ids = implode(',', array_values($map));
    $url  = "https://api.coingecko.com/api/v3/simple/price?ids={$ids}&vs_currencies=usd";
    $data = curl_get_json($url);

    $source = "coingecko";
    $updatedOnline = 0;
    $updatedFallback = 0;

    foreach ($map as $coin => $cgId) {
        $usd = (float)($data[$cgId]['usd'] ?? 0);
        if ($usd <= 0) { out("WARN: {$coin} missing USD price. Skipped."); continue; }

        upsert_online_usd_price($conn, $coin, $usd, $source);
        $updatedOnline++;

        $ok = upsert_coin_rates_usd_price($conn, $coin, $usd);
        if ($ok) $updatedFallback++;

        out("OK: {$coin} USD={$usd} | online updated | coin_rates.usd_price updated=1");
    }

    release_lock($conn, $lock);
    out("DONE: online updated={$updatedOnline}, coin_rates.usd_price updated={$updatedFallback}");

} catch (Throwable $e) {
    release_lock($conn, 'bitcardo_online_rates_lock');
    out("ERROR: " . $e->getMessage());
    exit(1);
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!