You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
579 lines
17 KiB
579 lines
17 KiB
<?php
|
|
// +----------------------------------------------------------------------
|
|
// | PHPSpider [ A PHP Framework For Crawler ]
|
|
// +----------------------------------------------------------------------
|
|
// | Copyright (c) 2006-2014 https://doc.phpspider.org All rights reserved.
|
|
// +----------------------------------------------------------------------
|
|
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
|
// +----------------------------------------------------------------------
|
|
// | Author: Seatle Yang <seatle@foxmail.com>
|
|
// +----------------------------------------------------------------------
|
|
|
|
//----------------------------------
|
|
// PHPSpider数据库类文件
|
|
//----------------------------------
|
|
|
|
namespace phpspider\core;
|
|
|
|
class db
|
|
{
|
|
private static $configs = array();
|
|
private static $rsid;
|
|
private static $links = array();
|
|
private static $link_name = 'default';
|
|
private static $autocommiting = false;
|
|
|
|
public static function _init()
|
|
{
|
|
// 获取配置
|
|
$config = self::$link_name == 'default' ? self::_get_default_config() : self::$configs[self::$link_name];
|
|
|
|
// 创建连接
|
|
if (empty(self::$links[self::$link_name]) || empty(self::$links[self::$link_name]['conn']))
|
|
{
|
|
// 第一次连接,初始化fail和pid
|
|
if (empty(self::$links[self::$link_name]))
|
|
{
|
|
self::$links[self::$link_name]['fail'] = 0;
|
|
self::$links[self::$link_name]['pid'] = function_exists('posix_getpid') ? posix_getpid() : 0;
|
|
//echo "progress[".self::$links[self::$link_name]['pid']."] create db connect[".self::$link_name."]\n";
|
|
}
|
|
self::$links[self::$link_name]['conn'] = mysqli_connect($config['host'], $config['user'], $config['pass'], $config['name'], $config['port']);
|
|
if(mysqli_connect_errno())
|
|
{
|
|
self::$links[self::$link_name]['fail']++;
|
|
$errmsg = 'Mysql Connect failed['.self::$links[self::$link_name]['fail'].']: ' . mysqli_connect_error();
|
|
echo util::colorize(date("H:i:s") . " {$errmsg}\n\n", 'fail');
|
|
log::add($errmsg, "Error");
|
|
// 连接失败5次,中断进程
|
|
if (self::$links[self::$link_name]['fail'] >= 5)
|
|
{
|
|
exit(250);
|
|
}
|
|
self::_init($config);
|
|
}
|
|
else
|
|
{
|
|
mysqli_query(self::$links[self::$link_name]['conn'], " SET character_set_connection=utf8, character_set_results=utf8, character_set_client=binary, sql_mode='' ");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$curr_pid = function_exists('posix_getpid') ? posix_getpid() : 0;
|
|
// 如果父进程已经生成资源就释放重新生成,因为多进程不能共享连接资源
|
|
if (self::$links[self::$link_name]['pid'] != $curr_pid)
|
|
{
|
|
self::clear_link();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 重新设置连接
|
|
* 传空的话就等于关闭数据库再连接
|
|
* 在多进程环境下如果主进程已经调用过了,子进程一定要调用一次 clear_link,否则会报错:
|
|
* Error while reading greeting packet. PID=19615,这是两个进程互抢一个连接句柄引起的
|
|
*
|
|
* @param array $config
|
|
* @return void
|
|
* @author seatle <seatle@foxmail.com>
|
|
* @created time :2016-03-29 00:51
|
|
*/
|
|
public static function clear_link()
|
|
{
|
|
if(self::$links)
|
|
{
|
|
foreach(self::$links as $k=>$v)
|
|
{
|
|
@mysqli_close($v['conn']);
|
|
unset(self::$links[$k]);
|
|
}
|
|
}
|
|
// 注意,只会连接最后一个,不过貌似也够用了啊
|
|
self::_init();
|
|
}
|
|
|
|
/**
|
|
* 改变链接为指定配置的链接(如果不同时使用多个数据库,不会涉及这个操作)
|
|
* @parem $link_name 链接标识名
|
|
* @parem $config 多次使用时, 这个数组只需传递一次
|
|
* config 格式与 $GLOBALS['config']['db'] 一致
|
|
* @return void
|
|
*/
|
|
public static function set_connect($link_name, $config = array())
|
|
{
|
|
self::$link_name = $link_name;
|
|
if (!empty($config))
|
|
{
|
|
self::$configs[self::$link_name] = $config;
|
|
}
|
|
else
|
|
{
|
|
if (empty(self::$configs[self::$link_name]))
|
|
{
|
|
throw new Exception("You not set a config array for connect!");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 还原为默认连接(如果不同时使用多个数据库,不会涉及这个操作)
|
|
* @parem $config 指定配置(默认使用inc_config.php的配置)
|
|
* @return void
|
|
*/
|
|
public static function set_connect_default()
|
|
{
|
|
$config = self::_get_default_config();
|
|
self::set_connect('default', $config);
|
|
}
|
|
|
|
|
|
/**
|
|
* 获取默认配置
|
|
*/
|
|
protected static function _get_default_config()
|
|
{
|
|
if (empty(self::$configs['default']))
|
|
{
|
|
if (!is_array($GLOBALS['config']['db']))
|
|
{
|
|
exit('db.php _get_default_config()' . '没有mysql配置');
|
|
}
|
|
self::$configs['default'] = $GLOBALS['config']['db'];
|
|
}
|
|
return self::$configs['default'];
|
|
}
|
|
|
|
/**
|
|
* 返回查询游标
|
|
* @return rsid
|
|
*/
|
|
protected static function _get_rsid($rsid = '')
|
|
{
|
|
return $rsid == '' ? self::$rsid : $rsid;
|
|
}
|
|
|
|
public static function autocommit($mode = false)
|
|
{
|
|
if ( self::$autocommiting )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
self::$autocommiting = true;
|
|
|
|
self::_init();
|
|
return mysqli_autocommit(self::$links[self::$link_name]['conn'], $mode);
|
|
}
|
|
|
|
public static function begin_tran()
|
|
{
|
|
return self::autocommit(false);
|
|
}
|
|
|
|
public static function commit()
|
|
{
|
|
mysqli_commit(self::$links[self::$link_name]['conn']);
|
|
self::autocommit(true);
|
|
return true;
|
|
}
|
|
|
|
|
|
public static function rollback()
|
|
{
|
|
mysqli_rollback(self::$links[self::$link_name]['conn']);
|
|
self::autocommit(true);
|
|
return true;
|
|
}
|
|
|
|
public static function query($sql)
|
|
{
|
|
$sql = trim($sql);
|
|
|
|
// 初始化数据库
|
|
self::_init();
|
|
self::$rsid = @mysqli_query(self::$links[self::$link_name]['conn'], $sql);
|
|
|
|
if (self::$rsid === false)
|
|
{
|
|
// 不要每次都ping,浪费流量浪费性能,执行出错了才重新连接
|
|
$errno = mysqli_errno(self::$links[self::$link_name]['conn']);
|
|
if ($errno == 2013 || $errno == 2006)
|
|
{
|
|
$errmsg = mysqli_error(self::$links[self::$link_name]['conn']);
|
|
log::add($errmsg, "Error");
|
|
|
|
@mysqli_close(self::$links[self::$link_name]['conn']);
|
|
self::$links[self::$link_name]['conn'] = null;
|
|
return self::query($sql);
|
|
}
|
|
|
|
$errmsg = "Query SQL: ".$sql;
|
|
log::add($errmsg, "Warning");
|
|
$errmsg = "Error SQL: ".mysqli_error(self::$links[self::$link_name]['conn']);
|
|
log::add($errmsg, "Warning");
|
|
|
|
$backtrace = debug_backtrace();
|
|
array_shift($backtrace);
|
|
$narr = array('class', 'type', 'function', 'file', 'line');
|
|
$err = "debug_backtrace:\n";
|
|
foreach($backtrace as $i => $l)
|
|
{
|
|
foreach($narr as $k)
|
|
{
|
|
if( !isset($l[$k]) )
|
|
{
|
|
$l[$k] = '';
|
|
}
|
|
}
|
|
$err .= "[$i] in function {$l['class']}{$l['type']}{$l['function']} ";
|
|
if($l['file']) $err .= " in {$l['file']} ";
|
|
if($l['line']) $err .= " on line {$l['line']} ";
|
|
$err .= "\n";
|
|
}
|
|
log::add($err);
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return self::$rsid;
|
|
}
|
|
}
|
|
|
|
public static function fetch($rsid = '')
|
|
{
|
|
$rsid = self::_get_rsid($rsid);
|
|
$row = mysqli_fetch_array($rsid, MYSQLI_ASSOC);
|
|
return $row;
|
|
}
|
|
|
|
public static function get_one($sql)
|
|
{
|
|
if (!preg_match("/limit/i", $sql))
|
|
{
|
|
$sql = preg_replace("/[,;]$/i", '', trim($sql)) . " limit 1 ";
|
|
}
|
|
$rsid = self::query($sql);
|
|
if ($rsid === false)
|
|
{
|
|
return array();
|
|
}
|
|
$row = self::fetch($rsid);
|
|
self::free($rsid);
|
|
return $row;
|
|
}
|
|
|
|
public static function get_all($sql)
|
|
{
|
|
$rsid = self::query($sql);
|
|
if ($rsid === false)
|
|
{
|
|
return array();
|
|
}
|
|
while ( $row = self::fetch($rsid) )
|
|
{
|
|
$rows[] = $row;
|
|
}
|
|
self::free($rsid);
|
|
return empty($rows) ? false : $rows;
|
|
}
|
|
|
|
public static function free($rsid)
|
|
{
|
|
return mysqli_free_result($rsid);
|
|
}
|
|
|
|
public static function insert_id()
|
|
{
|
|
return mysqli_insert_id(self::$links[self::$link_name]['conn']);
|
|
}
|
|
|
|
public static function affected_rows()
|
|
{
|
|
return mysqli_affected_rows(self::$links[self::$link_name]['conn']);
|
|
}
|
|
|
|
public static function insert($table = '', $data = null, $return_sql = false)
|
|
{
|
|
$items_sql = $values_sql = "";
|
|
foreach ($data as $k => $v)
|
|
{
|
|
$v = stripslashes($v);
|
|
$v = addslashes($v);
|
|
$items_sql .= "`$k`,";
|
|
$values_sql .= "\"$v\",";
|
|
}
|
|
$sql = "Insert Ignore Into `{$table}` (" . substr($items_sql, 0, -1) . ") Values (" . substr($values_sql, 0, -1) . ")";
|
|
if ($return_sql)
|
|
{
|
|
return $sql;
|
|
}
|
|
else
|
|
{
|
|
if (self::query($sql))
|
|
{
|
|
return mysqli_insert_id(self::$links[self::$link_name]['conn']);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static function insert_batch($table = '', $set = NULL, $return_sql = FALSE)
|
|
{
|
|
if (empty($table) || empty($set))
|
|
{
|
|
return false;
|
|
}
|
|
$set = self::strsafe($set);
|
|
$fields = self::get_fields($table);
|
|
|
|
$keys_sql = $vals_sql = array();
|
|
foreach ($set as $i=>$val)
|
|
{
|
|
ksort($val);
|
|
$vals = array();
|
|
foreach ($val as $k => $v)
|
|
{
|
|
// 过滤掉数据库没有的字段
|
|
if (!in_array($k, $fields))
|
|
{
|
|
continue;
|
|
}
|
|
// 如果是第一个数组,把key当做插入条件
|
|
if ($i == 0 && $k == 0)
|
|
{
|
|
$keys_sql[] = "`$k`";
|
|
}
|
|
$vals[] = "\"$v\"";
|
|
}
|
|
$vals_sql[] = implode(",", $vals);
|
|
}
|
|
|
|
$sql = "Insert Ignore Into `{$table}`(".implode(", ", $keys_sql).") Values (".implode("), (", $vals_sql).")";
|
|
|
|
if ($return_sql) return $sql;
|
|
|
|
$rt = self::query($sql);
|
|
$insert_id = self::insert_id();
|
|
$return = empty($insert_id) ? $rt : $insert_id;
|
|
return $return;
|
|
}
|
|
|
|
public static function update_batch($table = '', $set = NULL, $index = NULL, $where = NULL, $return_sql = FALSE)
|
|
{
|
|
if (empty($table) || is_null($set) || is_null($index))
|
|
{
|
|
// 不要用exit,会中断程序
|
|
return false;
|
|
}
|
|
$set = self::strsafe($set);
|
|
$fields = self::get_fields($table);
|
|
|
|
$ids = array();
|
|
foreach ($set as $val)
|
|
{
|
|
ksort($val);
|
|
// 去重,其实不去也可以,因为相同的when只会执行第一个,后面的就直接跳过不执行了
|
|
$key = md5($val[$index]);
|
|
$ids[$key] = $val[$index];
|
|
|
|
foreach (array_keys($val) as $field)
|
|
{
|
|
if ($field != $index)
|
|
{
|
|
$final[$field][$key] = 'When `'.$index.'` = "'.$val[$index].'" Then "'.$val[$field].'"';
|
|
}
|
|
}
|
|
}
|
|
//$ids = array_values($ids);
|
|
|
|
// 如果不是数组而且不为空,就转数组
|
|
if (!is_array($where) && !empty($where))
|
|
{
|
|
$where = array($where);
|
|
}
|
|
$where[] = $index.' In ("'.implode('","', $ids).'")';
|
|
$where = empty($where) ? "" : " Where ".implode(" And ", $where);
|
|
|
|
$sql = "Update `".$table."` Set ";
|
|
$cases = '';
|
|
|
|
foreach ($final as $k => $v)
|
|
{
|
|
// 过滤掉数据库没有的字段
|
|
if (!in_array($k, $fields))
|
|
{
|
|
continue;
|
|
}
|
|
$cases .= '`'.$k.'` = Case '."\n";
|
|
foreach ($v as $row)
|
|
{
|
|
$cases .= $row."\n";
|
|
}
|
|
|
|
$cases .= 'Else `'.$k.'` End, ';
|
|
}
|
|
|
|
$sql .= substr($cases, 0, -2);
|
|
|
|
// 其实不带 Where In ($index) 的条件也可以的
|
|
$sql .= $where;
|
|
|
|
if ($return_sql) return $sql;
|
|
|
|
$rt = self::query($sql);
|
|
$insert_id = self::affected_rows();
|
|
$return = empty($affected_rows) ? $rt : $affected_rows;
|
|
return $return;
|
|
}
|
|
|
|
public static function update($table = '', $data = array(), $where = null, $return_sql = false)
|
|
{
|
|
$sql = "UPDATE `{$table}` SET ";
|
|
foreach ($data as $k => $v)
|
|
{
|
|
$v = stripslashes($v);
|
|
$v = addslashes($v);
|
|
$sql .= "`{$k}` = \"{$v}\",";
|
|
}
|
|
if (!is_array($where))
|
|
{
|
|
$where = array($where);
|
|
}
|
|
// 删除空字段,不然array("")会成为WHERE
|
|
foreach ($where as $k => $v)
|
|
{
|
|
if (empty($v))
|
|
{
|
|
unset($where[$k]);
|
|
}
|
|
}
|
|
$where = empty($where) ? "" : " Where " . implode(" And ", $where);
|
|
$sql = substr($sql, 0, -1) . $where;
|
|
if ($return_sql)
|
|
{
|
|
return $sql;
|
|
}
|
|
else
|
|
{
|
|
if (self::query($sql))
|
|
{
|
|
return mysqli_affected_rows(self::$links[self::$link_name]['conn']);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static function delete($table = '', $where = null, $return_sql = false)
|
|
{
|
|
// 小心全部被删除了
|
|
if (empty($where))
|
|
{
|
|
return false;
|
|
}
|
|
$where = 'Where ' . (!is_array($where) ? $where : implode(' And ', $where));
|
|
$sql = "Delete From `{$table}` {$where}";
|
|
if ($return_sql)
|
|
{
|
|
return $sql;
|
|
}
|
|
else
|
|
{
|
|
if (self::query($sql))
|
|
{
|
|
return mysqli_affected_rows(self::$links[self::$link_name]['conn']);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static function ping()
|
|
{
|
|
if (!mysqli_ping(self::$links[self::$link_name]['conn']))
|
|
{
|
|
@mysqli_close(self::$links[self::$link_name]['conn']);
|
|
self::$links[self::$link_name]['conn'] = null;
|
|
self::_init();
|
|
}
|
|
}
|
|
|
|
public static function strsafe($array)
|
|
{
|
|
$arrays = array();
|
|
if(is_array($array)===true)
|
|
{
|
|
foreach ($array as $key => $val)
|
|
{
|
|
if(is_array($val)===true)
|
|
{
|
|
$arrays[$key] = self::strsafe($val);
|
|
}
|
|
else
|
|
{
|
|
//先去掉转义,避免下面重复转义了
|
|
$val = stripslashes($val);
|
|
//进行转义
|
|
$val = addslashes($val);
|
|
//处理addslashes没法处理的 _ % 字符
|
|
//$val = strtr($val, array('_'=>'\_', '%'=>'\%'));
|
|
$arrays[$key] = $val;
|
|
}
|
|
}
|
|
return $arrays;
|
|
}
|
|
else
|
|
{
|
|
$array = stripslashes($array);
|
|
$array = addslashes($array);
|
|
//$array = strtr($array, array('_'=>'\_', '%'=>'\%'));
|
|
return $array;
|
|
}
|
|
}
|
|
|
|
// 这个是给insert、update、insert_batch、update_batch用的
|
|
public static function get_fields($table)
|
|
{
|
|
// $sql = "SHOW COLUMNS FROM $table"; //和下面的语句效果一样
|
|
$rows = self::get_all("Desc `{$table}`");
|
|
$fields = array();
|
|
foreach ($rows as $k => $v)
|
|
{
|
|
// 过滤自增主键
|
|
// if ($v['Key'] != 'PRI')
|
|
if ($v['Extra'] != 'auto_increment')
|
|
{
|
|
$fields[] = $v['Field'];
|
|
}
|
|
}
|
|
return $fields;
|
|
}
|
|
|
|
public static function table_exists($table_name)
|
|
{
|
|
$sql = "SHOW TABLES LIKE '" . $table_name . "'";
|
|
$rsid = self::query($sql);
|
|
$table = self::fetch($rsid);
|
|
if (empty($table))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|