[原创]v$resource_limit 与 v$license学习

第一次认识这两个视图:v$resource_limit 与 v$license

先看一下,ORACLE官方是怎么说的吧.

V$RESOURCE_LIMIT displays information about global resource use for some of the system resources. Use this view to monitor the consumption of resources so that you can take corrective action, if necessary. Many of the resources correspond to initialization parameters listed in Table 7-5.

Some resources, those used by DLM for example, have an initial allocation (soft limit), and the hard limit, which is theoretically infinite (although in practice it is limited by SGA size). During SGA reservation/initialization, a place is reserved in SGA for the INITIAL_ALLOCATION of resources, but if this allocation is exceeded, additional resources are allocated up to the value indicated by LIMIT_VALUE. The CURRENT_UTILIZATION column indicates whether the initial allocation has been exceeded. When the initial allocation value is exceeded, the additional required resources are allocated from the shared pool, where they must compete for space with other resources.

A good choice for the value of INITIAL_ALLOCATION will avoid the contention for space. For most resources, the value for INITIAL_ALLOCATION is the same as the LIMIT_VALUE. Exceeding LIMIT_VALUE results in an error.

ColumnDatatypeDescription
RESOURCE_NAMEVARCHAR2(30)Name of the resource (see Table 7-5)
CURRENT_UTILIZATIONNUMBERNumber of (resources, locks, or processes) currently being used
MAX_UTILIZATIONNUMBERMaximum consumption of this resource since the last instance start-up
INITIAL_ALLOCATIONVARCHAR2(10)Initial allocation. This will be equal to the value specified for the resource in the initialization parameter file (UNLIMITED for infinite allocation).
LIMIT_VALUEVARCHAR2(10)Unlimited for resources and locks. This can be greater than the initial allocation value (UNLIMITED for infinite limit).

Table 7-5 Values for the RESOURCE_NAME Column

Resource NameCorresponds to

DML_LOCKS

See "DML_LOCKS"

ENQUEUE_LOCKS

This value is computed by the Oracle Database. See V$ENQUEUE_LOCK to obtain more information about the enqueue locks.

GES_LOCKS

Global Enqueue Service locks

GES_PROCS

Global Enqueue Service processes

GES_RESS

Global Enqueue Service resources

MAX_SHARED_SERVERS

See "MAX_SHARED_SERVERS"

PARALLEL_MAX_SERVERS

See "PARALLEL_MAX_SERVERS"

PROCESSES

See "PROCESSES"

SESSIONS

See "SESSIONS"

SORT_SEGMENT_LOCKS

This value is computed by the Oracle Database

TEMPORARY_LOCKS

This value is computed by the Oracle Database

TRANSACTIONS

See "TRANSACTIONS"

 

V$LICENSE displays information about license limits.

ColumnDatatypeDescription
SESSIONS_MAXNUMBERMaximum number of concurrent user sessions allowed for the instance
SESSIONS_WARNINGNUMBERWarning limit for concurrent user sessions for the instance
SESSIONS_CURRENTNUMBERCurrent number of concurrent user sessions
SESSIONS_HIGHWATERNUMBERHighest number of concurrent user sessions since the instance started
USERS_MAXNUMBERMaximum number of named users allowed for the database
CPU_COUNT_CURRENTNUMBERCurrent number of logical CPUs or processors on the system
CPU_CORE_COUNT_CURRENTNUMBERCurrent number of CPU cores on the system (includes subcores of multicore CPUs, as well as single-core CPUs)
CPU_SOCKET_COUNT_CURRENTNUMBERCurrent number of CPU sockets on the system (represents an absolute count of CPU chips on the system, regardless of multithreading or multicore architectures)
CPU_COUNT_HIGHWATERNUMBERHighest number of logical CPUs or processors on the system since the instance started
CPU_CORE_COUNT_HIGHWATERNUMBERHighest number of CPU cores on the system since the instance started (includes subcores of multicore CPUs, as well as single-core CPUs)
CPU_SOCKET_COUNT_HIGHWATERNUMBERHighest number of CPU sockets on the system since the instance started (represents an absolute count of CPU chips on the system, regardless of multithreading or multicore architectures)

Note:

The availability of the CPU core count and CPU socket count statistics is subject to the operating system platform. on which the Oracle Database is running. If a statistic is unavailable, the view will return NULL for the statistic value.
 
做一个具体的例子可以简单说明一下用法.
SQL> select count(*) from v$session;
  COUNT(*)
----------
        25
得到当前的session总数(但它应该是个变化的数值)
SQL> select * from v$resource_limit where resource_name = 'sessions';
RESOURCE_NAME                  CURRENT_UTILIZATION MAX_UTILIZATION
------------------------------ ------------------- ---------------
INITIAL_ALLOCATION   LIMIT_VALUE
-------------------- --------------------
sessions                                        26              40
       555                  555
CURRENT_UTILIZATION得到当前被用的session总数。MAX_UTILIZATION是各项资源没有达到的最大值
SQL> select * from v$license;
SESSIONS_MAX SESSIONS_WARNING SESSIONS_CURRENT SESSIONS_HIGHWATER  USERS_MAX
------------ ---------------- ---------------- ------------------ ----------
           0                0               19                 31          0
可以得道自从实例启动以来,连接数据库的用户session的曾经达到的最大值(SESSIONS_HIGHWATER)和当前值(SESSIONS_CURRENT,该值并不等于 v$session 中查询出来的数量,似乎并不包括 Oracle 本身连接的 SESSION,但似乎还是有点出入,具体是怎样的对应关系,我还没有搞明白)

还有一点就是.V$LICENSE中我并没有见到描述CPU信息的列.这也在困惑当中.

总结:这两个视图不属于重要视图,权当了觖即可.


 

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/12045182/viewspace-448714/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/12045182/viewspace-448714/

这是api/api.php文件代码 <?php // api/api.php // === 配置区 === define('MUIP_API', 'http://127.0.0.1:62221/api'); define('REGION', 'dev_gio'); define('TIME_TOLERANT', 5); define('PRIVATE_KEY_SIZE', 4096); define('PRIVATE_KEY', '$$$PRIVATE_KEY$$$'); // 签名函数(根据实际算法调整) function calcSign($params, $secretKey) { ksort($params); $str = ''; foreach ($params as $k => $v) { $str .= $k . '=' . $v . '&'; } $str = rtrim($str, '&'); return md5($str . $secretKey); } // 返回 JSON 并结束 function returnJSON($data, $code = 200) { http_response_code($code); header('Content-Type: application/json'); echo json_encode($data, JSON_UNESCAPED_UNICODE); exit; } // === 认证校验 === if ($_GET["adminpass"] !== "syxywlQQ28009618hello") { returnJSON(['success' => false, 'message' => 'gm码错误'], 403); } if ($_GET["sha"] !== md5("getflag")) { returnJSON(['success' => false, 'message' => 'gm码错误'], 403); } $uid = intval($_GET["uid"]); $item = $_GET["item"] ?? ''; $number = max(1, intval($_GET["number"] ?? 1)); if ($uid <= 0) { returnJSON(['success' => false, 'message' => '无效UID'], 400); } // === 命令类型映射表 === $commandMap = [ 'item' => "item add $item $number", 'mcoin' => "mcoin $number", 'level' => "player level $item", 'exp' => "player exp $item", 'money' => "player money $item", 'vip' => "player vip $item", 'teleport' => "player teleport " . (floatval($item) ?: '0') . " 0", 'custom_cmd' => $item, ]; // 获取命令类型 $cmdType = $_GET['cmd_type'] ?? 'item'; // 特殊兼容:item=203 → mcoin if ($item == 203) { $cmdType = 'mcoin'; } if (!isset($commandMap[$cmdType])) { returnJSON(['success' => false, 'message' => "不支持的命令类型: $cmdType"], 400); } $command = $commandMap[$cmdType]; // === 构造并发送 GM 请求 === $query = [ 'cmd' => 1116, 'uid' => $uid, 'msg' => $command, 'region' => REGION ]; $query['sign'] = calcSign($query, "27bwq^d4zzXpdUxf"); $ch = curl_init(MUIP_API . '?' . http_build_query($query)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $rsp = curl_exec($ch); if (curl_error($ch)) { returnJSON(['success' => false, 'message' => '请求失败: ' . curl_error($ch)], 500); } curl_close($ch); $rspData = @json_decode($rsp, true); returnJSON([ 'success' => isset($rspData['retcode']) && $rspData['retcode'] === 0, 'message' => $rspData['msg'] ?? '操作完成', 'data' => $rspData['data'] ?? null, 'debug' => [ // 调试用,上线前可删除 'command_sent' => $command, 'cmd_type' => $cmdType, 'uid' => $uid ] ]); ?> 这是index.php文件代码 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>CDK兑换中心</title> <style> body { font-family: Arial, sans-serif; background: #f4f4f4; padding: 20px; } .container { max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); } input[type="text"] { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ccc; border-radius: 5px; font-size: 16px; } button { background: #007BFF; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; } button:hover { background: #0056b3; } .message { padding: 10px; margin: 10px 0; border-radius: 5px; } .error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } </style> </head> <body> <div class="container"> <h2 style="text-align:center;">🎮 CDK兑换系统</h2> <form method="post"> UID: <input type="text" name="uid" placeholder="输入你的游戏UID" required /><br/> CDK: <input type="text" name="cdk" placeholder="输入兑换码" required /><br/> <button type="submit">立即兑换</button> </form> <?php // 引入 Medoo(请确保路径正确) require_once './medoo.php'; // 根据实际路径调整,如:vendor/medoo/medoo.min.php use Medoo\Medoo; // 初始化数据库连接 $database = new Medoo([ 'database_type' => 'mysql', 'database_name' => 'db_hk4e_gm', 'server' => '127.0.0.1', 'username' => 'db_hk4e_gm', 'password' => 'syxywl.cn', 'charset' => 'utf8', 'port' => 3306, ]); if ($_POST) { $Uid = trim($_POST['uid']); $cdk_input = trim($_POST['cdk']); if (!is_numeric($Uid) || intval($Uid) <= 0) { echo "<div class='message error'>❌ 请输入有效的数字UID!</div>"; return; } $Uid = intval($Uid); try { // === 查询 CDK 是否存在且启用 === $cdkData = $database->get("cdk", [ "id", "item", "number", "command_type" ], [ "AND" => [ "cdk" => $cdk_input, "start" => 1 ] ]); if (!$cdkData) { echo "<div class='message error'>❌ CDK不存在或已停用!<br>卡密: $cdk_input</div>"; return; } // === 检查是否已兑换过该 CDK === $used = $database->get("used_cdk", ["id"], [ "AND" => [ "uid" => $Uid, "cdk" => $cdk_input ] ]); if ($used) { echo "<div class='message error'>❌ 此CDK已被您兑换过!<br>卡密: $cdk_input</div>"; return; } // === 准备调用 API 发放奖励 === $cmd_type = $cdkData["command_type"] ?? "item"; $item_value = $cdkData["item"]; $number_value = intval($cdkData["number"]); // 特殊兼容:item=203 → mcoin if ($item_value == 203) { $cmd_type = "mcoin"; } $api_url = "http://202.189.14.206:344/api/api.php?" . http_build_query([ 'sha' => md5("getflag"), 'adminpass' => 'syxywlQQ28009618hello', 'uid' => $Uid, 'item' => urlencode($item_value), 'number' => $number_value, 'cmd_type' => urlencode($cmd_type) ]); $result = @file_get_contents($api_url); $run = json_decode($result, true); if ($run && !empty($run['success']) && $run['success'] === true) { // 记录已使用 $database->insert("used_cdk", [ "uid" => $Uid, "cdk" => $cdk_input, "used_time" => date("Y-m-d H:i:s") ]); echo "<div class='message success'>✅ 兑换成功!<br>奖励已发放:<strong>$cdk_input</strong></div>"; } else { $msg = $run['message'] ?? '接口无响应或失败'; echo "<div class='message error'>❌ 发放失败!<br>错误信息: $msg</div>"; } } catch (Exception $e) { echo "<div class='message error'>❌ 系统异常:<br>" . $e->getMessage() . "</div>"; } } ?> </div> </body> </html> 这是medoo.php文件代码 <?php /*! * Medoo database framework * https://medoo.in * Version 1.7.10 * * Copyright 2020, Angel Lai * Released under the MIT license */ namespace Medoo; use PDO; use Exception; use PDOException; use InvalidArgumentException; class Raw { public $map; public $value; } class Medoo { public $pdo; protected $type; protected $prefix; protected $statement; protected $dsn; protected $logs = []; protected $logging = false; protected $debug_mode = false; protected $guid = 0; protected $errorInfo = null; public function __construct(array $options) { if (isset($options[ 'database_type' ])) { $this->type = strtolower($options[ 'database_type' ]); if ($this->type === 'mariadb') { $this->type = 'mysql'; } } if (isset($options[ 'prefix' ])) { $this->prefix = $options[ 'prefix' ]; } if (isset($options[ 'logging' ]) && is_bool($options[ 'logging' ])) { $this->logging = $options[ 'logging' ]; } $option = isset($options[ 'option' ]) ? $options[ 'option' ] : []; $commands = (isset($options[ 'command' ]) && is_array($options[ 'command' ])) ? $options[ 'command' ] : []; switch ($this->type) { case 'mysql': // Make MySQL using standard quoted identifier $commands[] = 'SET SQL_MODE=ANSI_QUOTES'; break; case 'mssql': // Keep MSSQL QUOTED_IDENTIFIER is ON for standard quoting $commands[] = 'SET QUOTED_IDENTIFIER ON'; // Make ANSI_NULLS is ON for NULL value $commands[] = 'SET ANSI_NULLS ON'; break; } if (isset($options[ 'pdo' ])) { if (!$options[ 'pdo' ] instanceof PDO) { throw new InvalidArgumentException('Invalid PDO object supplied'); } $this->pdo = $options[ 'pdo' ]; foreach ($commands as $value) { $this->pdo->exec($value); } return; } if (isset($options[ 'dsn' ])) { if (is_array($options[ 'dsn' ]) && isset($options[ 'dsn' ][ 'driver' ])) { $attr = $options[ 'dsn' ]; } else { throw new InvalidArgumentException('Invalid DSN option supplied'); } } else { if ( isset($options[ 'port' ]) && is_int($options[ 'port' ] * 1) ) { $port = $options[ 'port' ]; } $is_port = isset($port); switch ($this->type) { case 'mysql': $attr = [ 'driver' => 'mysql', 'dbname' => $options[ 'database_name' ] ]; if (isset($options[ 'socket' ])) { $attr[ 'unix_socket' ] = $options[ 'socket' ]; } else { $attr[ 'host' ] = $options[ 'server' ]; if ($is_port) { $attr[ 'port' ] = $port; } } break; case 'pgsql': $attr = [ 'driver' => 'pgsql', 'host' => $options[ 'server' ], 'dbname' => $options[ 'database_name' ] ]; if ($is_port) { $attr[ 'port' ] = $port; } break; case 'sybase': $attr = [ 'driver' => 'dblib', 'host' => $options[ 'server' ], 'dbname' => $options[ 'database_name' ] ]; if ($is_port) { $attr[ 'port' ] = $port; } break; case 'oracle': $attr = [ 'driver' => 'oci', 'dbname' => $options[ 'server' ] ? '//' . $options[ 'server' ] . ($is_port ? ':' . $port : ':1521') . '/' . $options[ 'database_name' ] : $options[ 'database_name' ] ]; if (isset($options[ 'charset' ])) { $attr[ 'charset' ] = $options[ 'charset' ]; } break; case 'mssql': if (isset($options[ 'driver' ]) && $options[ 'driver' ] === 'dblib') { $attr = [ 'driver' => 'dblib', 'host' => $options[ 'server' ] . ($is_port ? ':' . $port : ''), 'dbname' => $options[ 'database_name' ] ]; if (isset($options[ 'appname' ])) { $attr[ 'appname' ] = $options[ 'appname' ]; } if (isset($options[ 'charset' ])) { $attr[ 'charset' ] = $options[ 'charset' ]; } } else { $attr = [ 'driver' => 'sqlsrv', 'Server' => $options[ 'server' ] . ($is_port ? ',' . $port : ''), 'Database' => $options[ 'database_name' ] ]; if (isset($options[ 'appname' ])) { $attr[ 'APP' ] = $options[ 'appname' ]; } $config = [ 'ApplicationIntent', 'AttachDBFileName', 'Authentication', 'ColumnEncryption', 'ConnectionPooling', 'Encrypt', 'Failover_Partner', 'KeyStoreAuthentication', 'KeyStorePrincipalId', 'KeyStoreSecret', 'LoginTimeout', 'MultipleActiveResultSets', 'MultiSubnetFailover', 'Scrollable', 'TraceFile', 'TraceOn', 'TransactionIsolation', 'TransparentNetworkIPResolution', 'TrustServerCertificate', 'WSID', ]; foreach ($config as $value) { $keyname = strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $value)); if (isset($options[ $keyname ])) { $attr[ $value ] = $options[ $keyname ]; } } } break; case 'sqlite': $attr = [ 'driver' => 'sqlite', $options[ 'database_file' ] ]; break; } } if (!isset($attr)) { throw new InvalidArgumentException('Incorrect connection options'); } $driver = $attr[ 'driver' ]; if (!in_array($driver, PDO::getAvailableDrivers())) { throw new InvalidArgumentException("Unsupported PDO driver: {$driver}"); } unset($attr[ 'driver' ]); $stack = []; foreach ($attr as $key => $value) { $stack[] = is_int($key) ? $value : $key . '=' . $value; } $dsn = $driver . ':' . implode(';', $stack); if ( in_array($this->type, ['mysql', 'pgsql', 'sybase', 'mssql']) && isset($options[ 'charset' ]) ) { $commands[] = "SET NAMES '{$options[ 'charset' ]}'" . ( $this->type === 'mysql' && isset($options[ 'collation' ]) ? " COLLATE '{$options[ 'collation' ]}'" : '' ); } $this->dsn = $dsn; try { $this->pdo = new PDO( $dsn, isset($options[ 'username' ]) ? $options[ 'username' ] : null, isset($options[ 'password' ]) ? $options[ 'password' ] : null, $option ); foreach ($commands as $value) { $this->pdo->exec($value); } } catch (PDOException $e) { throw new PDOException($e->getMessage()); } } public function query($query, $map = []) { $raw = $this->raw($query, $map); $query = $this->buildRaw($raw, $map); return $this->exec($query, $map); } public function exec($query, $map = []) { $this->statement = null; if ($this->debug_mode) { echo $this->generate($query, $map); $this->debug_mode = false; return false; } if ($this->logging) { $this->logs[] = [$query, $map]; } else { $this->logs = [[$query, $map]]; } $statement = $this->pdo->prepare($query); if (!$statement) { $this->errorInfo = $this->pdo->errorInfo(); $this->statement = null; return false; } $this->statement = $statement; foreach ($map as $key => $value) { $statement->bindValue($key, $value[ 0 ], $value[ 1 ]); } $execute = $statement->execute(); $this->errorInfo = $statement->errorInfo(); if (!$execute) { $this->statement = null; } return $statement; } protected function generate($query, $map) { $identifier = [ 'mysql' => '`$1`', 'mssql' => '[$1]' ]; $query = preg_replace( '/"([a-zA-Z0-9_]+)"/i', isset($identifier[ $this->type ]) ? $identifier[ $this->type ] : '"$1"', $query ); foreach ($map as $key => $value) { if ($value[ 1 ] === PDO::PARAM_STR) { $replace = $this->quote($value[ 0 ]); } elseif ($value[ 1 ] === PDO::PARAM_NULL) { $replace = 'NULL'; } elseif ($value[ 1 ] === PDO::PARAM_LOB) { $replace = '{LOB_DATA}'; } else { $replace = $value[ 0 ]; } $query = str_replace($key, $replace, $query); } return $query; } public static function raw($string, $map = []) { $raw = new Raw(); $raw->map = $map; $raw->value = $string; return $raw; } protected function isRaw($object) { return $object instanceof Raw; } protected function buildRaw($raw, &$map) { if (!$this->isRaw($raw)) { return false; } $query = preg_replace_callback( '/(([`\']).*?)?((FROM|TABLE|INTO|UPDATE|JOIN)\s*)?\<(([a-zA-Z0-9_]+)(\.[a-zA-Z0-9_]+)?)\>(.*?\2)?/i', function ($matches) { if (!empty($matches[ 2 ]) && isset($matches[ 8 ])) { return $matches[ 0 ]; } if (!empty($matches[ 4 ])) { return $matches[ 1 ] . $matches[ 4 ] . ' ' . $this->tableQuote($matches[ 5 ]); } return $matches[ 1 ] . $this->columnQuote($matches[ 5 ]); }, $raw->value); $raw_map = $raw->map; if (!empty($raw_map)) { foreach ($raw_map as $key => $value) { $map[ $key ] = $this->typeMap($value, gettype($value)); } } return $query; } public function quote($string) { return $this->pdo->quote($string); } protected function tableQuote($table) { if (!preg_match('/^[a-zA-Z0-9_]+$/i', $table)) { throw new InvalidArgumentException("Incorrect table name \"$table\""); } return '"' . $this->prefix . $table . '"'; } protected function mapKey() { return ':MeDoO_' . $this->guid++ . '_mEdOo'; } protected function typeMap($value, $type) { $map = [ 'NULL' => PDO::PARAM_NULL, 'integer' => PDO::PARAM_INT, 'double' => PDO::PARAM_STR, 'boolean' => PDO::PARAM_BOOL, 'string' => PDO::PARAM_STR, 'object' => PDO::PARAM_STR, 'resource' => PDO::PARAM_LOB ]; if ($type === 'boolean') { $value = ($value ? '1' : '0'); } elseif ($type === 'NULL') { $value = null; } return [$value, $map[ $type ]]; } protected function columnQuote($string) { if (!preg_match('/^[a-zA-Z0-9_]+(\.?[a-zA-Z0-9_]+)?$/i', $string)) { throw new InvalidArgumentException("Incorrect column name \"$string\""); } if (strpos($string, '.') !== false) { return '"' . $this->prefix . str_replace('.', '"."', $string) . '"'; } return '"' . $string . '"'; } protected function columnPush(&$columns, &$map, $root, $is_join = false) { if ($columns === '*') { return $columns; } $stack = []; if (is_string($columns)) { $columns = [$columns]; } foreach ($columns as $key => $value) { if (!is_int($key) && is_array($value) && $root && count(array_keys($columns)) === 1) { $stack[] = $this->columnQuote($key); $stack[] = $this->columnPush($value, $map, false, $is_join); } elseif (is_array($value)) { $stack[] = $this->columnPush($value, $map, false, $is_join); } elseif (!is_int($key) && $raw = $this->buildRaw($value, $map)) { preg_match('/(?<column>[a-zA-Z0-9_\.]+)(\s*\[(?<type>(String|Bool|Int|Number))\])?/i', $key, $match); $stack[] = $raw . ' AS ' . $this->columnQuote($match[ 'column' ]); } elseif (is_int($key) && is_string($value)) { if ($is_join && strpos($value, '*') !== false) { throw new InvalidArgumentException('Cannot use table.* to select all columns while joining table'); } preg_match('/(?<column>[a-zA-Z0-9_\.]+)(?:\s*\((?<alias>[a-zA-Z0-9_]+)\))?(?:\s*\[(?<type>(?:String|Bool|Int|Number|Object|JSON))\])?/i', $value, $match); if (!empty($match[ 'alias' ])) { $stack[] = $this->columnQuote($match[ 'column' ]) . ' AS ' . $this->columnQuote($match[ 'alias' ]); $columns[ $key ] = $match[ 'alias' ]; if (!empty($match[ 'type' ])) { $columns[ $key ] .= ' [' . $match[ 'type' ] . ']'; } } else { $stack[] = $this->columnQuote($match[ 'column' ]); } } } return implode(',', $stack); } protected function arrayQuote($array) { $stack = []; foreach ($array as $value) { $stack[] = is_int($value) ? $value : $this->pdo->quote($value); } return implode(',', $stack); } protected function innerConjunct($data, $map, $conjunctor, $outer_conjunctor) { $stack = []; foreach ($data as $value) { $stack[] = '(' . $this->dataImplode($value, $map, $conjunctor) . ')'; } return implode($outer_conjunctor . ' ', $stack); } protected function dataImplode($data, &$map, $conjunctor) { $stack = []; foreach ($data as $key => $value) { $type = gettype($value); if ( $type === 'array' && preg_match("/^(AND|OR)(\s+#.*)?$/", $key, $relation_match) ) { $relationship = $relation_match[ 1 ]; $stack[] = $value !== array_keys(array_keys($value)) ? '(' . $this->dataImplode($value, $map, ' ' . $relationship) . ')' : '(' . $this->innerConjunct($value, $map, ' ' . $relationship, $conjunctor) . ')'; continue; } $map_key = $this->mapKey(); if ( is_int($key) && preg_match('/([a-zA-Z0-9_\.]+)\[(?<operator>\>\=?|\<\=?|\!?\=)\]([a-zA-Z0-9_\.]+)/i', $value, $match) ) { $stack[] = $this->columnQuote($match[ 1 ]) . ' ' . $match[ 'operator' ] . ' ' . $this->columnQuote($match[ 3 ]); } else { preg_match('/([a-zA-Z0-9_\.]+)(\[(?<operator>\>\=?|\<\=?|\!|\<\>|\>\<|\!?~|REGEXP)\])?/i', $key, $match); $column = $this->columnQuote($match[ 1 ]); if (isset($match[ 'operator' ])) { $operator = $match[ 'operator' ]; if (in_array($operator, ['>', '>=', '<', '<='])) { $condition = $column . ' ' . $operator . ' '; if (is_numeric($value)) { $condition .= $map_key; $map[ $map_key ] = [$value, is_float($value) ? PDO::PARAM_STR : PDO::PARAM_INT]; } elseif ($raw = $this->buildRaw($value, $map)) { $condition .= $raw; } else { $condition .= $map_key; $map[ $map_key ] = [$value, PDO::PARAM_STR]; } $stack[] = $condition; } elseif ($operator === '!') { switch ($type) { case 'NULL': $stack[] = $column . ' IS NOT NULL'; break; case 'array': $placeholders = []; foreach ($value as $index => $item) { $stack_key = $map_key . $index . '_i'; $placeholders[] = $stack_key; $map[ $stack_key ] = $this->typeMap($item, gettype($item)); } $stack[] = $column . ' NOT IN (' . implode(', ', $placeholders) . ')'; break; case 'object': if ($raw = $this->buildRaw($value, $map)) { $stack[] = $column . ' != ' . $raw; } break; case 'integer': case 'double': case 'boolean': case 'string': $stack[] = $column . ' != ' . $map_key; $map[ $map_key ] = $this->typeMap($value, $type); break; } } elseif ($operator === '~' || $operator === '!~') { if ($type !== 'array') { $value = [ $value ]; } $connector = ' OR '; $data = array_values($value); if (is_array($data[ 0 ])) { if (isset($value[ 'AND' ]) || isset($value[ 'OR' ])) { $connector = ' ' . array_keys($value)[ 0 ] . ' '; $value = $data[ 0 ]; } } $like_clauses = []; foreach ($value as $index => $item) { $item = strval($item); if (!preg_match('/(\[.+\]|[\*\?\!\%#^-_]|%.+|.+%)/', $item)) { $item = '%' . $item . '%'; } $like_clauses[] = $column . ($operator === '!~' ? ' NOT' : '') . ' LIKE ' . $map_key . 'L' . $index; $map[ $map_key . 'L' . $index ] = [$item, PDO::PARAM_STR]; } $stack[] = '(' . implode($connector, $like_clauses) . ')'; } elseif ($operator === '<>' || $operator === '><') { if ($type === 'array') { if ($operator === '><') { $column .= ' NOT'; } $stack[] = '(' . $column . ' BETWEEN ' . $map_key . 'a AND ' . $map_key . 'b)'; $data_type = (is_numeric($value[ 0 ]) && is_numeric($value[ 1 ])) ? PDO::PARAM_INT : PDO::PARAM_STR; $map[ $map_key . 'a' ] = [$value[ 0 ], $data_type]; $map[ $map_key . 'b' ] = [$value[ 1 ], $data_type]; } } elseif ($operator === 'REGEXP') { $stack[] = $column . ' REGEXP ' . $map_key; $map[ $map_key ] = [$value, PDO::PARAM_STR]; } } else { switch ($type) { case 'NULL': $stack[] = $column . ' IS NULL'; break; case 'array': $placeholders = []; foreach ($value as $index => $item) { $stack_key = $map_key . $index . '_i'; $placeholders[] = $stack_key; $map[ $stack_key ] = $this->typeMap($item, gettype($item)); } $stack[] = $column . ' IN (' . implode(', ', $placeholders) . ')'; break; case 'object': if ($raw = $this->buildRaw($value, $map)) { $stack[] = $column . ' = ' . $raw; } break; case 'integer': case 'double': case 'boolean': case 'string': $stack[] = $column . ' = ' . $map_key; $map[ $map_key ] = $this->typeMap($value, $type); break; } } } } return implode($conjunctor . ' ', $stack); } protected function whereClause($where, &$map) { $where_clause = ''; if (is_array($where)) { $where_keys = array_keys($where); $conditions = array_diff_key($where, array_flip( ['GROUP', 'ORDER', 'HAVING', 'LIMIT', 'LIKE', 'MATCH'] )); if (!empty($conditions)) { $where_clause = ' WHERE ' . $this->dataImplode($conditions, $map, ' AND'); } if (isset($where[ 'MATCH' ]) && $this->type === 'mysql') { $MATCH = $where[ 'MATCH' ]; if (is_array($MATCH) && isset($MATCH[ 'columns' ], $MATCH[ 'keyword' ])) { $mode = ''; $mode_array = [ 'natural' => 'IN NATURAL LANGUAGE MODE', 'natural+query' => 'IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION', 'boolean' => 'IN BOOLEAN MODE', 'query' => 'WITH QUERY EXPANSION' ]; if (isset($MATCH[ 'mode' ], $mode_array[ $MATCH[ 'mode' ] ])) { $mode = ' ' . $mode_array[ $MATCH[ 'mode' ] ]; } $columns = implode(', ', array_map([$this, 'columnQuote'], $MATCH[ 'columns' ])); $map_key = $this->mapKey(); $map[ $map_key ] = [$MATCH[ 'keyword' ], PDO::PARAM_STR]; $where_clause .= ($where_clause !== '' ? ' AND ' : ' WHERE') . ' MATCH (' . $columns . ') AGAINST (' . $map_key . $mode . ')'; } } if (isset($where[ 'GROUP' ])) { $GROUP = $where[ 'GROUP' ]; if (is_array($GROUP)) { $stack = []; foreach ($GROUP as $column => $value) { $stack[] = $this->columnQuote($value); } $where_clause .= ' GROUP BY ' . implode(',', $stack); } elseif ($raw = $this->buildRaw($GROUP, $map)) { $where_clause .= ' GROUP BY ' . $raw; } else { $where_clause .= ' GROUP BY ' . $this->columnQuote($GROUP); } if (isset($where[ 'HAVING' ])) { if ($raw = $this->buildRaw($where[ 'HAVING' ], $map)) { $where_clause .= ' HAVING ' . $raw; } else { $where_clause .= ' HAVING ' . $this->dataImplode($where[ 'HAVING' ], $map, ' AND'); } } } if (isset($where[ 'ORDER' ])) { $ORDER = $where[ 'ORDER' ]; if (is_array($ORDER)) { $stack = []; foreach ($ORDER as $column => $value) { if (is_array($value)) { $stack[] = 'FIELD(' . $this->columnQuote($column) . ', ' . $this->arrayQuote($value) . ')'; } elseif ($value === 'ASC' || $value === 'DESC') { $stack[] = $this->columnQuote($column) . ' ' . $value; } elseif (is_int($column)) { $stack[] = $this->columnQuote($value); } } $where_clause .= ' ORDER BY ' . implode(',', $stack); } elseif ($raw = $this->buildRaw($ORDER, $map)) { $where_clause .= ' ORDER BY ' . $raw; } else { $where_clause .= ' ORDER BY ' . $this->columnQuote($ORDER); } if ( isset($where[ 'LIMIT' ]) && in_array($this->type, ['oracle', 'mssql']) ) { $LIMIT = $where[ 'LIMIT' ]; if (is_numeric($LIMIT)) { $LIMIT = [0, $LIMIT]; } if ( is_array($LIMIT) && is_numeric($LIMIT[ 0 ]) && is_numeric($LIMIT[ 1 ]) ) { $where_clause .= ' OFFSET ' . $LIMIT[ 0 ] . ' ROWS FETCH NEXT ' . $LIMIT[ 1 ] . ' ROWS ONLY'; } } } if (isset($where[ 'LIMIT' ]) && !in_array($this->type, ['oracle', 'mssql'])) { $LIMIT = $where[ 'LIMIT' ]; if (is_numeric($LIMIT)) { $where_clause .= ' LIMIT ' . $LIMIT; } elseif ( is_array($LIMIT) && is_numeric($LIMIT[ 0 ]) && is_numeric($LIMIT[ 1 ]) ) { $where_clause .= ' LIMIT ' . $LIMIT[ 1 ] . ' OFFSET ' . $LIMIT[ 0 ]; } } } elseif ($raw = $this->buildRaw($where, $map)) { $where_clause .= ' ' . $raw; } return $where_clause; } protected function selectContext($table, &$map, $join, &$columns = null, $where = null, $column_fn = null) { preg_match('/(?<table>[a-zA-Z0-9_]+)\s*\((?<alias>[a-zA-Z0-9_]+)\)/i', $table, $table_match); if (isset($table_match[ 'table' ], $table_match[ 'alias' ])) { $table = $this->tableQuote($table_match[ 'table' ]); $table_query = $table . ' AS ' . $this->tableQuote($table_match[ 'alias' ]); } else { $table = $this->tableQuote($table); $table_query = $table; } $is_join = false; $join_key = is_array($join) ? array_keys($join) : null; if ( isset($join_key[ 0 ]) && strpos($join_key[ 0 ], '[') === 0 ) { $is_join = true; $table_query .= ' ' . $this->buildJoin($table, $join); } else { if (is_null($columns)) { if ( !is_null($where) || (is_array($join) && isset($column_fn)) ) { $where = $join; $columns = null; } else { $where = null; $columns = $join; } } else { $where = $columns; $columns = $join; } } if (isset($column_fn)) { if ($column_fn === 1) { $column = '1'; if (is_null($where)) { $where = $columns; } } elseif ($raw = $this->buildRaw($column_fn, $map)) { $column = $raw; } else { if (empty($columns) || $this->isRaw($columns)) { $columns = '*'; $where = $join; } $column = $column_fn . '(' . $this->columnPush($columns, $map, true) . ')'; } } else { $column = $this->columnPush($columns, $map, true, $is_join); } return 'SELECT ' . $column . ' FROM ' . $table_query . $this->whereClause($where, $map); } protected function buildJoin($table, $join) { $table_join = []; $join_array = [ '>' => 'LEFT', '<' => 'RIGHT', '<>' => 'FULL', '><' => 'INNER' ]; foreach($join as $sub_table => $relation) { preg_match('/(\[(?<join>\<\>?|\>\<?)\])?(?<table>[a-zA-Z0-9_]+)\s?(\((?<alias>[a-zA-Z0-9_]+)\))?/', $sub_table, $match); if ($match[ 'join' ] !== '' && $match[ 'table' ] !== '') { if (is_string($relation)) { $relation = 'USING ("' . $relation . '")'; } if (is_array($relation)) { // For ['column1', 'column2'] if (isset($relation[ 0 ])) { $relation = 'USING ("' . implode('", "', $relation) . '")'; } else { $joins = []; foreach ($relation as $key => $value) { $joins[] = ( strpos($key, '.') > 0 ? // For ['tableB.column' => 'column'] $this->columnQuote($key) : // For ['column1' => 'column2'] $table . '."' . $key . '"' ) . ' = ' . $this->tableQuote(isset($match[ 'alias' ]) ? $match[ 'alias' ] : $match[ 'table' ]) . '."' . $value . '"'; } $relation = 'ON ' . implode(' AND ', $joins); } } $table_name = $this->tableQuote($match[ 'table' ]) . ' '; if (isset($match[ 'alias' ])) { $table_name .= 'AS ' . $this->tableQuote($match[ 'alias' ]) . ' '; } $table_join[] = $join_array[ $match[ 'join' ] ] . ' JOIN ' . $table_name . $relation; } } return implode(' ', $table_join); } protected function columnMap($columns, &$stack, $root) { if ($columns === '*') { return $stack; } foreach ($columns as $key => $value) { if (is_int($key)) { preg_match('/([a-zA-Z0-9_]+\.)?(?<column>[a-zA-Z0-9_]+)(?:\s*\((?<alias>[a-zA-Z0-9_]+)\))?(?:\s*\[(?<type>(?:String|Bool|Int|Number|Object|JSON))\])?/i', $value, $key_match); $column_key = !empty($key_match[ 'alias' ]) ? $key_match[ 'alias' ] : $key_match[ 'column' ]; if (isset($key_match[ 'type' ])) { $stack[ $value ] = [$column_key, $key_match[ 'type' ]]; } else { $stack[ $value ] = [$column_key, 'String']; } } elseif ($this->isRaw($value)) { preg_match('/([a-zA-Z0-9_]+\.)?(?<column>[a-zA-Z0-9_]+)(\s*\[(?<type>(String|Bool|Int|Number))\])?/i', $key, $key_match); $column_key = $key_match[ 'column' ]; if (isset($key_match[ 'type' ])) { $stack[ $key ] = [$column_key, $key_match[ 'type' ]]; } else { $stack[ $key ] = [$column_key, 'String']; } } elseif (!is_int($key) && is_array($value)) { if ($root && count(array_keys($columns)) === 1) { $stack[ $key ] = [$key, 'String']; } $this->columnMap($value, $stack, false); } } return $stack; } protected function dataMap($data, $columns, $column_map, &$stack, $root, &$result) { if ($root) { $columns_key = array_keys($columns); if (count($columns_key) === 1 && is_array($columns[$columns_key[0]])) { $index_key = array_keys($columns)[0]; $data_key = preg_replace("/^[a-zA-Z0-9_]+\./i", "", $index_key); $current_stack = []; foreach ($data as $item) { $this->dataMap($data, $columns[ $index_key ], $column_map, $current_stack, false, $result); $index = $data[ $data_key ]; $result[ $index ] = $current_stack; } } else { $current_stack = []; $this->dataMap($data, $columns, $column_map, $current_stack, false, $result); $result[] = $current_stack; } return; } foreach ($columns as $key => $value) { $isRaw = $this->isRaw($value); if (is_int($key) || $isRaw) { $map = $column_map[ $isRaw ? $key : $value ]; $column_key = $map[ 0 ]; $item = $data[ $column_key ]; if (isset($map[ 1 ])) { if ($isRaw && in_array($map[ 1 ], ['Object', 'JSON'])) { continue; } if (is_null($item)) { $stack[ $column_key ] = null; continue; } switch ($map[ 1 ]) { case 'Number': $stack[ $column_key ] = (double) $item; break; case 'Int': $stack[ $column_key ] = (int) $item; break; case 'Bool': $stack[ $column_key ] = (bool) $item; break; case 'Object': $stack[ $column_key ] = unserialize($item); break; case 'JSON': $stack[ $column_key ] = json_decode($item, true); break; case 'String': $stack[ $column_key ] = $item; break; } } else { $stack[ $column_key ] = $item; } } else { $current_stack = []; $this->dataMap($data, $value, $column_map, $current_stack, false, $result); $stack[ $key ] = $current_stack; } } } public function create($table, $columns, $options = null) { $stack = []; $tableName = $this->prefix . $table; foreach ($columns as $name => $definition) { if (is_int($name)) { $stack[] = preg_replace('/\<([a-zA-Z0-9_]+)\>/i', '"$1"', $definition); } elseif (is_array($definition)) { $stack[] = $name . ' ' . implode(' ', $definition); } elseif (is_string($definition)) { $stack[] = $name . ' ' . $this->query($definition); } } $table_option = ''; if (is_array($options)) { $option_stack = []; foreach ($options as $key => $value) { if (is_string($value) || is_int($value)) { $option_stack[] = "$key = $value"; } } $table_option = ' ' . implode(', ', $option_stack); } elseif (is_string($options)) { $table_option = ' ' . $options; } return $this->exec("CREATE TABLE IF NOT EXISTS $tableName (" . implode(', ', $stack) . ")$table_option"); } public function drop($table) { $tableName = $this->prefix . $table; return $this->exec("DROP TABLE IF EXISTS $tableName"); } public function select($table, $join, $columns = null, $where = null) { $map = []; $result = []; $column_map = []; $index = 0; $column = $where === null ? $join : $columns; $is_single = (is_string($column) && $column !== '*'); $query = $this->exec($this->selectContext($table, $map, $join, $columns, $where), $map); $this->columnMap($columns, $column_map, true); if (!$this->statement) { return false; } if ($columns === '*') { return $query->fetchAll(PDO::FETCH_ASSOC); } while ($data = $query->fetch(PDO::FETCH_ASSOC)) { $current_stack = []; $this->dataMap($data, $columns, $column_map, $current_stack, true, $result); } if ($is_single) { $single_result = []; $result_key = $column_map[ $column ][ 0 ]; foreach ($result as $item) { $single_result[] = $item[ $result_key ]; } return $single_result; } return $result; } public function insert($table, $datas) { $stack = []; $columns = []; $fields = []; $map = []; if (!isset($datas[ 0 ])) { $datas = [$datas]; } foreach ($datas as $data) { foreach ($data as $key => $value) { $columns[] = $key; } } $columns = array_unique($columns); foreach ($datas as $data) { $values = []; foreach ($columns as $key) { if ($raw = $this->buildRaw($data[ $key ], $map)) { $values[] = $raw; continue; } $map_key = $this->mapKey(); $values[] = $map_key; if (!isset($data[ $key ])) { $map[ $map_key ] = [null, PDO::PARAM_NULL]; } else { $value = $data[ $key ]; $type = gettype($value); switch ($type) { case 'array': $map[ $map_key ] = [ strpos($key, '[JSON]') === strlen($key) - 6 ? json_encode($value) : serialize($value), PDO::PARAM_STR ]; break; case 'object': $value = serialize($value); case 'NULL': case 'resource': case 'boolean': case 'integer': case 'double': case 'string': $map[ $map_key ] = $this->typeMap($value, $type); break; } } } $stack[] = '(' . implode(', ', $values) . ')'; } foreach ($columns as $key) { $fields[] = $this->columnQuote(preg_replace("/(\s*\[JSON\]$)/i", '', $key)); } return $this->exec('INSERT INTO ' . $this->tableQuote($table) . ' (' . implode(', ', $fields) . ') VALUES ' . implode(', ', $stack), $map); } public function update($table, $data, $where = null) { $fields = []; $map = []; foreach ($data as $key => $value) { $column = $this->columnQuote(preg_replace("/(\s*\[(JSON|\+|\-|\*|\/)\]$)/i", '', $key)); if ($raw = $this->buildRaw($value, $map)) { $fields[] = $column . ' = ' . $raw; continue; } $map_key = $this->mapKey(); preg_match('/(?<column>[a-zA-Z0-9_]+)(\[(?<operator>\+|\-|\*|\/)\])?/i', $key, $match); if (isset($match[ 'operator' ])) { if (is_numeric($value)) { $fields[] = $column . ' = ' . $column . ' ' . $match[ 'operator' ] . ' ' . $value; } } else { $fields[] = $column . ' = ' . $map_key; $type = gettype($value); switch ($type) { case 'array': $map[ $map_key ] = [ strpos($key, '[JSON]') === strlen($key) - 6 ? json_encode($value) : serialize($value), PDO::PARAM_STR ]; break; case 'object': $value = serialize($value); case 'NULL': case 'resource': case 'boolean': case 'integer': case 'double': case 'string': $map[ $map_key ] = $this->typeMap($value, $type); break; } } } return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . implode(', ', $fields) . $this->whereClause($where, $map), $map); } public function delete($table, $where) { $map = []; return $this->exec('DELETE FROM ' . $this->tableQuote($table) . $this->whereClause($where, $map), $map); } public function replace($table, $columns, $where = null) { if (!is_array($columns) || empty($columns)) { return false; } $map = []; $stack = []; foreach ($columns as $column => $replacements) { if (is_array($replacements)) { foreach ($replacements as $old => $new) { $map_key = $this->mapKey(); $stack[] = $this->columnQuote($column) . ' = REPLACE(' . $this->columnQuote($column) . ', ' . $map_key . 'a, ' . $map_key . 'b)'; $map[ $map_key . 'a' ] = [$old, PDO::PARAM_STR]; $map[ $map_key . 'b' ] = [$new, PDO::PARAM_STR]; } } } if (!empty($stack)) { return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . implode(', ', $stack) . $this->whereClause($where, $map), $map); } return false; } public function get($table, $join = null, $columns = null, $where = null) { $map = []; $result = []; $column_map = []; $current_stack = []; if ($where === null) { $column = $join; unset($columns[ 'LIMIT' ]); } else { $column = $columns; unset($where[ 'LIMIT' ]); } $is_single = (is_string($column) && $column !== '*'); $query = $this->exec($this->selectContext($table, $map, $join, $columns, $where) . ' LIMIT 1', $map); if (!$this->statement) { return false; } $data = $query->fetchAll(PDO::FETCH_ASSOC); if (isset($data[ 0 ])) { if ($column === '*') { return $data[ 0 ]; } $this->columnMap($columns, $column_map, true); $this->dataMap($data[ 0 ], $columns, $column_map, $current_stack, true, $result); if ($is_single) { return $result[ 0 ][ $column_map[ $column ][ 0 ] ]; } return $result[ 0 ]; } } public function has($table, $join, $where = null) { $map = []; $column = null; if ($this->type === 'mssql') { $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, Medoo::raw('TOP 1 1')), $map); } else { $query = $this->exec('SELECT EXISTS(' . $this->selectContext($table, $map, $join, $column, $where, 1) . ')', $map); } if (!$this->statement) { return false; } $result = $query->fetchColumn(); return $result === '1' || $result === 1 || $result === true; } public function rand($table, $join = null, $columns = null, $where = null) { $type = $this->type; $order = 'RANDOM()'; if ($type === 'mysql') { $order = 'RAND()'; } elseif ($type === 'mssql') { $order = 'NEWID()'; } $order_raw = $this->raw($order); if ($where === null) { if ($columns === null) { $columns = [ 'ORDER' => $order_raw ]; } else { $column = $join; unset($columns[ 'ORDER' ]); $columns[ 'ORDER' ] = $order_raw; } } else { unset($where[ 'ORDER' ]); $where[ 'ORDER' ] = $order_raw; } return $this->select($table, $join, $columns, $where); } private function aggregate($type, $table, $join = null, $column = null, $where = null) { $map = []; $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, strtoupper($type)), $map); if (!$this->statement) { return false; } $number = $query->fetchColumn(); return is_numeric($number) ? $number + 0 : $number; } public function count($table, $join = null, $column = null, $where = null) { return $this->aggregate('count', $table, $join, $column, $where); } public function avg($table, $join, $column = null, $where = null) { return $this->aggregate('avg', $table, $join, $column, $where); } public function max($table, $join, $column = null, $where = null) { return $this->aggregate('max', $table, $join, $column, $where); } public function min($table, $join, $column = null, $where = null) { return $this->aggregate('min', $table, $join, $column, $where); } public function sum($table, $join, $column = null, $where = null) { return $this->aggregate('sum', $table, $join, $column, $where); } public function action($actions) { if (is_callable($actions)) { $this->pdo->beginTransaction(); try { $result = $actions($this); if ($result === false) { $this->pdo->rollBack(); } else { $this->pdo->commit(); } } catch (Exception $e) { $this->pdo->rollBack(); throw $e; } return $result; } return false; } public function id() { if ($this->statement == null) { return null; } $type = $this->type; if ($type === 'oracle') { return 0; } elseif ($type === 'pgsql') { return $this->pdo->query('SELECT LASTVAL()')->fetchColumn(); } $lastId = $this->pdo->lastInsertId(); if ($lastId != "0" && $lastId != "") { return $lastId; } return null; } public function debug() { $this->debug_mode = true; return $this; } public function error() { return $this->errorInfo; } public function last() { $log = end($this->logs); return $this->generate($log[ 0 ], $log[ 1 ]); } public function log() { return array_map(function ($log) { return $this->generate($log[ 0 ], $log[ 1 ]); }, $this->logs ); } public function info() { $output = [ 'server' => 'SERVER_INFO', 'driver' => 'DRIVER_NAME', 'client' => 'CLIENT_VERSION', 'version' => 'SERVER_VERSION', 'connection' => 'CONNECTION_STATUS' ]; foreach ($output as $key => $value) { $output[ $key ] = @$this->pdo->getAttribute(constant('PDO::ATTR_' . $value)); } $output[ 'dsn' ] = $this->dsn; return $output; } } 完整写出修复后的代码 我复制
11-29
cdk兑换页面的代码 <?php ini_set('display_errors', 0); ini_set('log_errors', 1); ini_set('error_log', './cdk_error.log'); header('Cache-Control: no-cache, must-revalidate'); header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); ?> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>CDK兑换中心</title> <style> body { font-family: Arial, sans-serif; background: #f4f4f4; padding: 20px; } .container { max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); } input[type="text"] { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ccc; border-radius: 5px; font-size: 16px; } button { background: #007BFF; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; } button:hover { background: #0056b3; } .message { padding: 10px; margin: 10px 0; border-radius: 5px; } .error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } hr { margin: 20px 0; border: 1px dashed #ddd; } </style> </head> <body> <div class="container"> <h2 style="text-align:center;">🎮 CDK兑换系统</h2> <form method="post"> UID: <input type="text" name="uid" placeholder="输入你的游戏UID" required /><br/> CDK: <input type="text" name="cdk" placeholder="输入兑换码" required /><br/> <button type="submit">立即兑换</button> </form> <?php if (!isset($_POST['uid']) || !isset($_POST['cdk'])) { echo '</div></body></html>'; exit; } if (!file_exists('./Medoo.php')) { die("<div class='message error'>❌ 找不到数据库驱动文件 Medoo.php</div></div></body></html>"); } require_once './Medoo.php'; use Medoo\Medoo; try { $database = new Medoo([ 'database_type' => 'mysql', 'database_name' => 'db_hk4e_gm', 'server' => '127.0.0.1', 'username' => 'db_hk4e_gm', 'password' => 'syxywl.cn', 'charset' => 'utf8mb4', 'port' => 3306, ]); } catch (Exception $e) { error_log("【CRITICAL】数据库连接失败:" . $e->getMessage()); echo "<div class='message error'>❌ 系统维护中,请稍后再试。</div>"; goto showForm; } $Uid = intval(trim($_POST['uid'] ?? '')); $cdk_input = trim($_POST['cdk'] ?? ''); if ($Uid <= 0) { echo "<div class='message error'>❌ 请输入有效的数字UID!</div>"; goto showForm; } if (empty($cdk_input)) { echo "<div class='message error'>❌ 请输入兑换码!</div>"; goto showForm; } try { $cdkData = $database->get("cdk", ["id", "item", "number", "cmd_type", "start"], ["cdk" => $cdk_input]); if (!$cdkData) { echo "<div class='message error'>❌ 兑换码不存在!</div>"; goto showForm; } $status = (int)$cdkData['start']; $itemId = (int)$cdkData["item"]; $num = (int)$cdkData["number"]; $raw_cmd_type = $cdkData["cmd_type"] ?? "item"; $cmd_map = [ 'player_level' => 'level', 'player_exp' => 'exp', 'player_money' => 'money', 'player_vip' => 'vip', 'player_mcoin' => 'mcoin', ]; $cmd_type = $cmd_map[$raw_cmd_type] ?? $raw_cmd_type; if ($itemId === 203) { $cmd_type = 'mcoin'; } switch ($status) { case 0: echo "<div class='message error'>❌ 此兑换码已失效!或已被使用</div>"; break; case 1: $used = $database->get("used_cdk", ["id"], ["AND" => ["uid" => $Uid, "cdk" => $cdk_input]]); if ($used) { echo "<div class='message error'>❌ 该兑换码已被您领取过!</div>"; break; } if (callApiToGrant($Uid, $itemId, $num, $cmd_type)) { $database->insert("used_cdk", [ "uid" => $Uid, "cdk" => $cdk_input, "item" => $itemId, "number" => $num, "cmd_type" => $cmd_type, "created_at" => date('Y-m-d H:i:s'), ]); $database->update("cdk", ["start" => 0], ["cdk" => $cdk_input]); echo "<div class='message success'>🎉 兑换成功!命令已执行,请进入游戏查看。</div>"; } else { echo "<div class='message error'>❌ 兑换失败:服务器未响应或角色异常,请检查后重试。</div>"; } break; case 2: $record = $database->get("used_cdk", ["uid"], ["cdk" => $cdk_input]); if (!$record) { if (callApiToGrant($Uid, $itemId, $num, $cmd_type)) { $database->insert("used_cdk", [ "uid" => $Uid, "cdk" => $cdk_input, "item" => $itemId, "number" => $num, "cmd_type" => $cmd_type, "created_at" => date('Y-m-d H:i:s'), ]); echo "<div class='message success'>🎉 首次兑换成功!已绑定您的账号,今后可重复使用此CDK。</div>"; } else { echo "<div class='message error'>❌ 首次兑换失败,未绑定账号,请检查UID是否正确。</div>"; } } elseif ((int)$record['uid'] === $Uid) { if (callApiToGrant($Uid, $itemId, $num, $cmd_type)) { $database->insert("used_cdk", [ "uid" => $Uid, "cdk" => $cdk_input, "item" => $itemId, "number" => $num, "cmd_type" => $cmd_type, "created_at" => date('Y-m-d H:i:s'), ]); echo "<div class='message success'>🎉 无限移动体力!再次开启成功。</div>"; } else { echo "<div class='message error'>❌ 兑换失败,请稍后重试。</div>"; } } else { echo "<div class='message error'>❌ 此兑换码已被其他用户绑定,无法使用!</div>"; } break; default: echo "<div class='message error'>❌ 未知的兑换码状态,请联系管理员。</div>"; break; } } catch (Exception $e) { error_log("【EXCEPTION】" . $e->getMessage() . " | UID=$Uid, CDK=$cdk_input"); echo "<div class='message error'>❌ 系统异常,请稍后再试。</div>"; } showForm: ?> </div> </body> </html> <?php function callApiToGrant($uid, $item, $number, $cmd_type) { $params = [ 'adminpass' => 'syxywlQQ28009618hello', 'uid' => $uid, 'item' => $item, 'number' => $number, 'cmd_type' => $cmd_type, 'sha' => md5('getflag'), ]; $api_url = "http://202.189.14.206:344/api/api.php?" . http_build_query($params); $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $api_url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_USERAGENT => 'CDK-System/v1', CURLOPT_REFERER => 'https://your-site.com/cdk', CURLOPT_SSL_VERIFYPEER => false, CURLOPT_HTTPHEADER => ['Expect:'], CURLOPT_FOLLOWLOCATION => true, ]); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curl_error = curl_error($ch); curl_close($ch); if ($curl_error) { error_log("【cURL ERROR】$curl_error | URL=" . htmlspecialchars($api_url)); return false; } if ($http_code !== 200) { error_log("【HTTP ERROR】Code=$http_code | UID=$uid, Item=$item"); return false; } $result = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { error_log("【JSON PARSE FAIL】Response: $response"); return false; } return $result && isset($result['success']) && $result['success'] === true; } cdk生成页面的代码 <?php ob_start(); error_reporting(E_ALL); ini_set('display_errors', 1); $message = ''; // 检查 Medoo 是否存在 if (!file_exists('./Medoo.php')) { die('<font color="red">❌ 缺少 Medoo.php 文件,请将它本文件放在同一目录。</font>'); } require_once './Medoo.php'; use Medoo\Medoo; // 数据库连接配置 try { $database = new Medoo([ 'database_type' => 'mysql', 'database_name' => 'db_hk4e_gm', 'server' => '127.0.0.1', 'username' => 'db_hk4e_gm', 'password' => 'syxywl.cn', 'charset' => 'utf8', 'port' => 3306, 'option' => [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION], ]); // 测试连接 $database->query("SELECT 1")->fetch(); } catch (Exception $e) { echo '<font color="red">❌ 数据库连接失败:' . htmlspecialchars($e->getMessage()) . '</font>'; exit; } // 处理表单提交 if ($_POST['addcdk'] ?? false) { $adminpass = trim($_POST["adminpass"] ?? ''); $cmd_type_input = $_POST["cmd_type"] ?? ''; if ($adminpass !== '787673') { $message = '<font size="4" color="#FF4D4D">❌ GM密码错误!请检查后重试。</font>'; } else { try { $success = 0; $codes = []; // 固定生成一个CDK(可扩展为批量) do { $raw = time() . mt_rand(100000, 999999); $code = strtoupper(substr(base_convert($raw, 10, 36), 0, 10)); $exists = $database->get("cdk", "id", ["cdk" => $code]); } while ($exists); $data = [ "cdk" => $code, "start" => 1, "cmd_type" => '', "item" => 0, "number" => 1 ]; switch ($cmd_type_input) { case 'player_level': $level = intval($_POST["level"] ?? 0); if ($level < 1 || $level > 60) { $message = '<font size="4" color="#FFA500">⚠️ 玩家等级必须在 1~60 之间。</font>'; goto showForm; } $data["cmd_type"] = 'player_level'; $data["item"] = $level; $data["number"] = 1; $desc = "🎯 设置玩家等级为 {$level}"; break; case 'point_3_all': $data["cmd_type"] = 'point'; $data["item"] = 3; $data["number"] = 999; // 特殊值表示“全部” $data["start"] = 1; $desc = "📍 开启所有锚点 (point 3 all)"; break; case 'stamina_infinite_on': $data["cmd_type"] = 'stamina'; $data["item"] = 0; $data["number"] = 0; $data["start"] = 2; // 允许重复兑换 $desc = "⚡ 开启无限体力 (stamina infinite on)"; break; default: $message = '<font size="4" color="#FFA500">⚠️ 无效的操作类型。</font>'; goto showForm; } // 写入数据库 $database->insert("cdk", $data); $codes[] = $code; $success++; // 成功提示 $message = "<font size='4' color='#00BFFF'>🎉 成功生成 {$success} 个 CDK:</font>({$desc})<br>"; foreach ($codes as $c) { $message .= "<font size='4' color='green'><b>{$c}</b></font><br>"; } } catch (Exception $e) { error_log("[CDK生成失败] " . $e->getMessage()); $message = '<font size="4" color="red">❌ 操作失败:' . htmlspecialchars($e->getMessage()) . '</font>'; } } } showForm: $html = ob_get_clean(); ?> <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8" /> <title>🔐 CDK 生成系统</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; background: #f0f2f5; padding: 40px; margin: 0; } .container { max-width: 600px; margin: 0 auto; background: white; padding: 30px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } h2 { text-align: center; color: #2c3e50; margin-bottom: 10px; } p.desc { text-align: center; color: #7f8c8d; font-size: 14px; margin-top: 0; } label { display: block; margin-top: 18px; font-weight: bold; color: #2c3e50; } input[type="text"], input[type="number"], select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus, select:focus { outline: none; border-color: #007BFF; box-shadow: 0 0 5px rgba(0,123,255,0.3); } input[type="submit"] { margin-top: 24px; width: 100%; padding: 12px; background: #007BFF; color: white; border: none; border-radius: 6px; font-size: 18px; cursor: pointer; transition: background 0.2s; } input[type="submit"]:hover { background: #0056b3; } .result-box { margin-top: 20px; line-height: 1.8; font-family: monospace; font-size: 14px; white-space: pre-wrap; word-break: break-all; } </style> </head> <body> <div class="container"> <h2>🔐 CDK 生成系统</h2> <p class="desc">为游戏运营快速生成专用兑换码</p> <?php if (!empty($message)): ?> <div class="result-box"><?php echo $message; ?></div> <?php endif; ?> <form method="post"> <label for="adminpass">🔑 GM 密码:</label> <input type="text" name="adminpass" placeholder="输入管理员密码" required autocomplete="off" /> <label for="cmd_type">🛠️ 操作类型:</label> <select name="cmd_type" id="cmd_type"> <option value="player_level">🎯 设置玩家等级</option> <option value="point_3_all">📍 开启所有传送锚点 (point 3 all)</option> <option value="stamina_infinite_on">⚡ 开启无限体力(可重复使用)</option> </select> <!-- 等级输入框 --> <div id="level_field"> <label for="level">🔢 目标等级(1-60):</label> <input type="number" name="level" min="1" max="60" value="30" /> </div> <input type="submit" name="addcdk" value="✅ 生成 CDK" /> </form> <script> document.getElementById('cmd_type').addEventListener('change', function () { const levelField = document.getElementById('level_field'); levelField.style.display = this.value === 'player_level' ? 'block' : 'none'; }); window.onload = function () { const event = new Event('change'); document.getElementById('cmd_type').dispatchEvent(event); }; </script> </div> </body> </html> api/api.php的代码 <?php // api/api.php // === 配置区 === define('MUIP_API', 'http://127.0.0.1:62221/api'); define('REGION', 'dev_gio'); define('TIME_TOLERANT', 5); define('PRIVATE_KEY_SIZE', 4096); define('PRIVATE_KEY', '$$$PRIVATE_KEY$$$'); // 签名函数(根据实际算法调整) function calcSign($params, $secretKey) { ksort($params); $str = ''; foreach ($params as $k => $v) { $str .= $k . '=' . $v . '&'; } $str = rtrim($str, '&'); return md5($str . $secretKey); } // 返回 JSON 并结束 function returnJSON($data, $code = 200) { http_response_code($code); header('Content-Type: application/json'); echo json_encode($data, JSON_UNESCAPED_UNICODE); exit; } // === 认证校验 === if ($_GET["adminpass"] !== "syxywlQQ28009618hello") { returnJSON(['success' => false, 'message' => 'gm码错误'], 403); } if ($_GET["sha"] !== md5("getflag")) { returnJSON(['success' => false, 'message' => 'gm码错误'], 403); } $uid = intval($_GET["uid"]); $item = $_GET["item"] ?? ''; $number = max(1, intval($_GET["number"] ?? 1)); if ($uid <= 0) { returnJSON(['success' => false, 'message' => '无效UID'], 400); } // === 命令类型映射表 === $commandMap = [ 'item' => "item add $item $number", 'mcoin' => "mcoin $number", 'level' => "player level $item", 'exp' => "player exp $item", 'money' => "player money $item", 'vip' => "player vip $item", 'teleport' => "player teleport " . (floatval($item) ?: '0') . " 0", 'custom_cmd' => $item, ]; // 获取命令类型 $cmdType = $_GET['cmd_type'] ?? 'item'; // 特殊兼容:item=203 → mcoin if ($item == 203) { $cmdType = 'mcoin'; } if (!isset($commandMap[$cmdType])) { returnJSON(['success' => false, 'message' => "不支持的命令类型: $cmdType"], 400); } $command = $commandMap[$cmdType]; // === 构造并发送 GM 请求 === $query = [ 'cmd' => 1116, 'uid' => $uid, 'msg' => $command, 'region' => REGION ]; $query['sign'] = calcSign($query, "27bwq^d4zzXpdUxf"); $ch = curl_init(MUIP_API . '?' . http_build_query($query)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $rsp = curl_exec($ch); if (curl_error($ch)) { returnJSON(['success' => false, 'message' => '请求失败: ' . curl_error($ch)], 500); } curl_close($ch); $rspData = @json_decode($rsp, true); returnJSON([ 'success' => isset($rspData['retcode']) && $rspData['retcode'] === 0, 'message' => $rspData['msg'] ?? '操作完成', 'data' => $rspData['data'] ?? null, 'debug' => [ // 调试用,上线前可删除 'command_sent' => $command, 'cmd_type' => $cmdType, 'uid' => $uid ] ]); ?> Medoo.php内的代码 <?php /*! * Medoo database framework * https://medoo.in * Version 1.7.10 * * Copyright 2020, Angel Lai * Released under the MIT license */ namespace Medoo; use PDO; use Exception; use PDOException; use InvalidArgumentException; class Raw { public $map; public $value; } class Medoo { public $pdo; protected $type; protected $prefix; protected $statement; protected $dsn; protected $logs = []; protected $logging = false; protected $debug_mode = false; protected $guid = 0; protected $errorInfo = null; public function __construct(array $options) { if (isset($options[ 'database_type' ])) { $this->type = strtolower($options[ 'database_type' ]); if ($this->type === 'mariadb') { $this->type = 'mysql'; } } if (isset($options[ 'prefix' ])) { $this->prefix = $options[ 'prefix' ]; } if (isset($options[ 'logging' ]) && is_bool($options[ 'logging' ])) { $this->logging = $options[ 'logging' ]; } $option = isset($options[ 'option' ]) ? $options[ 'option' ] : []; $commands = (isset($options[ 'command' ]) && is_array($options[ 'command' ])) ? $options[ 'command' ] : []; switch ($this->type) { case 'mysql': // Make MySQL using standard quoted identifier $commands[] = 'SET SQL_MODE=ANSI_QUOTES'; break; case 'mssql': // Keep MSSQL QUOTED_IDENTIFIER is ON for standard quoting $commands[] = 'SET QUOTED_IDENTIFIER ON'; // Make ANSI_NULLS is ON for NULL value $commands[] = 'SET ANSI_NULLS ON'; break; } if (isset($options[ 'pdo' ])) { if (!$options[ 'pdo' ] instanceof PDO) { throw new InvalidArgumentException('Invalid PDO object supplied'); } $this->pdo = $options[ 'pdo' ]; foreach ($commands as $value) { $this->pdo->exec($value); } return; } if (isset($options[ 'dsn' ])) { if (is_array($options[ 'dsn' ]) && isset($options[ 'dsn' ][ 'driver' ])) { $attr = $options[ 'dsn' ]; } else { throw new InvalidArgumentException('Invalid DSN option supplied'); } } else { if ( isset($options[ 'port' ]) && is_int($options[ 'port' ] * 1) ) { $port = $options[ 'port' ]; } $is_port = isset($port); switch ($this->type) { case 'mysql': $attr = [ 'driver' => 'mysql', 'dbname' => $options[ 'database_name' ] ]; if (isset($options[ 'socket' ])) { $attr[ 'unix_socket' ] = $options[ 'socket' ]; } else { $attr[ 'host' ] = $options[ 'server' ]; if ($is_port) { $attr[ 'port' ] = $port; } } break; case 'pgsql': $attr = [ 'driver' => 'pgsql', 'host' => $options[ 'server' ], 'dbname' => $options[ 'database_name' ] ]; if ($is_port) { $attr[ 'port' ] = $port; } break; case 'sybase': $attr = [ 'driver' => 'dblib', 'host' => $options[ 'server' ], 'dbname' => $options[ 'database_name' ] ]; if ($is_port) { $attr[ 'port' ] = $port; } break; case 'oracle': $attr = [ 'driver' => 'oci', 'dbname' => $options[ 'server' ] ? '//' . $options[ 'server' ] . ($is_port ? ':' . $port : ':1521') . '/' . $options[ 'database_name' ] : $options[ 'database_name' ] ]; if (isset($options[ 'charset' ])) { $attr[ 'charset' ] = $options[ 'charset' ]; } break; case 'mssql': if (isset($options[ 'driver' ]) && $options[ 'driver' ] === 'dblib') { $attr = [ 'driver' => 'dblib', 'host' => $options[ 'server' ] . ($is_port ? ':' . $port : ''), 'dbname' => $options[ 'database_name' ] ]; if (isset($options[ 'appname' ])) { $attr[ 'appname' ] = $options[ 'appname' ]; } if (isset($options[ 'charset' ])) { $attr[ 'charset' ] = $options[ 'charset' ]; } } else { $attr = [ 'driver' => 'sqlsrv', 'Server' => $options[ 'server' ] . ($is_port ? ',' . $port : ''), 'Database' => $options[ 'database_name' ] ]; if (isset($options[ 'appname' ])) { $attr[ 'APP' ] = $options[ 'appname' ]; } $config = [ 'ApplicationIntent', 'AttachDBFileName', 'Authentication', 'ColumnEncryption', 'ConnectionPooling', 'Encrypt', 'Failover_Partner', 'KeyStoreAuthentication', 'KeyStorePrincipalId', 'KeyStoreSecret', 'LoginTimeout', 'MultipleActiveResultSets', 'MultiSubnetFailover', 'Scrollable', 'TraceFile', 'TraceOn', 'TransactionIsolation', 'TransparentNetworkIPResolution', 'TrustServerCertificate', 'WSID', ]; foreach ($config as $value) { $keyname = strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $value)); if (isset($options[ $keyname ])) { $attr[ $value ] = $options[ $keyname ]; } } } break; case 'sqlite': $attr = [ 'driver' => 'sqlite', $options[ 'database_file' ] ]; break; } } if (!isset($attr)) { throw new InvalidArgumentException('Incorrect connection options'); } $driver = $attr[ 'driver' ]; if (!in_array($driver, PDO::getAvailableDrivers())) { throw new InvalidArgumentException("Unsupported PDO driver: {$driver}"); } unset($attr[ 'driver' ]); $stack = []; foreach ($attr as $key => $value) { $stack[] = is_int($key) ? $value : $key . '=' . $value; } $dsn = $driver . ':' . implode(';', $stack); if ( in_array($this->type, ['mysql', 'pgsql', 'sybase', 'mssql']) && isset($options[ 'charset' ]) ) { $commands[] = "SET NAMES '{$options[ 'charset' ]}'" . ( $this->type === 'mysql' && isset($options[ 'collation' ]) ? " COLLATE '{$options[ 'collation' ]}'" : '' ); } $this->dsn = $dsn; try { $this->pdo = new PDO( $dsn, isset($options[ 'username' ]) ? $options[ 'username' ] : null, isset($options[ 'password' ]) ? $options[ 'password' ] : null, $option ); foreach ($commands as $value) { $this->pdo->exec($value); } } catch (PDOException $e) { throw new PDOException($e->getMessage()); } } public function query($query, $map = []) { $raw = $this->raw($query, $map); $query = $this->buildRaw($raw, $map); return $this->exec($query, $map); } public function exec($query, $map = []) { $this->statement = null; if ($this->debug_mode) { echo $this->generate($query, $map); $this->debug_mode = false; return false; } if ($this->logging) { $this->logs[] = [$query, $map]; } else { $this->logs = [[$query, $map]]; } $statement = $this->pdo->prepare($query); if (!$statement) { $this->errorInfo = $this->pdo->errorInfo(); $this->statement = null; return false; } $this->statement = $statement; foreach ($map as $key => $value) { $statement->bindValue($key, $value[ 0 ], $value[ 1 ]); } $execute = $statement->execute(); $this->errorInfo = $statement->errorInfo(); if (!$execute) { $this->statement = null; } return $statement; } protected function generate($query, $map) { $identifier = [ 'mysql' => '`$1`', 'mssql' => '[$1]' ]; $query = preg_replace( '/"([a-zA-Z0-9_]+)"/i', isset($identifier[ $this->type ]) ? $identifier[ $this->type ] : '"$1"', $query ); foreach ($map as $key => $value) { if ($value[ 1 ] === PDO::PARAM_STR) { $replace = $this->quote($value[ 0 ]); } elseif ($value[ 1 ] === PDO::PARAM_NULL) { $replace = 'NULL'; } elseif ($value[ 1 ] === PDO::PARAM_LOB) { $replace = '{LOB_DATA}'; } else { $replace = $value[ 0 ]; } $query = str_replace($key, $replace, $query); } return $query; } public static function raw($string, $map = []) { $raw = new Raw(); $raw->map = $map; $raw->value = $string; return $raw; } protected function isRaw($object) { return $object instanceof Raw; } protected function buildRaw($raw, &$map) { if (!$this->isRaw($raw)) { return false; } $query = preg_replace_callback( '/(([`\']).*?)?((FROM|TABLE|INTO|UPDATE|JOIN)\s*)?\<(([a-zA-Z0-9_]+)(\.[a-zA-Z0-9_]+)?)\>(.*?\2)?/i', function ($matches) { if (!empty($matches[ 2 ]) && isset($matches[ 8 ])) { return $matches[ 0 ]; } if (!empty($matches[ 4 ])) { return $matches[ 1 ] . $matches[ 4 ] . ' ' . $this->tableQuote($matches[ 5 ]); } return $matches[ 1 ] . $this->columnQuote($matches[ 5 ]); }, $raw->value); $raw_map = $raw->map; if (!empty($raw_map)) { foreach ($raw_map as $key => $value) { $map[ $key ] = $this->typeMap($value, gettype($value)); } } return $query; } public function quote($string) { return $this->pdo->quote($string); } protected function tableQuote($table) { if (!preg_match('/^[a-zA-Z0-9_]+$/i', $table)) { throw new InvalidArgumentException("Incorrect table name \"$table\""); } return '"' . $this->prefix . $table . '"'; } protected function mapKey() { return ':MeDoO_' . $this->guid++ . '_mEdOo'; } protected function typeMap($value, $type) { $map = [ 'NULL' => PDO::PARAM_NULL, 'integer' => PDO::PARAM_INT, 'double' => PDO::PARAM_STR, 'boolean' => PDO::PARAM_BOOL, 'string' => PDO::PARAM_STR, 'object' => PDO::PARAM_STR, 'resource' => PDO::PARAM_LOB ]; if ($type === 'boolean') { $value = ($value ? '1' : '0'); } elseif ($type === 'NULL') { $value = null; } return [$value, $map[ $type ]]; } protected function columnQuote($string) { if (!preg_match('/^[a-zA-Z0-9_]+(\.?[a-zA-Z0-9_]+)?$/i', $string)) { throw new InvalidArgumentException("Incorrect column name \"$string\""); } if (strpos($string, '.') !== false) { return '"' . $this->prefix . str_replace('.', '"."', $string) . '"'; } return '"' . $string . '"'; } protected function columnPush(&$columns, &$map, $root, $is_join = false) { if ($columns === '*') { return $columns; } $stack = []; if (is_string($columns)) { $columns = [$columns]; } foreach ($columns as $key => $value) { if (!is_int($key) && is_array($value) && $root && count(array_keys($columns)) === 1) { $stack[] = $this->columnQuote($key); $stack[] = $this->columnPush($value, $map, false, $is_join); } elseif (is_array($value)) { $stack[] = $this->columnPush($value, $map, false, $is_join); } elseif (!is_int($key) && $raw = $this->buildRaw($value, $map)) { preg_match('/(?<column>[a-zA-Z0-9_\.]+)(\s*\[(?<type>(String|Bool|Int|Number))\])?/i', $key, $match); $stack[] = $raw . ' AS ' . $this->columnQuote($match[ 'column' ]); } elseif (is_int($key) && is_string($value)) { if ($is_join && strpos($value, '*') !== false) { throw new InvalidArgumentException('Cannot use table.* to select all columns while joining table'); } preg_match('/(?<column>[a-zA-Z0-9_\.]+)(?:\s*\((?<alias>[a-zA-Z0-9_]+)\))?(?:\s*\[(?<type>(?:String|Bool|Int|Number|Object|JSON))\])?/i', $value, $match); if (!empty($match[ 'alias' ])) { $stack[] = $this->columnQuote($match[ 'column' ]) . ' AS ' . $this->columnQuote($match[ 'alias' ]); $columns[ $key ] = $match[ 'alias' ]; if (!empty($match[ 'type' ])) { $columns[ $key ] .= ' [' . $match[ 'type' ] . ']'; } } else { $stack[] = $this->columnQuote($match[ 'column' ]); } } } return implode(',', $stack); } protected function arrayQuote($array) { $stack = []; foreach ($array as $value) { $stack[] = is_int($value) ? $value : $this->pdo->quote($value); } return implode(',', $stack); } protected function innerConjunct($data, $map, $conjunctor, $outer_conjunctor) { $stack = []; foreach ($data as $value) { $stack[] = '(' . $this->dataImplode($value, $map, $conjunctor) . ')'; } return implode($outer_conjunctor . ' ', $stack); } protected function dataImplode($data, &$map, $conjunctor) { $stack = []; foreach ($data as $key => $value) { $type = gettype($value); if ( $type === 'array' && preg_match("/^(AND|OR)(\s+#.*)?$/", $key, $relation_match) ) { $relationship = $relation_match[ 1 ]; $stack[] = $value !== array_keys(array_keys($value)) ? '(' . $this->dataImplode($value, $map, ' ' . $relationship) . ')' : '(' . $this->innerConjunct($value, $map, ' ' . $relationship, $conjunctor) . ')'; continue; } $map_key = $this->mapKey(); if ( is_int($key) && preg_match('/([a-zA-Z0-9_\.]+)\[(?<operator>\>\=?|\<\=?|\!?\=)\]([a-zA-Z0-9_\.]+)/i', $value, $match) ) { $stack[] = $this->columnQuote($match[ 1 ]) . ' ' . $match[ 'operator' ] . ' ' . $this->columnQuote($match[ 3 ]); } else { preg_match('/([a-zA-Z0-9_\.]+)(\[(?<operator>\>\=?|\<\=?|\!|\<\>|\>\<|\!?~|REGEXP)\])?/i', $key, $match); $column = $this->columnQuote($match[ 1 ]); if (isset($match[ 'operator' ])) { $operator = $match[ 'operator' ]; if (in_array($operator, ['>', '>=', '<', '<='])) { $condition = $column . ' ' . $operator . ' '; if (is_numeric($value)) { $condition .= $map_key; $map[ $map_key ] = [$value, is_float($value) ? PDO::PARAM_STR : PDO::PARAM_INT]; } elseif ($raw = $this->buildRaw($value, $map)) { $condition .= $raw; } else { $condition .= $map_key; $map[ $map_key ] = [$value, PDO::PARAM_STR]; } $stack[] = $condition; } elseif ($operator === '!') { switch ($type) { case 'NULL': $stack[] = $column . ' IS NOT NULL'; break; case 'array': $placeholders = []; foreach ($value as $index => $item) { $stack_key = $map_key . $index . '_i'; $placeholders[] = $stack_key; $map[ $stack_key ] = $this->typeMap($item, gettype($item)); } $stack[] = $column . ' NOT IN (' . implode(', ', $placeholders) . ')'; break; case 'object': if ($raw = $this->buildRaw($value, $map)) { $stack[] = $column . ' != ' . $raw; } break; case 'integer': case 'double': case 'boolean': case 'string': $stack[] = $column . ' != ' . $map_key; $map[ $map_key ] = $this->typeMap($value, $type); break; } } elseif ($operator === '~' || $operator === '!~') { if ($type !== 'array') { $value = [ $value ]; } $connector = ' OR '; $data = array_values($value); if (is_array($data[ 0 ])) { if (isset($value[ 'AND' ]) || isset($value[ 'OR' ])) { $connector = ' ' . array_keys($value)[ 0 ] . ' '; $value = $data[ 0 ]; } } $like_clauses = []; foreach ($value as $index => $item) { $item = strval($item); if (!preg_match('/(\[.+\]|[\*\?\!\%#^-_]|%.+|.+%)/', $item)) { $item = '%' . $item . '%'; } $like_clauses[] = $column . ($operator === '!~' ? ' NOT' : '') . ' LIKE ' . $map_key . 'L' . $index; $map[ $map_key . 'L' . $index ] = [$item, PDO::PARAM_STR]; } $stack[] = '(' . implode($connector, $like_clauses) . ')'; } elseif ($operator === '<>' || $operator === '><') { if ($type === 'array') { if ($operator === '><') { $column .= ' NOT'; } $stack[] = '(' . $column . ' BETWEEN ' . $map_key . 'a AND ' . $map_key . 'b)'; $data_type = (is_numeric($value[ 0 ]) && is_numeric($value[ 1 ])) ? PDO::PARAM_INT : PDO::PARAM_STR; $map[ $map_key . 'a' ] = [$value[ 0 ], $data_type]; $map[ $map_key . 'b' ] = [$value[ 1 ], $data_type]; } } elseif ($operator === 'REGEXP') { $stack[] = $column . ' REGEXP ' . $map_key; $map[ $map_key ] = [$value, PDO::PARAM_STR]; } } else { switch ($type) { case 'NULL': $stack[] = $column . ' IS NULL'; break; case 'array': $placeholders = []; foreach ($value as $index => $item) { $stack_key = $map_key . $index . '_i'; $placeholders[] = $stack_key; $map[ $stack_key ] = $this->typeMap($item, gettype($item)); } $stack[] = $column . ' IN (' . implode(', ', $placeholders) . ')'; break; case 'object': if ($raw = $this->buildRaw($value, $map)) { $stack[] = $column . ' = ' . $raw; } break; case 'integer': case 'double': case 'boolean': case 'string': $stack[] = $column . ' = ' . $map_key; $map[ $map_key ] = $this->typeMap($value, $type); break; } } } } return implode($conjunctor . ' ', $stack); } protected function whereClause($where, &$map) { $where_clause = ''; if (is_array($where)) { $where_keys = array_keys($where); $conditions = array_diff_key($where, array_flip( ['GROUP', 'ORDER', 'HAVING', 'LIMIT', 'LIKE', 'MATCH'] )); if (!empty($conditions)) { $where_clause = ' WHERE ' . $this->dataImplode($conditions, $map, ' AND'); } if (isset($where[ 'MATCH' ]) && $this->type === 'mysql') { $MATCH = $where[ 'MATCH' ]; if (is_array($MATCH) && isset($MATCH[ 'columns' ], $MATCH[ 'keyword' ])) { $mode = ''; $mode_array = [ 'natural' => 'IN NATURAL LANGUAGE MODE', 'natural+query' => 'IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION', 'boolean' => 'IN BOOLEAN MODE', 'query' => 'WITH QUERY EXPANSION' ]; if (isset($MATCH[ 'mode' ], $mode_array[ $MATCH[ 'mode' ] ])) { $mode = ' ' . $mode_array[ $MATCH[ 'mode' ] ]; } $columns = implode(', ', array_map([$this, 'columnQuote'], $MATCH[ 'columns' ])); $map_key = $this->mapKey(); $map[ $map_key ] = [$MATCH[ 'keyword' ], PDO::PARAM_STR]; $where_clause .= ($where_clause !== '' ? ' AND ' : ' WHERE') . ' MATCH (' . $columns . ') AGAINST (' . $map_key . $mode . ')'; } } if (isset($where[ 'GROUP' ])) { $GROUP = $where[ 'GROUP' ]; if (is_array($GROUP)) { $stack = []; foreach ($GROUP as $column => $value) { $stack[] = $this->columnQuote($value); } $where_clause .= ' GROUP BY ' . implode(',', $stack); } elseif ($raw = $this->buildRaw($GROUP, $map)) { $where_clause .= ' GROUP BY ' . $raw; } else { $where_clause .= ' GROUP BY ' . $this->columnQuote($GROUP); } if (isset($where[ 'HAVING' ])) { if ($raw = $this->buildRaw($where[ 'HAVING' ], $map)) { $where_clause .= ' HAVING ' . $raw; } else { $where_clause .= ' HAVING ' . $this->dataImplode($where[ 'HAVING' ], $map, ' AND'); } } } if (isset($where[ 'ORDER' ])) { $ORDER = $where[ 'ORDER' ]; if (is_array($ORDER)) { $stack = []; foreach ($ORDER as $column => $value) { if (is_array($value)) { $stack[] = 'FIELD(' . $this->columnQuote($column) . ', ' . $this->arrayQuote($value) . ')'; } elseif ($value === 'ASC' || $value === 'DESC') { $stack[] = $this->columnQuote($column) . ' ' . $value; } elseif (is_int($column)) { $stack[] = $this->columnQuote($value); } } $where_clause .= ' ORDER BY ' . implode(',', $stack); } elseif ($raw = $this->buildRaw($ORDER, $map)) { $where_clause .= ' ORDER BY ' . $raw; } else { $where_clause .= ' ORDER BY ' . $this->columnQuote($ORDER); } if ( isset($where[ 'LIMIT' ]) && in_array($this->type, ['oracle', 'mssql']) ) { $LIMIT = $where[ 'LIMIT' ]; if (is_numeric($LIMIT)) { $LIMIT = [0, $LIMIT]; } if ( is_array($LIMIT) && is_numeric($LIMIT[ 0 ]) && is_numeric($LIMIT[ 1 ]) ) { $where_clause .= ' OFFSET ' . $LIMIT[ 0 ] . ' ROWS FETCH NEXT ' . $LIMIT[ 1 ] . ' ROWS ONLY'; } } } if (isset($where[ 'LIMIT' ]) && !in_array($this->type, ['oracle', 'mssql'])) { $LIMIT = $where[ 'LIMIT' ]; if (is_numeric($LIMIT)) { $where_clause .= ' LIMIT ' . $LIMIT; } elseif ( is_array($LIMIT) && is_numeric($LIMIT[ 0 ]) && is_numeric($LIMIT[ 1 ]) ) { $where_clause .= ' LIMIT ' . $LIMIT[ 1 ] . ' OFFSET ' . $LIMIT[ 0 ]; } } } elseif ($raw = $this->buildRaw($where, $map)) { $where_clause .= ' ' . $raw; } return $where_clause; } protected function selectContext($table, &$map, $join, &$columns = null, $where = null, $column_fn = null) { preg_match('/(?<table>[a-zA-Z0-9_]+)\s*\((?<alias>[a-zA-Z0-9_]+)\)/i', $table, $table_match); if (isset($table_match[ 'table' ], $table_match[ 'alias' ])) { $table = $this->tableQuote($table_match[ 'table' ]); $table_query = $table . ' AS ' . $this->tableQuote($table_match[ 'alias' ]); } else { $table = $this->tableQuote($table); $table_query = $table; } $is_join = false; $join_key = is_array($join) ? array_keys($join) : null; if ( isset($join_key[ 0 ]) && strpos($join_key[ 0 ], '[') === 0 ) { $is_join = true; $table_query .= ' ' . $this->buildJoin($table, $join); } else { if (is_null($columns)) { if ( !is_null($where) || (is_array($join) && isset($column_fn)) ) { $where = $join; $columns = null; } else { $where = null; $columns = $join; } } else { $where = $columns; $columns = $join; } } if (isset($column_fn)) { if ($column_fn === 1) { $column = '1'; if (is_null($where)) { $where = $columns; } } elseif ($raw = $this->buildRaw($column_fn, $map)) { $column = $raw; } else { if (empty($columns) || $this->isRaw($columns)) { $columns = '*'; $where = $join; } $column = $column_fn . '(' . $this->columnPush($columns, $map, true) . ')'; } } else { $column = $this->columnPush($columns, $map, true, $is_join); } return 'SELECT ' . $column . ' FROM ' . $table_query . $this->whereClause($where, $map); } protected function buildJoin($table, $join) { $table_join = []; $join_array = [ '>' => 'LEFT', '<' => 'RIGHT', '<>' => 'FULL', '><' => 'INNER' ]; foreach($join as $sub_table => $relation) { preg_match('/(\[(?<join>\<\>?|\>\<?)\])?(?<table>[a-zA-Z0-9_]+)\s?(\((?<alias>[a-zA-Z0-9_]+)\))?/', $sub_table, $match); if ($match[ 'join' ] !== '' && $match[ 'table' ] !== '') { if (is_string($relation)) { $relation = 'USING ("' . $relation . '")'; } if (is_array($relation)) { // For ['column1', 'column2'] if (isset($relation[ 0 ])) { $relation = 'USING ("' . implode('", "', $relation) . '")'; } else { $joins = []; foreach ($relation as $key => $value) { $joins[] = ( strpos($key, '.') > 0 ? // For ['tableB.column' => 'column'] $this->columnQuote($key) : // For ['column1' => 'column2'] $table . '."' . $key . '"' ) . ' = ' . $this->tableQuote(isset($match[ 'alias' ]) ? $match[ 'alias' ] : $match[ 'table' ]) . '."' . $value . '"'; } $relation = 'ON ' . implode(' AND ', $joins); } } $table_name = $this->tableQuote($match[ 'table' ]) . ' '; if (isset($match[ 'alias' ])) { $table_name .= 'AS ' . $this->tableQuote($match[ 'alias' ]) . ' '; } $table_join[] = $join_array[ $match[ 'join' ] ] . ' JOIN ' . $table_name . $relation; } } return implode(' ', $table_join); } protected function columnMap($columns, &$stack, $root) { if ($columns === '*') { return $stack; } foreach ($columns as $key => $value) { if (is_int($key)) { preg_match('/([a-zA-Z0-9_]+\.)?(?<column>[a-zA-Z0-9_]+)(?:\s*\((?<alias>[a-zA-Z0-9_]+)\))?(?:\s*\[(?<type>(?:String|Bool|Int|Number|Object|JSON))\])?/i', $value, $key_match); $column_key = !empty($key_match[ 'alias' ]) ? $key_match[ 'alias' ] : $key_match[ 'column' ]; if (isset($key_match[ 'type' ])) { $stack[ $value ] = [$column_key, $key_match[ 'type' ]]; } else { $stack[ $value ] = [$column_key, 'String']; } } elseif ($this->isRaw($value)) { preg_match('/([a-zA-Z0-9_]+\.)?(?<column>[a-zA-Z0-9_]+)(\s*\[(?<type>(String|Bool|Int|Number))\])?/i', $key, $key_match); $column_key = $key_match[ 'column' ]; if (isset($key_match[ 'type' ])) { $stack[ $key ] = [$column_key, $key_match[ 'type' ]]; } else { $stack[ $key ] = [$column_key, 'String']; } } elseif (!is_int($key) && is_array($value)) { if ($root && count(array_keys($columns)) === 1) { $stack[ $key ] = [$key, 'String']; } $this->columnMap($value, $stack, false); } } return $stack; } protected function dataMap($data, $columns, $column_map, &$stack, $root, &$result) { if ($root) { $columns_key = array_keys($columns); if (count($columns_key) === 1 && is_array($columns[$columns_key[0]])) { $index_key = array_keys($columns)[0]; $data_key = preg_replace("/^[a-zA-Z0-9_]+\./i", "", $index_key); $current_stack = []; foreach ($data as $item) { $this->dataMap($data, $columns[ $index_key ], $column_map, $current_stack, false, $result); $index = $data[ $data_key ]; $result[ $index ] = $current_stack; } } else { $current_stack = []; $this->dataMap($data, $columns, $column_map, $current_stack, false, $result); $result[] = $current_stack; } return; } foreach ($columns as $key => $value) { $isRaw = $this->isRaw($value); if (is_int($key) || $isRaw) { $map = $column_map[ $isRaw ? $key : $value ]; $column_key = $map[ 0 ]; $item = $data[ $column_key ]; if (isset($map[ 1 ])) { if ($isRaw && in_array($map[ 1 ], ['Object', 'JSON'])) { continue; } if (is_null($item)) { $stack[ $column_key ] = null; continue; } switch ($map[ 1 ]) { case 'Number': $stack[ $column_key ] = (double) $item; break; case 'Int': $stack[ $column_key ] = (int) $item; break; case 'Bool': $stack[ $column_key ] = (bool) $item; break; case 'Object': $stack[ $column_key ] = unserialize($item); break; case 'JSON': $stack[ $column_key ] = json_decode($item, true); break; case 'String': $stack[ $column_key ] = $item; break; } } else { $stack[ $column_key ] = $item; } } else { $current_stack = []; $this->dataMap($data, $value, $column_map, $current_stack, false, $result); $stack[ $key ] = $current_stack; } } } public function create($table, $columns, $options = null) { $stack = []; $tableName = $this->prefix . $table; foreach ($columns as $name => $definition) { if (is_int($name)) { $stack[] = preg_replace('/\<([a-zA-Z0-9_]+)\>/i', '"$1"', $definition); } elseif (is_array($definition)) { $stack[] = $name . ' ' . implode(' ', $definition); } elseif (is_string($definition)) { $stack[] = $name . ' ' . $this->query($definition); } } $table_option = ''; if (is_array($options)) { $option_stack = []; foreach ($options as $key => $value) { if (is_string($value) || is_int($value)) { $option_stack[] = "$key = $value"; } } $table_option = ' ' . implode(', ', $option_stack); } elseif (is_string($options)) { $table_option = ' ' . $options; } return $this->exec("CREATE TABLE IF NOT EXISTS $tableName (" . implode(', ', $stack) . ")$table_option"); } public function drop($table) { $tableName = $this->prefix . $table; return $this->exec("DROP TABLE IF EXISTS $tableName"); } public function select($table, $join, $columns = null, $where = null) { $map = []; $result = []; $column_map = []; $index = 0; $column = $where === null ? $join : $columns; $is_single = (is_string($column) && $column !== '*'); $query = $this->exec($this->selectContext($table, $map, $join, $columns, $where), $map); $this->columnMap($columns, $column_map, true); if (!$this->statement) { return false; } if ($columns === '*') { return $query->fetchAll(PDO::FETCH_ASSOC); } while ($data = $query->fetch(PDO::FETCH_ASSOC)) { $current_stack = []; $this->dataMap($data, $columns, $column_map, $current_stack, true, $result); } if ($is_single) { $single_result = []; $result_key = $column_map[ $column ][ 0 ]; foreach ($result as $item) { $single_result[] = $item[ $result_key ]; } return $single_result; } return $result; } public function insert($table, $datas) { $stack = []; $columns = []; $fields = []; $map = []; if (!isset($datas[ 0 ])) { $datas = [$datas]; } foreach ($datas as $data) { foreach ($data as $key => $value) { $columns[] = $key; } } $columns = array_unique($columns); foreach ($datas as $data) { $values = []; foreach ($columns as $key) { if ($raw = $this->buildRaw($data[ $key ], $map)) { $values[] = $raw; continue; } $map_key = $this->mapKey(); $values[] = $map_key; if (!isset($data[ $key ])) { $map[ $map_key ] = [null, PDO::PARAM_NULL]; } else { $value = $data[ $key ]; $type = gettype($value); switch ($type) { case 'array': $map[ $map_key ] = [ strpos($key, '[JSON]') === strlen($key) - 6 ? json_encode($value) : serialize($value), PDO::PARAM_STR ]; break; case 'object': $value = serialize($value); case 'NULL': case 'resource': case 'boolean': case 'integer': case 'double': case 'string': $map[ $map_key ] = $this->typeMap($value, $type); break; } } } $stack[] = '(' . implode(', ', $values) . ')'; } foreach ($columns as $key) { $fields[] = $this->columnQuote(preg_replace("/(\s*\[JSON\]$)/i", '', $key)); } return $this->exec('INSERT INTO ' . $this->tableQuote($table) . ' (' . implode(', ', $fields) . ') VALUES ' . implode(', ', $stack), $map); } public function update($table, $data, $where = null) { $fields = []; $map = []; foreach ($data as $key => $value) { $column = $this->columnQuote(preg_replace("/(\s*\[(JSON|\+|\-|\*|\/)\]$)/i", '', $key)); if ($raw = $this->buildRaw($value, $map)) { $fields[] = $column . ' = ' . $raw; continue; } $map_key = $this->mapKey(); preg_match('/(?<column>[a-zA-Z0-9_]+)(\[(?<operator>\+|\-|\*|\/)\])?/i', $key, $match); if (isset($match[ 'operator' ])) { if (is_numeric($value)) { $fields[] = $column . ' = ' . $column . ' ' . $match[ 'operator' ] . ' ' . $value; } } else { $fields[] = $column . ' = ' . $map_key; $type = gettype($value); switch ($type) { case 'array': $map[ $map_key ] = [ strpos($key, '[JSON]') === strlen($key) - 6 ? json_encode($value) : serialize($value), PDO::PARAM_STR ]; break; case 'object': $value = serialize($value); case 'NULL': case 'resource': case 'boolean': case 'integer': case 'double': case 'string': $map[ $map_key ] = $this->typeMap($value, $type); break; } } } return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . implode(', ', $fields) . $this->whereClause($where, $map), $map); } public function delete($table, $where) { $map = []; return $this->exec('DELETE FROM ' . $this->tableQuote($table) . $this->whereClause($where, $map), $map); } public function replace($table, $columns, $where = null) { if (!is_array($columns) || empty($columns)) { return false; } $map = []; $stack = []; foreach ($columns as $column => $replacements) { if (is_array($replacements)) { foreach ($replacements as $old => $new) { $map_key = $this->mapKey(); $stack[] = $this->columnQuote($column) . ' = REPLACE(' . $this->columnQuote($column) . ', ' . $map_key . 'a, ' . $map_key . 'b)'; $map[ $map_key . 'a' ] = [$old, PDO::PARAM_STR]; $map[ $map_key . 'b' ] = [$new, PDO::PARAM_STR]; } } } if (!empty($stack)) { return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . implode(', ', $stack) . $this->whereClause($where, $map), $map); } return false; } public function get($table, $join = null, $columns = null, $where = null) { $map = []; $result = []; $column_map = []; $current_stack = []; if ($where === null) { $column = $join; unset($columns[ 'LIMIT' ]); } else { $column = $columns; unset($where[ 'LIMIT' ]); } $is_single = (is_string($column) && $column !== '*'); $query = $this->exec($this->selectContext($table, $map, $join, $columns, $where) . ' LIMIT 1', $map); if (!$this->statement) { return false; } $data = $query->fetchAll(PDO::FETCH_ASSOC); if (isset($data[ 0 ])) { if ($column === '*') { return $data[ 0 ]; } $this->columnMap($columns, $column_map, true); $this->dataMap($data[ 0 ], $columns, $column_map, $current_stack, true, $result); if ($is_single) { return $result[ 0 ][ $column_map[ $column ][ 0 ] ]; } return $result[ 0 ]; } } public function has($table, $join, $where = null) { $map = []; $column = null; if ($this->type === 'mssql') { $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, Medoo::raw('TOP 1 1')), $map); } else { $query = $this->exec('SELECT EXISTS(' . $this->selectContext($table, $map, $join, $column, $where, 1) . ')', $map); } if (!$this->statement) { return false; } $result = $query->fetchColumn(); return $result === '1' || $result === 1 || $result === true; } public function rand($table, $join = null, $columns = null, $where = null) { $type = $this->type; $order = 'RANDOM()'; if ($type === 'mysql') { $order = 'RAND()'; } elseif ($type === 'mssql') { $order = 'NEWID()'; } $order_raw = $this->raw($order); if ($where === null) { if ($columns === null) { $columns = [ 'ORDER' => $order_raw ]; } else { $column = $join; unset($columns[ 'ORDER' ]); $columns[ 'ORDER' ] = $order_raw; } } else { unset($where[ 'ORDER' ]); $where[ 'ORDER' ] = $order_raw; } return $this->select($table, $join, $columns, $where); } private function aggregate($type, $table, $join = null, $column = null, $where = null) { $map = []; $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, strtoupper($type)), $map); if (!$this->statement) { return false; } $number = $query->fetchColumn(); return is_numeric($number) ? $number + 0 : $number; } public function count($table, $join = null, $column = null, $where = null) { return $this->aggregate('count', $table, $join, $column, $where); } public function avg($table, $join, $column = null, $where = null) { return $this->aggregate('avg', $table, $join, $column, $where); } public function max($table, $join, $column = null, $where = null) { return $this->aggregate('max', $table, $join, $column, $where); } public function min($table, $join, $column = null, $where = null) { return $this->aggregate('min', $table, $join, $column, $where); } public function sum($table, $join, $column = null, $where = null) { return $this->aggregate('sum', $table, $join, $column, $where); } public function action($actions) { if (is_callable($actions)) { $this->pdo->beginTransaction(); try { $result = $actions($this); if ($result === false) { $this->pdo->rollBack(); } else { $this->pdo->commit(); } } catch (Exception $e) { $this->pdo->rollBack(); throw $e; } return $result; } return false; } public function id() { if ($this->statement == null) { return null; } $type = $this->type; if ($type === 'oracle') { return 0; } elseif ($type === 'pgsql') { return $this->pdo->query('SELECT LASTVAL()')->fetchColumn(); } $lastId = $this->pdo->lastInsertId(); if ($lastId != "0" && $lastId != "") { return $lastId; } return null; } public function debug() { $this->debug_mode = true; return $this; } public function error() { return $this->errorInfo; } public function last() { $log = end($this->logs); return $this->generate($log[ 0 ], $log[ 1 ]); } public function log() { return array_map(function ($log) { return $this->generate($log[ 0 ], $log[ 1 ]); }, $this->logs ); } public function info() { $output = [ 'server' => 'SERVER_INFO', 'driver' => 'DRIVER_NAME', 'client' => 'CLIENT_VERSION', 'version' => 'SERVER_VERSION', 'connection' => 'CONNECTION_STATUS' ]; foreach ($output as $key => $value) { $output[ $key ] = @$this->pdo->getAttribute(constant('PDO::ATTR_' . $value)); } $output[ 'dsn' ] = $this->dsn; return $output; } 现在问题是生成cdk执行兑换player_level+自定义等级成功 开启所有锚点生成后 point 3 all 不能兑换成功 开启无限体力 生成后stamina infinite on 不能兑换成功 可重写数据库 cdk逻辑和used_cdk表记录逻辑
最新发布
11-30
<?php /*! * Medoo database framework * https://medoo.in * Version 1.7.10 * * Copyright 2020, Angel Lai * Released under the MIT license */ namespace Medoo; use PDO; use Exception; use PDOException; use InvalidArgumentException; class Raw { public $map; public $value; } class Medoo { public $pdo; protected $type; protected $prefix; protected $statement; protected $dsn; protected $logs = []; protected $logging = false; protected $debug_mode = false; protected $guid = 0; protected $errorInfo = null; public function __construct(array $options) { if (isset($options[ 'database_type' ])) { $this->type = strtolower($options[ 'database_type' ]); if ($this->type === 'mariadb') { $this->type = 'mysql'; } } if (isset($options[ 'prefix' ])) { $this->prefix = $options[ 'prefix' ]; } if (isset($options[ 'logging' ]) && is_bool($options[ 'logging' ])) { $this->logging = $options[ 'logging' ]; } $option = isset($options[ 'option' ]) ? $options[ 'option' ] : []; $commands = (isset($options[ 'command' ]) && is_array($options[ 'command' ])) ? $options[ 'command' ] : []; switch ($this->type) { case 'mysql': // Make MySQL using standard quoted identifier $commands[] = 'SET SQL_MODE=ANSI_QUOTES'; break; case 'mssql': // Keep MSSQL QUOTED_IDENTIFIER is ON for standard quoting $commands[] = 'SET QUOTED_IDENTIFIER ON'; // Make ANSI_NULLS is ON for NULL value $commands[] = 'SET ANSI_NULLS ON'; break; } if (isset($options[ 'pdo' ])) { if (!$options[ 'pdo' ] instanceof PDO) { throw new InvalidArgumentException('Invalid PDO object supplied'); } $this->pdo = $options[ 'pdo' ]; foreach ($commands as $value) { $this->pdo->exec($value); } return; } if (isset($options[ 'dsn' ])) { if (is_array($options[ 'dsn' ]) && isset($options[ 'dsn' ][ 'driver' ])) { $attr = $options[ 'dsn' ]; } else { throw new InvalidArgumentException('Invalid DSN option supplied'); } } else { if ( isset($options[ 'port' ]) && is_int($options[ 'port' ] * 1) ) { $port = $options[ 'port' ]; } $is_port = isset($port); switch ($this->type) { case 'mysql': $attr = [ 'driver' => 'mysql', 'dbname' => $options[ 'database_name' ] ]; if (isset($options[ 'socket' ])) { $attr[ 'unix_socket' ] = $options[ 'socket' ]; } else { $attr[ 'host' ] = $options[ 'server' ]; if ($is_port) { $attr[ 'port' ] = $port; } } break; case 'pgsql': $attr = [ 'driver' => 'pgsql', 'host' => $options[ 'server' ], 'dbname' => $options[ 'database_name' ] ]; if ($is_port) { $attr[ 'port' ] = $port; } break; case 'sybase': $attr = [ 'driver' => 'dblib', 'host' => $options[ 'server' ], 'dbname' => $options[ 'database_name' ] ]; if ($is_port) { $attr[ 'port' ] = $port; } break; case 'oracle': $attr = [ 'driver' => 'oci', 'dbname' => $options[ 'server' ] ? '//' . $options[ 'server' ] . ($is_port ? ':' . $port : ':1521') . '/' . $options[ 'database_name' ] : $options[ 'database_name' ] ]; if (isset($options[ 'charset' ])) { $attr[ 'charset' ] = $options[ 'charset' ]; } break; case 'mssql': if (isset($options[ 'driver' ]) && $options[ 'driver' ] === 'dblib') { $attr = [ 'driver' => 'dblib', 'host' => $options[ 'server' ] . ($is_port ? ':' . $port : ''), 'dbname' => $options[ 'database_name' ] ]; if (isset($options[ 'appname' ])) { $attr[ 'appname' ] = $options[ 'appname' ]; } if (isset($options[ 'charset' ])) { $attr[ 'charset' ] = $options[ 'charset' ]; } } else { $attr = [ 'driver' => 'sqlsrv', 'Server' => $options[ 'server' ] . ($is_port ? ',' . $port : ''), 'Database' => $options[ 'database_name' ] ]; if (isset($options[ 'appname' ])) { $attr[ 'APP' ] = $options[ 'appname' ]; } $config = [ 'ApplicationIntent', 'AttachDBFileName', 'Authentication', 'ColumnEncryption', 'ConnectionPooling', 'Encrypt', 'Failover_Partner', 'KeyStoreAuthentication', 'KeyStorePrincipalId', 'KeyStoreSecret', 'LoginTimeout', 'MultipleActiveResultSets', 'MultiSubnetFailover', 'Scrollable', 'TraceFile', 'TraceOn', 'TransactionIsolation', 'TransparentNetworkIPResolution', 'TrustServerCertificate', 'WSID', ]; foreach ($config as $value) { $keyname = strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $value)); if (isset($options[ $keyname ])) { $attr[ $value ] = $options[ $keyname ]; } } } break; case 'sqlite': $attr = [ 'driver' => 'sqlite', $options[ 'database_file' ] ]; break; } } if (!isset($attr)) { throw new InvalidArgumentException('Incorrect connection options'); } $driver = $attr[ 'driver' ]; if (!in_array($driver, PDO::getAvailableDrivers())) { throw new InvalidArgumentException("Unsupported PDO driver: {$driver}"); } unset($attr[ 'driver' ]); $stack = []; foreach ($attr as $key => $value) { $stack[] = is_int($key) ? $value : $key . '=' . $value; } $dsn = $driver . ':' . implode(';', $stack); if ( in_array($this->type, ['mysql', 'pgsql', 'sybase', 'mssql']) && isset($options[ 'charset' ]) ) { $commands[] = "SET NAMES '{$options[ 'charset' ]}'" . ( $this->type === 'mysql' && isset($options[ 'collation' ]) ? " COLLATE '{$options[ 'collation' ]}'" : '' ); } $this->dsn = $dsn; try { $this->pdo = new PDO( $dsn, isset($options[ 'username' ]) ? $options[ 'username' ] : null, isset($options[ 'password' ]) ? $options[ 'password' ] : null, $option ); foreach ($commands as $value) { $this->pdo->exec($value); } } catch (PDOException $e) { throw new PDOException($e->getMessage()); } } public function query($query, $map = []) { $raw = $this->raw($query, $map); $query = $this->buildRaw($raw, $map); return $this->exec($query, $map); } public function exec($query, $map = []) { $this->statement = null; if ($this->debug_mode) { echo $this->generate($query, $map); $this->debug_mode = false; return false; } if ($this->logging) { $this->logs[] = [$query, $map]; } else { $this->logs = [[$query, $map]]; } $statement = $this->pdo->prepare($query); if (!$statement) { $this->errorInfo = $this->pdo->errorInfo(); $this->statement = null; return false; } $this->statement = $statement; foreach ($map as $key => $value) { $statement->bindValue($key, $value[ 0 ], $value[ 1 ]); } $execute = $statement->execute(); $this->errorInfo = $statement->errorInfo(); if (!$execute) { $this->statement = null; } return $statement; } protected function generate($query, $map) { $identifier = [ 'mysql' => '`$1`', 'mssql' => '[$1]' ]; $query = preg_replace( '/"([a-zA-Z0-9_]+)"/i', isset($identifier[ $this->type ]) ? $identifier[ $this->type ] : '"$1"', $query ); foreach ($map as $key => $value) { if ($value[ 1 ] === PDO::PARAM_STR) { $replace = $this->quote($value[ 0 ]); } elseif ($value[ 1 ] === PDO::PARAM_NULL) { $replace = 'NULL'; } elseif ($value[ 1 ] === PDO::PARAM_LOB) { $replace = '{LOB_DATA}'; } else { $replace = $value[ 0 ]; } $query = str_replace($key, $replace, $query); } return $query; } public static function raw($string, $map = []) { $raw = new Raw(); $raw->map = $map; $raw->value = $string; return $raw; } protected function isRaw($object) { return $object instanceof Raw; } protected function buildRaw($raw, &$map) { if (!$this->isRaw($raw)) { return false; } $query = preg_replace_callback( '/(([`\']).*?)?((FROM|TABLE|INTO|UPDATE|JOIN)\s*)?\<(([a-zA-Z0-9_]+)(\.[a-zA-Z0-9_]+)?)\>(.*?\2)?/i', function ($matches) { if (!empty($matches[ 2 ]) && isset($matches[ 8 ])) { return $matches[ 0 ]; } if (!empty($matches[ 4 ])) { return $matches[ 1 ] . $matches[ 4 ] . ' ' . $this->tableQuote($matches[ 5 ]); } return $matches[ 1 ] . $this->columnQuote($matches[ 5 ]); }, $raw->value); $raw_map = $raw->map; if (!empty($raw_map)) { foreach ($raw_map as $key => $value) { $map[ $key ] = $this->typeMap($value, gettype($value)); } } return $query; } public function quote($string) { return $this->pdo->quote($string); } protected function tableQuote($table) { if (!preg_match('/^[a-zA-Z0-9_]+$/i', $table)) { throw new InvalidArgumentException("Incorrect table name \"$table\""); } return '"' . $this->prefix . $table . '"'; } protected function mapKey() { return ':MeDoO_' . $this->guid++ . '_mEdOo'; } protected function typeMap($value, $type) { $map = [ 'NULL' => PDO::PARAM_NULL, 'integer' => PDO::PARAM_INT, 'double' => PDO::PARAM_STR, 'boolean' => PDO::PARAM_BOOL, 'string' => PDO::PARAM_STR, 'object' => PDO::PARAM_STR, 'resource' => PDO::PARAM_LOB ]; if ($type === 'boolean') { $value = ($value ? '1' : '0'); } elseif ($type === 'NULL') { $value = null; } return [$value, $map[ $type ]]; } protected function columnQuote($string) { if (!preg_match('/^[a-zA-Z0-9_]+(\.?[a-zA-Z0-9_]+)?$/i', $string)) { throw new InvalidArgumentException("Incorrect column name \"$string\""); } if (strpos($string, '.') !== false) { return '"' . $this->prefix . str_replace('.', '"."', $string) . '"'; } return '"' . $string . '"'; } protected function columnPush(&$columns, &$map, $root, $is_join = false) { if ($columns === '*') { return $columns; } $stack = []; if (is_string($columns)) { $columns = [$columns]; } foreach ($columns as $key => $value) { if (!is_int($key) && is_array($value) && $root && count(array_keys($columns)) === 1) { $stack[] = $this->columnQuote($key); $stack[] = $this->columnPush($value, $map, false, $is_join); } elseif (is_array($value)) { $stack[] = $this->columnPush($value, $map, false, $is_join); } elseif (!is_int($key) && $raw = $this->buildRaw($value, $map)) { preg_match('/(?<column>[a-zA-Z0-9_\.]+)(\s*\[(?<type>(String|Bool|Int|Number))\])?/i', $key, $match); $stack[] = $raw . ' AS ' . $this->columnQuote($match[ 'column' ]); } elseif (is_int($key) && is_string($value)) { if ($is_join && strpos($value, '*') !== false) { throw new InvalidArgumentException('Cannot use table.* to select all columns while joining table'); } preg_match('/(?<column>[a-zA-Z0-9_\.]+)(?:\s*\((?<alias>[a-zA-Z0-9_]+)\))?(?:\s*\[(?<type>(?:String|Bool|Int|Number|Object|JSON))\])?/i', $value, $match); if (!empty($match[ 'alias' ])) { $stack[] = $this->columnQuote($match[ 'column' ]) . ' AS ' . $this->columnQuote($match[ 'alias' ]); $columns[ $key ] = $match[ 'alias' ]; if (!empty($match[ 'type' ])) { $columns[ $key ] .= ' [' . $match[ 'type' ] . ']'; } } else { $stack[] = $this->columnQuote($match[ 'column' ]); } } } return implode(',', $stack); } protected function arrayQuote($array) { $stack = []; foreach ($array as $value) { $stack[] = is_int($value) ? $value : $this->pdo->quote($value); } return implode(',', $stack); } protected function innerConjunct($data, $map, $conjunctor, $outer_conjunctor) { $stack = []; foreach ($data as $value) { $stack[] = '(' . $this->dataImplode($value, $map, $conjunctor) . ')'; } return implode($outer_conjunctor . ' ', $stack); } protected function dataImplode($data, &$map, $conjunctor) { $stack = []; foreach ($data as $key => $value) { $type = gettype($value); if ( $type === 'array' && preg_match("/^(AND|OR)(\s+#.*)?$/", $key, $relation_match) ) { $relationship = $relation_match[ 1 ]; $stack[] = $value !== array_keys(array_keys($value)) ? '(' . $this->dataImplode($value, $map, ' ' . $relationship) . ')' : '(' . $this->innerConjunct($value, $map, ' ' . $relationship, $conjunctor) . ')'; continue; } $map_key = $this->mapKey(); if ( is_int($key) && preg_match('/([a-zA-Z0-9_\.]+)\[(?<operator>\>\=?|\<\=?|\!?\=)\]([a-zA-Z0-9_\.]+)/i', $value, $match) ) { $stack[] = $this->columnQuote($match[ 1 ]) . ' ' . $match[ 'operator' ] . ' ' . $this->columnQuote($match[ 3 ]); } else { preg_match('/([a-zA-Z0-9_\.]+)(\[(?<operator>\>\=?|\<\=?|\!|\<\>|\>\<|\!?~|REGEXP)\])?/i', $key, $match); $column = $this->columnQuote($match[ 1 ]); if (isset($match[ 'operator' ])) { $operator = $match[ 'operator' ]; if (in_array($operator, ['>', '>=', '<', '<='])) { $condition = $column . ' ' . $operator . ' '; if (is_numeric($value)) { $condition .= $map_key; $map[ $map_key ] = [$value, is_float($value) ? PDO::PARAM_STR : PDO::PARAM_INT]; } elseif ($raw = $this->buildRaw($value, $map)) { $condition .= $raw; } else { $condition .= $map_key; $map[ $map_key ] = [$value, PDO::PARAM_STR]; } $stack[] = $condition; } elseif ($operator === '!') { switch ($type) { case 'NULL': $stack[] = $column . ' IS NOT NULL'; break; case 'array': $placeholders = []; foreach ($value as $index => $item) { $stack_key = $map_key . $index . '_i'; $placeholders[] = $stack_key; $map[ $stack_key ] = $this->typeMap($item, gettype($item)); } $stack[] = $column . ' NOT IN (' . implode(', ', $placeholders) . ')'; break; case 'object': if ($raw = $this->buildRaw($value, $map)) { $stack[] = $column . ' != ' . $raw; } break; case 'integer': case 'double': case 'boolean': case 'string': $stack[] = $column . ' != ' . $map_key; $map[ $map_key ] = $this->typeMap($value, $type); break; } } elseif ($operator === '~' || $operator === '!~') { if ($type !== 'array') { $value = [ $value ]; } $connector = ' OR '; $data = array_values($value); if (is_array($data[ 0 ])) { if (isset($value[ 'AND' ]) || isset($value[ 'OR' ])) { $connector = ' ' . array_keys($value)[ 0 ] . ' '; $value = $data[ 0 ]; } } $like_clauses = []; foreach ($value as $index => $item) { $item = strval($item); if (!preg_match('/(\[.+\]|[\*\?\!\%#^-_]|%.+|.+%)/', $item)) { $item = '%' . $item . '%'; } $like_clauses[] = $column . ($operator === '!~' ? ' NOT' : '') . ' LIKE ' . $map_key . 'L' . $index; $map[ $map_key . 'L' . $index ] = [$item, PDO::PARAM_STR]; } $stack[] = '(' . implode($connector, $like_clauses) . ')'; } elseif ($operator === '<>' || $operator === '><') { if ($type === 'array') { if ($operator === '><') { $column .= ' NOT'; } $stack[] = '(' . $column . ' BETWEEN ' . $map_key . 'a AND ' . $map_key . 'b)'; $data_type = (is_numeric($value[ 0 ]) && is_numeric($value[ 1 ])) ? PDO::PARAM_INT : PDO::PARAM_STR; $map[ $map_key . 'a' ] = [$value[ 0 ], $data_type]; $map[ $map_key . 'b' ] = [$value[ 1 ], $data_type]; } } elseif ($operator === 'REGEXP') { $stack[] = $column . ' REGEXP ' . $map_key; $map[ $map_key ] = [$value, PDO::PARAM_STR]; } } else { switch ($type) { case 'NULL': $stack[] = $column . ' IS NULL'; break; case 'array': $placeholders = []; foreach ($value as $index => $item) { $stack_key = $map_key . $index . '_i'; $placeholders[] = $stack_key; $map[ $stack_key ] = $this->typeMap($item, gettype($item)); } $stack[] = $column . ' IN (' . implode(', ', $placeholders) . ')'; break; case 'object': if ($raw = $this->buildRaw($value, $map)) { $stack[] = $column . ' = ' . $raw; } break; case 'integer': case 'double': case 'boolean': case 'string': $stack[] = $column . ' = ' . $map_key; $map[ $map_key ] = $this->typeMap($value, $type); break; } } } } return implode($conjunctor . ' ', $stack); } protected function whereClause($where, &$map) { $where_clause = ''; if (is_array($where)) { $where_keys = array_keys($where); $conditions = array_diff_key($where, array_flip( ['GROUP', 'ORDER', 'HAVING', 'LIMIT', 'LIKE', 'MATCH'] )); if (!empty($conditions)) { $where_clause = ' WHERE ' . $this->dataImplode($conditions, $map, ' AND'); } if (isset($where[ 'MATCH' ]) && $this->type === 'mysql') { $MATCH = $where[ 'MATCH' ]; if (is_array($MATCH) && isset($MATCH[ 'columns' ], $MATCH[ 'keyword' ])) { $mode = ''; $mode_array = [ 'natural' => 'IN NATURAL LANGUAGE MODE', 'natural+query' => 'IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION', 'boolean' => 'IN BOOLEAN MODE', 'query' => 'WITH QUERY EXPANSION' ]; if (isset($MATCH[ 'mode' ], $mode_array[ $MATCH[ 'mode' ] ])) { $mode = ' ' . $mode_array[ $MATCH[ 'mode' ] ]; } $columns = implode(', ', array_map([$this, 'columnQuote'], $MATCH[ 'columns' ])); $map_key = $this->mapKey(); $map[ $map_key ] = [$MATCH[ 'keyword' ], PDO::PARAM_STR]; $where_clause .= ($where_clause !== '' ? ' AND ' : ' WHERE') . ' MATCH (' . $columns . ') AGAINST (' . $map_key . $mode . ')'; } } if (isset($where[ 'GROUP' ])) { $GROUP = $where[ 'GROUP' ]; if (is_array($GROUP)) { $stack = []; foreach ($GROUP as $column => $value) { $stack[] = $this->columnQuote($value); } $where_clause .= ' GROUP BY ' . implode(',', $stack); } elseif ($raw = $this->buildRaw($GROUP, $map)) { $where_clause .= ' GROUP BY ' . $raw; } else { $where_clause .= ' GROUP BY ' . $this->columnQuote($GROUP); } if (isset($where[ 'HAVING' ])) { if ($raw = $this->buildRaw($where[ 'HAVING' ], $map)) { $where_clause .= ' HAVING ' . $raw; } else { $where_clause .= ' HAVING ' . $this->dataImplode($where[ 'HAVING' ], $map, ' AND'); } } } if (isset($where[ 'ORDER' ])) { $ORDER = $where[ 'ORDER' ]; if (is_array($ORDER)) { $stack = []; foreach ($ORDER as $column => $value) { if (is_array($value)) { $stack[] = 'FIELD(' . $this->columnQuote($column) . ', ' . $this->arrayQuote($value) . ')'; } elseif ($value === 'ASC' || $value === 'DESC') { $stack[] = $this->columnQuote($column) . ' ' . $value; } elseif (is_int($column)) { $stack[] = $this->columnQuote($value); } } $where_clause .= ' ORDER BY ' . implode(',', $stack); } elseif ($raw = $this->buildRaw($ORDER, $map)) { $where_clause .= ' ORDER BY ' . $raw; } else { $where_clause .= ' ORDER BY ' . $this->columnQuote($ORDER); } if ( isset($where[ 'LIMIT' ]) && in_array($this->type, ['oracle', 'mssql']) ) { $LIMIT = $where[ 'LIMIT' ]; if (is_numeric($LIMIT)) { $LIMIT = [0, $LIMIT]; } if ( is_array($LIMIT) && is_numeric($LIMIT[ 0 ]) && is_numeric($LIMIT[ 1 ]) ) { $where_clause .= ' OFFSET ' . $LIMIT[ 0 ] . ' ROWS FETCH NEXT ' . $LIMIT[ 1 ] . ' ROWS ONLY'; } } } if (isset($where[ 'LIMIT' ]) && !in_array($this->type, ['oracle', 'mssql'])) { $LIMIT = $where[ 'LIMIT' ]; if (is_numeric($LIMIT)) { $where_clause .= ' LIMIT ' . $LIMIT; } elseif ( is_array($LIMIT) && is_numeric($LIMIT[ 0 ]) && is_numeric($LIMIT[ 1 ]) ) { $where_clause .= ' LIMIT ' . $LIMIT[ 1 ] . ' OFFSET ' . $LIMIT[ 0 ]; } } } elseif ($raw = $this->buildRaw($where, $map)) { $where_clause .= ' ' . $raw; } return $where_clause; } protected function selectContext($table, &$map, $join, &$columns = null, $where = null, $column_fn = null) { preg_match('/(?<table>[a-zA-Z0-9_]+)\s*\((?<alias>[a-zA-Z0-9_]+)\)/i', $table, $table_match); if (isset($table_match[ 'table' ], $table_match[ 'alias' ])) { $table = $this->tableQuote($table_match[ 'table' ]); $table_query = $table . ' AS ' . $this->tableQuote($table_match[ 'alias' ]); } else { $table = $this->tableQuote($table); $table_query = $table; } $is_join = false; $join_key = is_array($join) ? array_keys($join) : null; if ( isset($join_key[ 0 ]) && strpos($join_key[ 0 ], '[') === 0 ) { $is_join = true; $table_query .= ' ' . $this->buildJoin($table, $join); } else { if (is_null($columns)) { if ( !is_null($where) || (is_array($join) && isset($column_fn)) ) { $where = $join; $columns = null; } else { $where = null; $columns = $join; } } else { $where = $columns; $columns = $join; } } if (isset($column_fn)) { if ($column_fn === 1) { $column = '1'; if (is_null($where)) { $where = $columns; } } elseif ($raw = $this->buildRaw($column_fn, $map)) { $column = $raw; } else { if (empty($columns) || $this->isRaw($columns)) { $columns = '*'; $where = $join; } $column = $column_fn . '(' . $this->columnPush($columns, $map, true) . ')'; } } else { $column = $this->columnPush($columns, $map, true, $is_join); } return 'SELECT ' . $column . ' FROM ' . $table_query . $this->whereClause($where, $map); } protected function buildJoin($table, $join) { $table_join = []; $join_array = [ '>' => 'LEFT', '<' => 'RIGHT', '<>' => 'FULL', '><' => 'INNER' ]; foreach($join as $sub_table => $relation) { preg_match('/(\[(?<join>\<\>?|\>\<?)\])?(?<table>[a-zA-Z0-9_]+)\s?(\((?<alias>[a-zA-Z0-9_]+)\))?/', $sub_table, $match); if ($match[ 'join' ] !== '' && $match[ 'table' ] !== '') { if (is_string($relation)) { $relation = 'USING ("' . $relation . '")'; } if (is_array($relation)) { // For ['column1', 'column2'] if (isset($relation[ 0 ])) { $relation = 'USING ("' . implode('", "', $relation) . '")'; } else { $joins = []; foreach ($relation as $key => $value) { $joins[] = ( strpos($key, '.') > 0 ? // For ['tableB.column' => 'column'] $this->columnQuote($key) : // For ['column1' => 'column2'] $table . '."' . $key . '"' ) . ' = ' . $this->tableQuote(isset($match[ 'alias' ]) ? $match[ 'alias' ] : $match[ 'table' ]) . '."' . $value . '"'; } $relation = 'ON ' . implode(' AND ', $joins); } } $table_name = $this->tableQuote($match[ 'table' ]) . ' '; if (isset($match[ 'alias' ])) { $table_name .= 'AS ' . $this->tableQuote($match[ 'alias' ]) . ' '; } $table_join[] = $join_array[ $match[ 'join' ] ] . ' JOIN ' . $table_name . $relation; } } return implode(' ', $table_join); } protected function columnMap($columns, &$stack, $root) { if ($columns === '*') { return $stack; } foreach ($columns as $key => $value) { if (is_int($key)) { preg_match('/([a-zA-Z0-9_]+\.)?(?<column>[a-zA-Z0-9_]+)(?:\s*\((?<alias>[a-zA-Z0-9_]+)\))?(?:\s*\[(?<type>(?:String|Bool|Int|Number|Object|JSON))\])?/i', $value, $key_match); $column_key = !empty($key_match[ 'alias' ]) ? $key_match[ 'alias' ] : $key_match[ 'column' ]; if (isset($key_match[ 'type' ])) { $stack[ $value ] = [$column_key, $key_match[ 'type' ]]; } else { $stack[ $value ] = [$column_key, 'String']; } } elseif ($this->isRaw($value)) { preg_match('/([a-zA-Z0-9_]+\.)?(?<column>[a-zA-Z0-9_]+)(\s*\[(?<type>(String|Bool|Int|Number))\])?/i', $key, $key_match); $column_key = $key_match[ 'column' ]; if (isset($key_match[ 'type' ])) { $stack[ $key ] = [$column_key, $key_match[ 'type' ]]; } else { $stack[ $key ] = [$column_key, 'String']; } } elseif (!is_int($key) && is_array($value)) { if ($root && count(array_keys($columns)) === 1) { $stack[ $key ] = [$key, 'String']; } $this->columnMap($value, $stack, false); } } return $stack; } protected function dataMap($data, $columns, $column_map, &$stack, $root, &$result) { if ($root) { $columns_key = array_keys($columns); if (count($columns_key) === 1 && is_array($columns[$columns_key[0]])) { $index_key = array_keys($columns)[0]; $data_key = preg_replace("/^[a-zA-Z0-9_]+\./i", "", $index_key); $current_stack = []; foreach ($data as $item) { $this->dataMap($data, $columns[ $index_key ], $column_map, $current_stack, false, $result); $index = $data[ $data_key ]; $result[ $index ] = $current_stack; } } else { $current_stack = []; $this->dataMap($data, $columns, $column_map, $current_stack, false, $result); $result[] = $current_stack; } return; } foreach ($columns as $key => $value) { $isRaw = $this->isRaw($value); if (is_int($key) || $isRaw) { $map = $column_map[ $isRaw ? $key : $value ]; $column_key = $map[ 0 ]; $item = $data[ $column_key ]; if (isset($map[ 1 ])) { if ($isRaw && in_array($map[ 1 ], ['Object', 'JSON'])) { continue; } if (is_null($item)) { $stack[ $column_key ] = null; continue; } switch ($map[ 1 ]) { case 'Number': $stack[ $column_key ] = (double) $item; break; case 'Int': $stack[ $column_key ] = (int) $item; break; case 'Bool': $stack[ $column_key ] = (bool) $item; break; case 'Object': $stack[ $column_key ] = unserialize($item); break; case 'JSON': $stack[ $column_key ] = json_decode($item, true); break; case 'String': $stack[ $column_key ] = $item; break; } } else { $stack[ $column_key ] = $item; } } else { $current_stack = []; $this->dataMap($data, $value, $column_map, $current_stack, false, $result); $stack[ $key ] = $current_stack; } } } public function create($table, $columns, $options = null) { $stack = []; $tableName = $this->prefix . $table; foreach ($columns as $name => $definition) { if (is_int($name)) { $stack[] = preg_replace('/\<([a-zA-Z0-9_]+)\>/i', '"$1"', $definition); } elseif (is_array($definition)) { $stack[] = $name . ' ' . implode(' ', $definition); } elseif (is_string($definition)) { $stack[] = $name . ' ' . $this->query($definition); } } $table_option = ''; if (is_array($options)) { $option_stack = []; foreach ($options as $key => $value) { if (is_string($value) || is_int($value)) { $option_stack[] = "$key = $value"; } } $table_option = ' ' . implode(', ', $option_stack); } elseif (is_string($options)) { $table_option = ' ' . $options; } return $this->exec("CREATE TABLE IF NOT EXISTS $tableName (" . implode(', ', $stack) . ")$table_option"); } public function drop($table) { $tableName = $this->prefix . $table; return $this->exec("DROP TABLE IF EXISTS $tableName"); } public function select($table, $join, $columns = null, $where = null) { $map = []; $result = []; $column_map = []; $index = 0; $column = $where === null ? $join : $columns; $is_single = (is_string($column) && $column !== '*'); $query = $this->exec($this->selectContext($table, $map, $join, $columns, $where), $map); $this->columnMap($columns, $column_map, true); if (!$this->statement) { return false; } if ($columns === '*') { return $query->fetchAll(PDO::FETCH_ASSOC); } while ($data = $query->fetch(PDO::FETCH_ASSOC)) { $current_stack = []; $this->dataMap($data, $columns, $column_map, $current_stack, true, $result); } if ($is_single) { $single_result = []; $result_key = $column_map[ $column ][ 0 ]; foreach ($result as $item) { $single_result[] = $item[ $result_key ]; } return $single_result; } return $result; } public function insert($table, $datas) { $stack = []; $columns = []; $fields = []; $map = []; if (!isset($datas[ 0 ])) { $datas = [$datas]; } foreach ($datas as $data) { foreach ($data as $key => $value) { $columns[] = $key; } } $columns = array_unique($columns); foreach ($datas as $data) { $values = []; foreach ($columns as $key) { if ($raw = $this->buildRaw($data[ $key ], $map)) { $values[] = $raw; continue; } $map_key = $this->mapKey(); $values[] = $map_key; if (!isset($data[ $key ])) { $map[ $map_key ] = [null, PDO::PARAM_NULL]; } else { $value = $data[ $key ]; $type = gettype($value); switch ($type) { case 'array': $map[ $map_key ] = [ strpos($key, '[JSON]') === strlen($key) - 6 ? json_encode($value) : serialize($value), PDO::PARAM_STR ]; break; case 'object': $value = serialize($value); case 'NULL': case 'resource': case 'boolean': case 'integer': case 'double': case 'string': $map[ $map_key ] = $this->typeMap($value, $type); break; } } } $stack[] = '(' . implode(', ', $values) . ')'; } foreach ($columns as $key) { $fields[] = $this->columnQuote(preg_replace("/(\s*\[JSON\]$)/i", '', $key)); } return $this->exec('INSERT INTO ' . $this->tableQuote($table) . ' (' . implode(', ', $fields) . ') VALUES ' . implode(', ', $stack), $map); } public function update($table, $data, $where = null) { $fields = []; $map = []; foreach ($data as $key => $value) { $column = $this->columnQuote(preg_replace("/(\s*\[(JSON|\+|\-|\*|\/)\]$)/i", '', $key)); if ($raw = $this->buildRaw($value, $map)) { $fields[] = $column . ' = ' . $raw; continue; } $map_key = $this->mapKey(); preg_match('/(?<column>[a-zA-Z0-9_]+)(\[(?<operator>\+|\-|\*|\/)\])?/i', $key, $match); if (isset($match[ 'operator' ])) { if (is_numeric($value)) { $fields[] = $column . ' = ' . $column . ' ' . $match[ 'operator' ] . ' ' . $value; } } else { $fields[] = $column . ' = ' . $map_key; $type = gettype($value); switch ($type) { case 'array': $map[ $map_key ] = [ strpos($key, '[JSON]') === strlen($key) - 6 ? json_encode($value) : serialize($value), PDO::PARAM_STR ]; break; case 'object': $value = serialize($value); case 'NULL': case 'resource': case 'boolean': case 'integer': case 'double': case 'string': $map[ $map_key ] = $this->typeMap($value, $type); break; } } } return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . implode(', ', $fields) . $this->whereClause($where, $map), $map); } public function delete($table, $where) { $map = []; return $this->exec('DELETE FROM ' . $this->tableQuote($table) . $this->whereClause($where, $map), $map); } public function replace($table, $columns, $where = null) { if (!is_array($columns) || empty($columns)) { return false; } $map = []; $stack = []; foreach ($columns as $column => $replacements) { if (is_array($replacements)) { foreach ($replacements as $old => $new) { $map_key = $this->mapKey(); $stack[] = $this->columnQuote($column) . ' = REPLACE(' . $this->columnQuote($column) . ', ' . $map_key . 'a, ' . $map_key . 'b)'; $map[ $map_key . 'a' ] = [$old, PDO::PARAM_STR]; $map[ $map_key . 'b' ] = [$new, PDO::PARAM_STR]; } } } if (!empty($stack)) { return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . implode(', ', $stack) . $this->whereClause($where, $map), $map); } return false; } public function get($table, $join = null, $columns = null, $where = null) { $map = []; $result = []; $column_map = []; $current_stack = []; if ($where === null) { $column = $join; unset($columns[ 'LIMIT' ]); } else { $column = $columns; unset($where[ 'LIMIT' ]); } $is_single = (is_string($column) && $column !== '*'); $query = $this->exec($this->selectContext($table, $map, $join, $columns, $where) . ' LIMIT 1', $map); if (!$this->statement) { return false; } $data = $query->fetchAll(PDO::FETCH_ASSOC); if (isset($data[ 0 ])) { if ($column === '*') { return $data[ 0 ]; } $this->columnMap($columns, $column_map, true); $this->dataMap($data[ 0 ], $columns, $column_map, $current_stack, true, $result); if ($is_single) { return $result[ 0 ][ $column_map[ $column ][ 0 ] ]; } return $result[ 0 ]; } } public function has($table, $join, $where = null) { $map = []; $column = null; if ($this->type === 'mssql') { $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, Medoo::raw('TOP 1 1')), $map); } else { $query = $this->exec('SELECT EXISTS(' . $this->selectContext($table, $map, $join, $column, $where, 1) . ')', $map); } if (!$this->statement) { return false; } $result = $query->fetchColumn(); return $result === '1' || $result === 1 || $result === true; } public function rand($table, $join = null, $columns = null, $where = null) { $type = $this->type; $order = 'RANDOM()'; if ($type === 'mysql') { $order = 'RAND()'; } elseif ($type === 'mssql') { $order = 'NEWID()'; } $order_raw = $this->raw($order); if ($where === null) { if ($columns === null) { $columns = [ 'ORDER' => $order_raw ]; } else { $column = $join; unset($columns[ 'ORDER' ]); $columns[ 'ORDER' ] = $order_raw; } } else { unset($where[ 'ORDER' ]); $where[ 'ORDER' ] = $order_raw; } return $this->select($table, $join, $columns, $where); } private function aggregate($type, $table, $join = null, $column = null, $where = null) { $map = []; $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, strtoupper($type)), $map); if (!$this->statement) { return false; } $number = $query->fetchColumn(); return is_numeric($number) ? $number + 0 : $number; } public function count($table, $join = null, $column = null, $where = null) { return $this->aggregate('count', $table, $join, $column, $where); } public function avg($table, $join, $column = null, $where = null) { return $this->aggregate('avg', $table, $join, $column, $where); } public function max($table, $join, $column = null, $where = null) { return $this->aggregate('max', $table, $join, $column, $where); } public function min($table, $join, $column = null, $where = null) { return $this->aggregate('min', $table, $join, $column, $where); } public function sum($table, $join, $column = null, $where = null) { return $this->aggregate('sum', $table, $join, $column, $where); } public function action($actions) { if (is_callable($actions)) { $this->pdo->beginTransaction(); try { $result = $actions($this); if ($result === false) { $this->pdo->rollBack(); } else { $this->pdo->commit(); } } catch (Exception $e) { $this->pdo->rollBack(); throw $e; } return $result; } return false; } public function id() { if ($this->statement == null) { return null; } $type = $this->type; if ($type === 'oracle') { return 0; } elseif ($type === 'pgsql') { return $this->pdo->query('SELECT LASTVAL()')->fetchColumn(); } $lastId = $this->pdo->lastInsertId(); if ($lastId != "0" && $lastId != "") { return $lastId; } return null; } public function debug() { $this->debug_mode = true; return $this; } public function error() { return $this->errorInfo; } public function last() { $log = end($this->logs); return $this->generate($log[ 0 ], $log[ 1 ]); } public function log() { return array_map(function ($log) { return $this->generate($log[ 0 ], $log[ 1 ]); }, $this->logs ); } public function info() { $output = [ 'server' => 'SERVER_INFO', 'driver' => 'DRIVER_NAME', 'client' => 'CLIENT_VERSION', 'version' => 'SERVER_VERSION', 'connection' => 'CONNECTION_STATUS' ]; foreach ($output as $key => $value) { $output[ $key ] = @$this->pdo->getAttribute(constant('PDO::ATTR_' . $value)); } $output[ 'dsn' ] = $this->dsn; return $output; } } require_once 'Medoo.php'; use Medoo\Medoo; $database = new Medoo([ // required 'database_type' => 'mysql', 'database_name' => 'db_hk4e_gm', 'server' => '127.0.0.1', 'username' => 'db_hk4e_gm', 'password' => 'syxywl.cn', // [optional] 'charset' => 'utf8', 'port' => 3306, ]); 这是medoo.php
11-29
全局 Rules --- ## Cursor Rules for Comment Standards(注释规范 Cursor 规则) ### 1. 总体注释原则 - **目的**:注释应解释代码的**意图**(为什么这样做)、**复杂逻辑**或**非显而易见的实现细节**,而不是重复代码的字面含义。 - **简洁性**:注释应简明扼要,避免冗长或无意义的描述。 - **语言**:注释使用**中文**,确保团队成员一致理解;必要时可结合英文(如引用第三方库文档)。 - **维护性**:注释需代码同步更新,避免过时或误导性注释。 - **格式**:遵循 PEP 8 规范,注释代码对齐,使用适当的缩进和空行。 ### 2. 注释类型规范 以下是项目中常用的注释类型及其具体规范,适用于 `src/`、`scripts/`、`tests/` 等目录中的 Python 代码。 #### 2.1 模块级注释(Module-Level Comments) - **位置**:位于模块顶部,紧跟 `import` 语句之前或之后。 - **内容**:描述模块的**功能**、**主要职责**和**使用场景**。 - **格式**:使用多行注释(`"""` 或 `#`),以 `#` 开头每行对齐。 - **示例**(`src/modules/adspower/client.py`): ```python # 本模块封装 AdsPower 本地 API 客户端,用于管理浏览器实例的启动、停止和状态检查。 # 主要功能包括速率限制、多进程同步和错误重试机制。 # 使用时需配置 config.yaml 中的 adspower 字段。 ``` #### 2.2 函数方法注释(Function/Method Comments) - **位置**:使用 **Docstring**,位于函数或方法定义下方。 - **内容**: - 简要描述函数功能。 - 列出所有参数(`Args`)、返回值(`Returns`)和可能的异常(`Raises`)。 - 可选:补充使用场景或注意事项。 - **格式**:采用 **Google 风格** Docstring,使用三引号(`"""`)。 - **规范**: - 参数和返回值需包含类型注解。 - 每个部分(Args、Returns、Raises)使用缩进对齐。 - **示例**(`src/utils/config.py`): ```python def load_config(config_path: str) -> dict: """加载 YAML 配置文件并解析路径 Args: config_path (str): 配置文件路径 Returns: dict: 解析后的配置字典 Raises: FileNotFoundError: 配置文件不存在 yaml.YAMLError: 配置文件格式错误 """ ``` - **注意**:简单函数(如 getter/setter)可使用单行 Docstring,但仍需说明功能: ```python def get_logger(module_name: str) -> logging.Logger: """获取指定模块的日志记录器""" ``` #### 2.3 类注释(Class Comments) - **位置**:使用 Docstring,位于类定义下方。 - **内容**: - 描述类的**职责**和**主要功能**。 - 列出关键属性(`Attributes`)和初始化参数(`Args`)。 - 可选:说明类的主要方法或使用场景。 - **格式**:Google 风格 Docstring。 - **示例**(`src/modules/adspower/client.py`): ```python class AdsPowerLocalApi: """AdsPower 本地 API 客户端,支持多进程速率限制 Attributes: user_id (str): AdsPower 环境 ID base_url (str): API 基础 URL,从 config.yaml 加载 Args: user_id (str): AdsPower 环境 ID(必需) acc_id (str, optional): 账户标识符,默认为 user_id headless (int, optional): 无头模式(0=有界面,1=无界面),默认为 0 timeout (int, optional): 请求超时时间(秒),默认为 10 """ ``` #### 2.4 行内注释(Inline Comments) - **位置**:位于代码行末尾,代码间隔至少两个空格。 - **内容**:解释**复杂逻辑**、**魔法数字**或**特殊处理**,避免描述显而易见的内容。 - **格式**:使用 `#` 开头,简短且清晰。 - **规范**: - 行内注释应少用,仅在必要时添加。 - 避免过长,超 40 字符的注释建议改为块注释。 - **示例**(`src/modules/adspower/client.py`): ```python interval = RATE_LIMIT_INTERVALS.get(endpoint, RATE_LIMIT_INTERVALS['default']) # 获取端点特定间隔,默认为 1.0 秒 ``` #### 2.5 块注释(Block Comments) - **位置**:位于代码段之前,描述一段逻辑的**目的**或**步骤**。 - **内容**:解释多行代码的整体意图,适用于复杂算法或流程。 - **格式**:每行以 `#` 开头,代码缩进对齐,上下各空一行。 - **示例**(`src/modules/adspower/client.py`): ```python # 全局同步速率限制,确保多进程环境下 API 调用不超频 try: with RATE_LIMIT_LOCK.acquire(timeout=5): with open(RATE_LIMIT_FILE, 'r') as f: state = json.load(f) ``` #### 2.6 TODO 注释 - **位置**:位于需要后续处理的地方,可为行内或块注释。 - **内容**:说明待完成的任务、优化点或问题,包含负责人(可选)和预期完成时间(推荐)。 - **格式**:使用 `# TODO:` 前缀,清晰描述任务。 - **示例**: ```python # TODO: 添加对 AdsPower API 新端点 /restart 的支持 (负责人: 张三, 预计: 2025-08-15) ``` ### 3. 注释使用场景 - **资源路径处理**: - 在使用 `resource_path` 的地方,注释说明路径用途和打包环境兼容性。 - 示例(`src/utils/helpers.py`): ```python def resource_path(relative_path: str) -> str: """获取资源文件的绝对路径,支持开发和 PyInstaller 打包环境 Args: relative_path (str): 相对路径,如 'config/config.yaml' Returns: str: 绝对路径 """ # 检查是否为 PyInstaller 打包环境 if hasattr(sys, '_MEIPASS'): base_path = Path(sys._MEIPASS) # 打包后的临时目录 else: base_path = Path(__file__).resolve().parent.parent.parent # 开发环境根目录 return str(base_path / relative_path) ``` - **配置加载**: - 在 `load_config` 中,注释说明配置字段的默认值和异常处理逻辑。 - 示例(`src/utils/config.py`): ```python # 设置默认 rate_limit_intervals,防止配置文件缺失 if 'rate_limit_intervals' not in config['adspower']: config['adspower']['rate_limit_intervals'] = {'default': 1.0, 'start': 1.0, 'stop': 1.0, 'active': 0.5} ``` - **API 请求**: - 在 AdsPower API 调用(如 `start_remote_browser`)中,注释说明重试机制和错误处理。 - 示例(`src/modules/adspower/client.py`): ```python # 重试 3 次以处理网络波动,每次间隔 1 秒 for attempt in range(3): try: response = requests.get(url, params=params, timeout=self.timeout) response.raise_for_status() return response.json() except requests.RequestException as e: if attempt < 2: time.sleep(1.0) # 等待 1 秒后重试 continue logger.error(f"请求失败: {e}") return None ``` - **日志管理**: - 在 `logger.py` 中,注释说明日志轮转机制和敏感信息过滤。 - 示例(`src/utils/logger.py`): ```python # 配置日志轮转,限制文件大小为 10MB,保留最近 30 个文件 handler = RotatingFileHandler( f"{log_dir}/{datetime.today().isoformat()}.log", maxBytes=10*1024*1024, # 10MB backupCount=30 ) ``` ### 4. 注释注意事项 - **避免冗余**: - 不要注释显而易见的代码,如 `x = 1 # 设置 x 为 1`。 - 示例(不推荐): ```python config = yaml.safe_load(f) # 加载 YAML 文件 ``` - 改为(推荐): ```python config = yaml.safe_load(f) # 解析 config.yaml 并返回字典 ``` - **保持同步**: - 修改代码时,同步更新相关注释,特别是在更改函数参数或逻辑时。 - 示例:若 `load_config` 新增参数,需更新 Docstring: ```python def load_config(config_path: str, env: str = "dev") -> dict: """加载 YAML 配置文件并解析路径 Args: config_path (str): 配置文件路径 env (str, optional): 环境名称,默认为 'dev' Returns: dict: 解析后的配置字典 """ ``` - **敏感信息**: - 注释中不得包含敏感信息(如 API 密钥、用户 ID)。 - 示例(不推荐): ```python # 使用密钥 ABC123 调用 API ``` - 改为(推荐): ```python # 使用 config.yaml 中的 API 密钥调用接口 ``` - **一致性**: - 统一使用中文注释,除非引用英文文档或第三方库术语(如 `PyInstaller` 的 `_MEIPASS`)。 - 所有 Docstring 使用 Google 风格,避免混用其他风格(如 reStructuredText)。 ### 5. Cursor 提示示例 在 Cursor 中编写代码时,可使用以下提示,确保注释符合规范: - **添加函数注释**: ``` 为 src/modules/my_module.py 中的新函数 process_data 添加 Google 风格 Docstring,说明功能、参数、返回值和异常,注释复杂逻辑。 ``` - **更新模块注释**: ``` 为 src/modules/adspower/client.py 添加模块级注释,描述模块职责和配置要求,使用 # 格式。 ``` - **添加 TODO 注释**: ``` 在 src/utils/config.py 的 load_config 函数中添加 TODO 注释,说明计划支持 JSON 格式配置,包含负责人和截止日期。 ``` --- 感谢您的要求!以下是基于您提供的项目目录结构和代码,针对**使用安全的第三方库**的 **Cursor Rules** 补充说明。这些规则专注于在开发本地化可执行文件时选择和使用安全的第三方库,确保项目的安全性、稳定性和兼容性,同时符合注释规范和项目结构要求。内容以中文表述,适用于 Cursor 或其他代码编辑器,并特别考虑 PyInstaller 打包环境。 --- ## Cursor Rules for Using Safe Third-Party Libraries(使用安全的第三方库 Cursor 规则) ### 1. 第三方库选择原则 - **安全性**: - 优先选择经过广泛使用和社区维护的成熟库,避免使用未经充分验证或长期未更新的库。 - 检查库的 GitHub 仓库活跃度(最近提交时间、问题响应速度)和安全漏洞报告(如通过 `pip-audit` 或 GitHub Dependabot)。 - 避免使用包含已知安全漏洞的库版本,定期更新到最新稳定版本。 - **兼容性**: - 确保库项目使用的 Python 版本(推荐 Python 3.8+)和 PyInstaller 打包环境兼容。 - 检查库是否支持跨平台(Windows、macOS、Linux),特别是针对 `data/` 和 `config/` 文件的路径处理。 - **许可证**: - 选择项目许可证(如 MIT/Apache 2.0)兼容的库,避免使用限制性许可证(如 GPL)导致分发问题。 - 记录使用的第三方库及其许可证信息,更新 `docs/README.md` 或 `LICENSE` 文件。 - **最小化依赖**: - 仅引入必要的库,减少打包体积和潜在的安全风险。 - 示例:优先使用标准库(如 `pathlib`、`json`)替代第三方库(如 `os.path` 的替代品)。 ### 2. 当前使用的第三方库 根据您提供的代码,以下是项目中已使用的第三方库及其安全使用建议: - **`pyyaml`**:用于解析 `config.yaml`。 - **安全建议**: - 使用 `yaml.safe_load`(已实现),避免 `yaml.load` 以防止代码注入风险。 - 示例(`src/utils/config.py`): ```python import yaml def load_config(config_path: str) -> dict: """加载 YAML 配置文件并解析路径""" with open(config_path, 'r') as f: config = yaml.safe_load(f) or {} # 使用 safe_load 防止代码注入 return config ``` - 确保版本为最新(如 `pyyaml>=6.0`),检查漏洞(如 CVE-2020-1747)。 - **`requests`**:用于 AdsPower API 调用。 - **安全建议**: - 确保使用 HTTPS 协议(如 `http://localhost:50325` 改为 `https://` 如果支持)。 - 配置合理的超时时间(已实现,`timeout=self.timeout`)。 - 示例(`src/modules/adspower/client.py`): ```python response = requests.get( f"{self.base_url}/api/v1/browser/start", params={"user_id": self.user_id, "headless": self.headless}, timeout=self.timeout # 设置超时防止挂起 ) ``` - 定期更新到最新版本(如 `requests>=2.28`),检查漏洞(如 CVE-2023-32681)。 - **`filelock`**:用于多进程速率限制。 - **安全建议**: - 确保锁文件(如 `ads_power_rate_limit.json.lock`)存储在 `data/adspower/`,通过 `resource_path` 访问。 - 处理 `Timeout` 异常并记录日志。 - 示例(`src/modules/adspower/client.py`): ```python from filelock import FileLock, Timeout from src.utils.helpers import resource_path RATE_LIMIT_FILE = resource_path(config['adspower']['rate_limit_file']) RATE_LIMIT_LOCK = FileLock(str(RATE_LIMIT_FILE) + ".lock") def _init_rate_limit_file(self): """初始化速率控制文件""" try: with RATE_LIMIT_LOCK.acquire(timeout=5): if not RATE_LIMIT_FILE.exists(): with open(RATE_LIMIT_FILE, 'w') as f: json.dump({"start": 0, "stop": 0, "active": 0}, f) except Timeout: logger.warning(f"初始化 {RATE_LIMIT_FILE} 超时,重置文件") # 记录超时情况 ``` - 使用最新版本(如 `filelock>=3.12`)。 - **`python-dotenv`**:加载 `.env` 文件中的环境变量。 - **安全建议**: - 确保 `.env` 文件在 `.gitignore` 中,避免泄露敏感信息。 - 验证环境变量是否存在并设置默认值。 - 示例: ```python from dotenv import load_dotenv import os load_dotenv() env = os.getenv("ENV", "dev") # 设置默认环境为 dev ``` - 使用最新版本(如 `python-dotenv>=1.0`)。 - **`pyinstaller`**:用于打包可执行文件。 - **安全建议**: - 确保 `pyinstaller` 版本 Python 版本兼容(如 `pyinstaller>=5.0`)。 - 使用 `--clean` 选项清理缓存,减少打包错误。 - 示例打包命令: ```bash pyinstaller --clean build.spec ``` ### 3. 第三方库管理规范 - **记录依赖**: - 将所有第三方库记录在 `requirements.txt` 和 `pyproject.toml` 中,确保版本明确。 - 示例 `requirements.txt`: ``` pyyaml>=6.0 requests>=2.28 filelock>=3.12 python-dotenv>=1.0 pyinstaller>=5.0 ``` - 示例 `pyproject.toml`: ```toml [project] name = "project_name" version = "0.1.0" dependencies = [ "pyyaml>=6.0", "requests>=2.28", "filelock>=3.12", "python-dotenv>=1.0", "pyinstaller>=5.0", ] ``` - **依赖安装**: - 使用 `pip install -r requirements.txt` 安装依赖。 - 推荐使用虚拟环境(如 `venv`)隔离项目依赖: ```bash python -m venv venv source venv/bin/activate # Linux/macOS venv\Scripts\activate # Windows pip install -r requirements.txt ``` - **依赖更新**: - 定期运行 `pip-audit` 检查依赖的安全漏洞: ```bash pip install pip-audit pip-audit -r requirements.txt ``` - 更新到最新安全版本,使用 `pip install --upgrade <package>`。 - **PyInstaller 兼容性**: - 确保第三方库在打包后正常工作,测试 `data/` 和 `config/` 文件访问。 - 在 `build.spec` 中显式包含第三方库的数据文件: ```python from PyInstaller.utils.hooks import collect_data_files a = Analysis( ['src/main.py'], datas=[ ('config/*', 'config'), ('data/input/*', 'data/input'), ('data/adspower/*', 'data/adspower') ], hiddenimports=['yaml', 'requests', 'filelock', 'python-dotenv'] ) ``` ### 4. 安全使用第三方库的代码规范 - **配置加载(`pyyaml`)**: - 始终使用 `yaml.safe_load` 解析 YAML 文件,防止代码注入。 - 添加注释说明安全措施: ```python import yaml from src.utils.helpers import resource_path def load_config(config_path: str) -> dict: """加载 YAML 配置文件并解析路径 Args: config_path (str): 配置文件路径 Returns: dict: 解析后的配置字典 Raises: FileNotFoundError: 配置文件不存在 yaml.YAMLError: 配置文件格式错误 """ config_path = resource_path(config_path) # 确保兼容 PyInstaller try: with open(config_path, 'r') as f: config = yaml.safe_load(f) or {} # 使用 safe_load 防止代码注入 except FileNotFoundError: raise FileNotFoundError(f"配置文件 {config_path} 不存在") except yaml.YAMLError as e: raise yaml.YAMLError(f"解析配置文件 {config_path} 失败: {e}") return config ``` - **API 请求(`requests`)**: - 使用 HTTPS 协议,设置超时,处理异常。 - 添加注释说明安全性和重试逻辑: ```python import requests from src.utils.logger import get_logger logger = get_logger(__name__) def start_remote_browser(self): """启动浏览器实例 Returns: str: 浏览器 WebSocket 地址(成功时) None: 失败时 """ self._rate_limit("start") # 确保速率限制 for attempt in range(3): try: response = requests.get( f"{self.base_url}/api/v1/browser/start", params={"user_id": self.user_id, "headless": self.headless}, timeout=self.timeout # 设置超时防止挂起 ) response.raise_for_status() # 检查 HTTP 状态码 data = response.json() if data.get("code") != 0: logger.error(f"启动浏览器失败: {data.get('msg', '未知错误')}") return None return data["data"]["ws"]["puppeteer"] except requests.RequestException as e: if attempt < 2: time.sleep(1.0) # 重试间隔 1 秒 continue logger.error(f"启动浏览器失败: {e}") return None ``` - **文件锁(`filelock`)**: - 使用 `resource_path` 确保锁文件路径正确。 - 处理 `Timeout` 异常并记录日志: ```python from filelock import FileLock, Timeout from src.utils.helpers import resource_path RATE_LIMIT_FILE = resource_path(config['adspower']['rate_limit_file']) RATE_LIMIT_LOCK = FileLock(str(RATE_LIMIT_FILE) + ".lock") def _init_rate_limit_file(self): """初始化速率控制文件,确保多进程安全""" try: with RATE_LIMIT_LOCK.acquire(timeout=5): # 设置 5 秒超时 if not RATE_LIMIT_FILE.exists(): with open(RATE_LIMIT_FILE, 'w') as f: json.dump({"start": 0, "stop": 0, "active": 0}, f) except Timeout: logger.warning(f"初始化 {RATE_LIMIT_FILE} 超时,重置文件") with open(RATE_LIMIT_FILE, 'w') as f: json.dump({"start": 0, "stop": 0, "active": 0}, f) ``` - **环境变量(`python-dotenv`)**: - 确保 `.env` 文件安全加载,注释说明敏感信息处理: ```python from dotenv import load_dotenv import os load_dotenv() # 加载 .env 文件中的环境变量 BASE_URL = os.getenv("ADPOWER_URL", "http://localhost:50325") # 默认值防止缺失 ``` ### 5. 注释规范(第三方库相关) - **库用途**:在模块级注释中说明使用的第三方库及其功能。 - 示例(`src/modules/adspower/client.py`): ```python # 本模块封装 AdsPower 本地 API 客户端,依赖 requests(HTTP 请求)、filelock(多进程锁)和 pyyaml(配置解析)。 # 所有文件路径通过 resource_path 处理,确保 PyInstaller 打包兼容。 ``` - **安全措施**:在代码中注释说明第三方库的安全使用方式。 - 示例(`src/utils/config.py`): ```python config = yaml.safe_load(f) # 使用 safe_load 防止 YAML 代码注入风险 ``` - **版本说明**:在 `docs/README.md` 或模块注释中记录推荐的库版本。 - 示例(`docs/README.md`): ```markdown ## 依赖 - pyyaml>=6.0: 安全的 YAML 解析 - requests>=2.28: HTTP 请求,支持超时和重试 - filelock>=3.12: 多进程文件锁 - python-dotenv>=1.0: 环境变量加载 - pyinstaller>=5.0: 打包为可执行文件 ``` ### 6. PyInstaller 打包第三方库 - **包含依赖**: - 在 `build.spec` 中通过 `hiddenimports` 显式包含第三方库: ```python a = Analysis( ['src/main.py'], hiddenimports=['yaml', 'requests', 'filelock', 'python-dotenv'], datas=[ ('config/*', 'config'), ('data/input/*', 'data/input'), ('data/adspower/*', 'data/adspower') ] ) ``` - **测试打包**: - 打包后运行可执行文件,验证第三方库功能: - 检查 `yaml.safe_load` 是否正确解析 `config.yaml`。 - 验证 `requests` 是否能正常访问 AdsPower API。 - 确保 `filelock` 正确管理 `ads_power_rate_limit.json`。 - **日志记录**: - 在打包环境中记录第三方库相关错误: ```python try: import yaml except ImportError: logger.error("未找到 pyyaml 库,请确保已安装 pyyaml>=6.0") raise ``` ### 7. Cursor 提示示例 在 Cursor 中使用以下提示,确保第三方库安全集成: - **添加新库**: ``` 在 src/utils/ 中添加新模块 crypto.py,使用 cryptography 库(版本>=40.0)进行数据加密,添加 Google 风格 Docstring,说明安全措施,并在 requirements.txt 中记录。 ``` - **安全配置**: ``` 修改 src/utils/config.py,使用 yaml.safe_load 解析 config.yaml,添加注释说明安全性和 resource_path 使用。 ``` - **打包配置**: ``` 更新 build.spec,确保包含 pyyaml、requests 和 data/adspower/ 目录,验证 resource_path 在打包后正常工作。 ``` ### 8. 其他注意事项 - **安全扫描**: - 定期使用 `pip-audit` 检查依赖漏洞: ```bash pip-audit -r requirements.txt ``` - **最小化权限**: - 限制第三方库的权限,如避免 `requests` 访问非必要 URL。 - 示例: ```python if not self.base_url.startswith("https://"): logger.warning(f"非 HTTPS 协议 URL: {self.base_url},建议启用 HTTPS") ``` - **文档更新**: - 在 `docs/README.md` 中记录新增第三方库的用途、版本和安全注意事项。 - 示例: ```markdown ### 新增依赖 - cryptography>=40.0: 用于加密敏感数据,需确保使用安全的密钥管理。 ```
07-31
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值