diff --git a/app/Models/Torrent.php b/app/Models/Torrent.php index e5f3569d..327f8348 100644 --- a/app/Models/Torrent.php +++ b/app/Models/Torrent.php @@ -202,11 +202,20 @@ class Torrent extends NexusModel /** * 重写获取 info_hash 的方法,确保从数据库读出时是正确的格式 + * 注意:不要使用 getInfoHashAttribute(),不带缓存,第1次有值,第2次指针到头,数据是空!!! */ - public function getInfoHashAttribute($value): false|string + public function infoHash(): Attribute { - // PostgreSQL 返回 bytea 时可能是十六进制流或资源 - return is_resource($value) ? stream_get_contents($value) : $value; + return Attribute::make( + get: function ($value) { + // PostgreSQL 返回 bytea 时可能是十六进制流或资源 + if (is_resource($value)) { + rewind($value); + return stream_get_contents($value); + } + return $value; + } + )->shouldCache(); } public function getPickInfoAttribute() diff --git a/database/migrations/2021_06_08_113437_create_peers_table.php b/database/migrations/2021_06_08_113437_create_peers_table.php index 8ab0eeab..e6144257 100644 --- a/database/migrations/2021_06_08_113437_create_peers_table.php +++ b/database/migrations/2021_06_08_113437_create_peers_table.php @@ -19,7 +19,7 @@ class CreatePeersTable extends Migration Schema::create('peers', function (Blueprint $table) { $table->bigIncrements('id'); $table->unsignedMediumInteger('torrent')->default(0); - $table->char('peer_id', 20)->charset('binary')->index(); + $table->binary('peer_id', 20)->index(); $table->string('ip', 64)->default(''); $table->unsignedSmallInteger('port')->default(0); $table->unsignedBigInteger('uploaded')->default(0); diff --git a/include/globalfunctions.php b/include/globalfunctions.php index 3bbe6d31..aa8e04a9 100644 --- a/include/globalfunctions.php +++ b/include/globalfunctions.php @@ -90,6 +90,7 @@ function sqlesc($value) { function hash_pad($hash) { if (is_resource($hash)) { + rewind($hash); $hash = stream_get_contents($hash); } return str_pad($hash, 20); @@ -99,7 +100,14 @@ function hash_where($name, $hash) { // $shhash = preg_replace('/ *$/s', "", $hash); // return "($name = " . sqlesc($hash) . " OR $name = " . sqlesc($shhash) . ")"; // return sprintf("$name in (%s, %s)", sqlesc($hash), sqlesc($shhash)); - return "$name = " . sqlesc($hash); + if (\Nexus\Database\NexusDB::isMysql()) { + return "$name = " . sqlesc($hash); + } elseif (Nexus\Database\NexusDB::isPgsql()) { + return "$name = decode(bin2hex('$hash'), 'hex')"; + } else { + throw new \RuntimeException("Not supported database"); + } + } //no need any more... diff --git a/nexus/Database/DBInterface.php b/nexus/Database/DBInterface.php index 60048245..c387a0e2 100644 --- a/nexus/Database/DBInterface.php +++ b/nexus/Database/DBInterface.php @@ -1,6 +1,8 @@ free_result(); } + public function prepare(string $sql): \PDOStatement + { + throw new \RuntimeException("mysqli not supported"); + } + } diff --git a/nexus/Database/DBPdo.php b/nexus/Database/DBPdo.php index cdefeec9..f64eac38 100644 --- a/nexus/Database/DBPdo.php +++ b/nexus/Database/DBPdo.php @@ -125,4 +125,9 @@ class DBPdo implements DBInterface return $stmt->closeCursor(); } + public function prepare(string $sql): \PDOStatement + { + return $this->lastStmt = $this->pdo->prepare($sql); + } + } diff --git a/nexus/Database/NexusDB.php b/nexus/Database/NexusDB.php index 86f38930..90b225b8 100644 --- a/nexus/Database/NexusDB.php +++ b/nexus/Database/NexusDB.php @@ -151,6 +151,11 @@ class NexusDB return $this->driver->freeResult($result); } + public function prepare(string $sql) + { + return $this->driver->prepare($sql); + } + public function isConnected() { return $this->isConnected; @@ -506,4 +511,15 @@ class NexusDB return compact('version', 'match', 'minVersion', 'dbType'); } + public static function unixTimestampField(string $field): string + { + if (self::isMysql()) { + return sprintf("UNIX_TIMESTAMP(%s)", $field); + } elseif (self::isPgsql()) { + return sprintf("EXTRACT(EPOCH FROM %s)", $field); + } else { + throw new \RuntimeException('Not supported database.'); + } + } + } diff --git a/nexus/Database/helpers.php b/nexus/Database/helpers.php index 7324538e..19749176 100644 --- a/nexus/Database/helpers.php +++ b/nexus/Database/helpers.php @@ -65,3 +65,8 @@ function mysql_free_result($result) { return NexusDB::getInstance()->freeResult($result); } + +function mysql_prepare(string $sql): \PDOStatement +{ + return NexusDB::getInstance()->prepare($sql); +} diff --git a/public/announce.php b/public/announce.php index 3706c21e..a1cae29d 100644 --- a/public/announce.php +++ b/public/announce.php @@ -172,11 +172,13 @@ elseif ($az['showclienterror'] == 'yes'){ } // check torrent based on info_hash -$checkTorrentSql = "SELECT torrents.id, size, owner, sp_state, seeders, leechers, times_completed, UNIX_TIMESTAMP(added) AS ts, added, banned, hr, approval_status, price, categories.mode FROM torrents left join categories on torrents.category = categories.id WHERE " . hash_where("info_hash", $info_hash); -if (!$torrent = $Cache->get_value('torrent_hash_'.$info_hash.'_content')){ - $res = sql_query($checkTorrentSql); +$tsField = \Nexus\Database\NexusDB::unixTimestampField('added'); +$checkTorrentSql = "SELECT torrents.id, size, owner, sp_state, seeders, leechers, times_completed, $tsField AS ts, added, banned, hr, approval_status, price, categories.mode FROM torrents left join categories on torrents.category = categories.id WHERE info_hash = decode(:info_hash, 'hex') limit 1"; +if (!$torrent = $Cache->get_value('torrent_hash_'.$info_hash.'_content_111')){ + $res = mysql_prepare($checkTorrentSql); + $res->execute(['info_hash' => bin2hex($info_hash)]); $torrent = mysql_fetch_array($res); - $Cache->cache_value('torrent_hash_'.$info_hash.'_content', $torrent, 350); + $Cache->cache_value('torrent_hash_'.$info_hash.'_content', $torrent, 350); } if (!$torrent) { $firstNeedle = "info_hash="; @@ -226,7 +228,9 @@ if ($newnumpeers > $rsize) else $limit = ""; $announce_wait = \App\Repositories\TrackerRepository::MIN_ANNOUNCE_WAIT_SECOND; -$fields = "id, seeder, peer_id, ip, ipv4, ipv6, port, uploaded, downloaded, userid, last_action, UNIX_TIMESTAMP(last_action) as last_action_unix_timestamp, prev_action, (".TIMENOW." - UNIX_TIMESTAMP(last_action)) AS announcetime, UNIX_TIMESTAMP(prev_action) AS prevts"; +$lastActionField = \Nexus\Database\NexusDB::unixTimestampField('last_action'); +$prevActionField = \Nexus\Database\NexusDB::unixTimestampField('prev_action'); +$fields = "id, seeder, peer_id, ip, ipv4, ipv6, port, uploaded, downloaded, userid, last_action, $lastActionField as last_action_unix_timestamp, prev_action, (".TIMENOW." - $lastActionField) AS announcetime, $prevActionField AS prevts"; //$peerlistsql = "SELECT ".$fields." FROM peers WHERE torrent = ".$torrentid." AND connectable = 'yes' ".$only_leech_query.$limit; /** * return all peers,include connectable no @@ -286,11 +290,12 @@ if (isset($event) && $event == "stopped") { } } } -$selfwhere = "torrent = $torrentid AND " . hash_where("peer_id", $peer_id) . " AND userid = $userid"; +$selfwhere = "torrent = $torrentid AND peer_id = decode(:peer_id, 'hex') AND userid = $userid"; //no found in the above random selection if (!isset($self)) { - $res = sql_query("SELECT $fields FROM peers WHERE $selfwhere LIMIT 1"); + $res = mysql_prepare("SELECT $fields FROM peers WHERE $selfwhere LIMIT 1"); + $res->execute(['peer_id' => bin2hex($peer_id)]); $row = mysql_fetch_assoc($res); if ($row) { @@ -500,8 +505,10 @@ elseif(isset($self)) else { if ($event != 'stopped') { - $isPeerExistResultSet = sql_query("select id from peers where $selfwhere limit 1"); - if (mysql_num_rows($isPeerExistResultSet) == 0) { + $stmt = mysql_prepare("select id from peers where $selfwhere limit 1"); + $stmt->execute(['peer_id' => bin2hex($peer_id)]); + $isPeerExistResultSet = mysql_fetch_assoc($stmt); + if (empty($isPeerExistResultSet)) { $connectable = "yes"; $insertPeerSql = "INSERT INTO peers (torrent, userid, peer_id, ip, port, connectable, uploaded, downloaded, to_go, started, last_action, seeder, agent, downloadoffset, uploadoffset, passkey, ipv4, ipv6, is_seed_box) VALUES ($torrentid, $userid, ".sqlesc($peer_id).", ".sqlesc($ip).", $port, '$connectable', $uploaded, $downloaded, $left, $dt, $dt, '$seeder', ".sqlesc($agent).", $downloaded, $uploaded, ".sqlesc($passkey).", ".sqlesc($ipv4).", ".sqlesc($ipv6).", ".intval($isIPSeedBox).")"; do_log("[INSERT PEER] peer not exists for $selfwhere, do insert with $insertPeerSql");