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);
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!