mirror of
https://github.com/kokororin/typecho-plugin-Access.git
synced 2026-08-19 02:13:29 +08:00
feat: Initial support for PostgreSQL (#80)
This commit is contained in:
@@ -33,11 +33,21 @@ class Access_Migration {
|
||||
public function exists()
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
if ($this->db->fetchRow($this->db->query("SHOW TABLES LIKE '{$prefix}access';", Typecho_Db::READ))) {
|
||||
return true;
|
||||
}
|
||||
if ($this->db->fetchRow($this->db->query("SHOW TABLES LIKE '{$prefix}access_log';", Typecho_Db::READ))) {
|
||||
return true;
|
||||
$adapterName = $this->db->getAdapterName();
|
||||
if (strpos($adapterName, 'Mysql') !== false || strpos($adapterName, 'SQLite') !== false) {
|
||||
if ($this->db->fetchRow($this->db->query("SHOW TABLES LIKE '{$prefix}access';", Typecho_Db::READ))) {
|
||||
return true;
|
||||
}
|
||||
if ($this->db->fetchRow($this->db->query("SHOW TABLES LIKE '{$prefix}access_log';", Typecho_Db::READ))) {
|
||||
return true;
|
||||
}
|
||||
} else if (strpos($adapterName, 'Pgsql') !== false) {
|
||||
if ($this->db->fetchRow($this->db->query("SELECT to_regclass('public.{$prefix}access');", Typecho_Db::READ))) {
|
||||
return true;
|
||||
}
|
||||
if ($this->db->fetchRow($this->db->query("SELECT to_regclass('public.{$prefix}access_log');", Typecho_Db::READ))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -53,18 +63,33 @@ class Access_Migration {
|
||||
{
|
||||
$resp = [ 'total' => 0 ];
|
||||
$prefix = $this->db->getPrefix();
|
||||
$adapterName = $this->db->getAdapterName();
|
||||
|
||||
// 统计 v1 版本数据
|
||||
if ($this->db->fetchRow($this->db->query("SHOW TABLES LIKE '{$prefix}access';", Typecho_Db::READ))) {
|
||||
if (strpos($adapterName, 'Mysql') !== false || strpos($adapterName, 'SQLite') !== false) {
|
||||
$checkTableV1 = $this->db->fetchRow($this->db->query("SHOW TABLES LIKE '{$prefix}access';", Typecho_Db::READ));
|
||||
} else if (strpos($adapterName, 'Pgsql') !== false) {
|
||||
$checkTableV1 = $this->db->fetchRow($this->db->query("SELECT tablename FROM pg_catalog.pg_tables WHERE tablename = '{$prefix}access';", Typecho_Db::READ));
|
||||
}
|
||||
if ($checkTableV1) {
|
||||
$resp['v1'] = $this->db->fetchRow($this->db->select('COUNT(1) AS cnt')->from('table.access'))['cnt'];
|
||||
$resp['total'] += $resp['v1'];
|
||||
}
|
||||
|
||||
// 统计 v2 版本数据
|
||||
if ($this->db->fetchRow($this->db->query("SHOW TABLES LIKE '{$prefix}access_log';", Typecho_Db::READ))) {
|
||||
if (strpos($adapterName, 'Mysql') !== false || strpos($adapterName, 'SQLite') !== false) {
|
||||
$checkTableV2 = $this->db->fetchRow($this->db->query("SHOW TABLES LIKE '{$prefix}access_log';", Typecho_Db::READ));
|
||||
} else if (strpos($adapterName, 'Pgsql') !== false) {
|
||||
$checkTableV2 = $this->db->fetchRow($this->db->query("SELECT tablename FROM pg_catalog.pg_tables WHERE tablename = '{$prefix}access_log';", Typecho_Db::READ));
|
||||
}
|
||||
if ($checkTableV2) {
|
||||
$resp['v2'] = intval($this->db->fetchRow($this->db->select('COUNT(1) AS cnt')->from('table.access_log'))['cnt']);
|
||||
$resp['total'] += $resp['v2'];
|
||||
}
|
||||
|
||||
return $resp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 迁移旧版本数据,单次1000条
|
||||
@@ -78,8 +103,13 @@ class Access_Migration {
|
||||
$resp = [ 'count' => 0, 'remain' => 0 ];
|
||||
$step = 1000;
|
||||
$prefix = $this->db->getPrefix();
|
||||
$adapterName = $this->db->getAdapterName();
|
||||
|
||||
$showTableQuery = strpos($adapterName, 'Pgsql') !== false ?
|
||||
"SELECT * FROM information_schema.tables WHERE table_name LIKE '{$prefix}access';" :
|
||||
"SHOW TABLES LIKE '{$prefix}access';";
|
||||
// 迁移 v1 版本数据
|
||||
if ($this->db->fetchRow($this->db->query("SHOW TABLES LIKE '{$prefix}access';", Typecho_Db::READ))) {
|
||||
if ($this->db->fetchRow($this->db->query($showTableQuery, Typecho_Db::READ))) {
|
||||
$remain = intval($this->db->fetchRow($this->db->select('COUNT(1) AS cnt')->from('table.access'))['cnt']);
|
||||
if ($resp['count'] === 0) {
|
||||
$rows = $this->db->fetchAll($this->db->select()->from('table.access')->limit($step));
|
||||
@@ -108,12 +138,19 @@ class Access_Migration {
|
||||
}
|
||||
}
|
||||
if ($remain === 0) {
|
||||
$this->db->query("DROP TABLE `{$prefix}access`;", Typecho_Db::WRITE);
|
||||
$dropTableQuery = strpos($adapterName, 'Pgsql') !== false ?
|
||||
"DROP TABLE IF EXISTS {$prefix}access;" :
|
||||
"DROP TABLE `{$prefix}access`;";
|
||||
$this->db->query($dropTableQuery, Typecho_Db::WRITE);
|
||||
}
|
||||
$resp['remain'] += $remain;
|
||||
}
|
||||
|
||||
$showTableQuery = strpos($adapterName, 'Pgsql') !== false ?
|
||||
"SELECT * FROM information_schema.tables WHERE table_name LIKE '{$prefix}access_log';" :
|
||||
"SHOW TABLES LIKE '{$prefix}access_log';";
|
||||
// 迁移 v2 版本数据
|
||||
if ($this->db->fetchRow($this->db->query("SHOW TABLES LIKE '{$prefix}access_log';", Typecho_Db::READ))) {
|
||||
if ($this->db->fetchRow($this->db->query($showTableQuery, Typecho_Db::READ))) {
|
||||
$remain = intval($this->db->fetchRow($this->db->select('COUNT(1) AS cnt')->from('table.access_log'))['cnt']);
|
||||
if ($resp['count'] === 0) {
|
||||
$rows = $this->db->fetchAll($this->db->select()->from('table.access_log')->limit($step));
|
||||
@@ -128,7 +165,10 @@ class Access_Migration {
|
||||
}
|
||||
}
|
||||
if ($remain === 0) {
|
||||
$this->db->query("DROP TABLE `{$prefix}access_log`;", Typecho_Db::WRITE);
|
||||
$dropTableQuery = strpos($adapterName, 'Pgsql') !== false ?
|
||||
"DROP TABLE IF EXISTS {$prefix}access_log;" :
|
||||
"DROP TABLE `{$prefix}access_log`;";
|
||||
$this->db->query($dropTableQuery, Typecho_Db::WRITE);
|
||||
}
|
||||
$resp['remain'] += $remain;
|
||||
}
|
||||
|
||||
@@ -100,23 +100,48 @@ class Access_Statistic {
|
||||
$resp = [];
|
||||
$ps = $this->request->get('ps', 10); # 页大小
|
||||
# 统计文章浏览比例
|
||||
foreach(
|
||||
$this->db->fetchAll(
|
||||
$this->db
|
||||
->select('content_id AS cid, table.contents.title AS title, COUNT(1) AS cnt')
|
||||
->from('table.access_logs')
|
||||
->join('table.contents', 'content_id = table.contents.cid', Typecho_Db::INNER_JOIN)
|
||||
->where('IFNULL(content_id, 0)')
|
||||
->group('content_id')
|
||||
->order('cnt', Typecho_Db::SORT_DESC)
|
||||
->limit($ps)
|
||||
) as $i
|
||||
) {
|
||||
$resp[] = [
|
||||
'cid' => intval($i['cid']),
|
||||
'title' => $i['title'],
|
||||
'count' => intval($i['cnt'])
|
||||
];
|
||||
|
||||
$adapterName = $this->db->getAdapterName();
|
||||
if (strpos($adapterName, 'Mysql') !== false || strpos($adapterName, 'SQLite') !== false) {
|
||||
foreach(
|
||||
$this->db->fetchAll(
|
||||
$this->db
|
||||
->select('content_id AS cid, table.contents.title AS title, COUNT(1) AS cnt')
|
||||
->from('table.access_logs')
|
||||
->join('table.contents', 'content_id = table.contents.cid', Typecho_Db::INNER_JOIN)
|
||||
->where('IFNULL(content_id, 0)')
|
||||
->group('content_id')
|
||||
->order('cnt', Typecho_Db::SORT_DESC)
|
||||
->limit($ps)
|
||||
) as $i
|
||||
) {
|
||||
$resp[] = [
|
||||
'cid' => intval($i['cid']),
|
||||
'title' => $i['title'],
|
||||
'count' => intval($i['cnt'])
|
||||
];
|
||||
}
|
||||
} else if (strpos($adapterName, 'Pgsql') !== false) {
|
||||
foreach(
|
||||
$this->db->fetchAll(
|
||||
$this->db
|
||||
->select('content_id AS cid, MAX(table.contents.title) AS title, COUNT(1) AS cnt')
|
||||
->from('table.access_logs')
|
||||
->join('table.contents', 'content_id = table.contents.cid', Typecho_Db::INNER_JOIN)
|
||||
->where('content_id <> ?', 0)
|
||||
->group('content_id')
|
||||
->order('cnt', Typecho_Db::SORT_DESC)
|
||||
->limit($ps)
|
||||
) as $i
|
||||
) {
|
||||
$resp[] = [
|
||||
'cid' => intval($i['cid']),
|
||||
'title' => $i['title'],
|
||||
'count' => intval($i['cnt'])
|
||||
];
|
||||
}
|
||||
} else {
|
||||
throw new Exception("Unsupported database adapter: " . $adapterName);
|
||||
}
|
||||
return $resp;
|
||||
}
|
||||
@@ -132,32 +157,61 @@ class Access_Statistic {
|
||||
$resp = [];
|
||||
$ps = $this->request->get('ps', 10); # 页大小
|
||||
$cate = $this->request->get('cate'); # 类型
|
||||
switch($cate) {
|
||||
case 'china':
|
||||
# 国内
|
||||
$fetchData = $this->db->fetchAll(
|
||||
$this->db
|
||||
->select("IF(ip_province = '中国', '国内未明确', ip_province) AS area, COUNT(1) AS cnt")
|
||||
->from('table.access_logs')
|
||||
->where("ip_country = '中国'")
|
||||
->group('area')
|
||||
->order('cnt', Typecho_Db::SORT_DESC)
|
||||
->limit($ps)
|
||||
);
|
||||
break;
|
||||
case 'inter':
|
||||
# 国际
|
||||
$fetchData = $this->db->fetchAll(
|
||||
$this->db
|
||||
->select("ip_country AS area, COUNT(1) AS cnt")
|
||||
->from('table.access_logs')
|
||||
->group('area')
|
||||
->order('cnt', Typecho_Db::SORT_DESC)
|
||||
->limit(15)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Bad Request', 400);
|
||||
$adapterName = $this->db->getAdapterName();
|
||||
if (strpos($adapterName, 'Mysql') !== false || strpos($adapterName, 'SQLite') !== false) {
|
||||
switch($cate) {
|
||||
case 'china':
|
||||
# 国内
|
||||
$fetchData = $this->db->fetchAll(
|
||||
$this->db
|
||||
->select("IF(ip_province = '中国', '国内未明确', ip_province) AS area, COUNT(1) AS cnt")
|
||||
->from('table.access_logs')
|
||||
->where("ip_country = '中国'")
|
||||
->group('area')
|
||||
->order('cnt', Typecho_Db::SORT_DESC)
|
||||
->limit($ps)
|
||||
);
|
||||
break;
|
||||
case 'inter':
|
||||
# 国际
|
||||
$fetchData = $this->db->fetchAll(
|
||||
$this->db
|
||||
->select("ip_country AS area, COUNT(1) AS cnt")
|
||||
->from('table.access_logs')
|
||||
->group('area')
|
||||
->order('cnt', Typecho_Db::SORT_DESC)
|
||||
->limit(15)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Bad Request', 400);
|
||||
}
|
||||
} else if (strpos($adapterName, 'Pgsql') !== false) {
|
||||
switch($cate) {
|
||||
case 'china':
|
||||
# 国内
|
||||
$sql = "SELECT CASE WHEN ip_province = '中国' THEN '国内未明确' ELSE ip_province END AS area, COUNT(1) AS cnt
|
||||
FROM {$this->db->getPrefix()}access_logs
|
||||
WHERE ip_country = '中国'
|
||||
GROUP BY area
|
||||
ORDER BY cnt DESC
|
||||
LIMIT {$ps}";
|
||||
$fetchData = $this->db->query($sql)->fetchAll();
|
||||
break;
|
||||
case 'inter':
|
||||
# 国际
|
||||
$fetchData = $this->db->fetchAll(
|
||||
$this->db
|
||||
->select("ip_country AS area, COUNT(1) AS cnt")
|
||||
->from('table.access_logs')
|
||||
->group('area')
|
||||
->order('cnt', Typecho_Db::SORT_DESC)
|
||||
->limit(15)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Bad Request', 400);
|
||||
}
|
||||
}
|
||||
foreach($fetchData as $row) {
|
||||
$resp[] = [
|
||||
|
||||
34
Plugin.php
34
Plugin.php
@@ -46,7 +46,12 @@ class Access_Plugin implements Typecho_Plugin_Interface
|
||||
$config = Typecho_Widget::widget('Widget_Options')->plugin('Access');
|
||||
if ($config->isDrop == 0) {
|
||||
$db = Typecho_Db::get();
|
||||
$db->query("DROP TABLE `{$db->getPrefix()}access_logs`", Typecho_Db::WRITE);
|
||||
$adapterName = $db->getAdapterName();
|
||||
if (strpos($adapterName, 'Mysql') !== false || strpos($adapterName, 'SQLite') !== false) {
|
||||
$db->query("DROP TABLE `{$db->getPrefix()}access_logs`", Typecho_Db::WRITE);
|
||||
} else if (strpos($adapterName, 'Pgsql') !== false) {
|
||||
$db->query("DROP TABLE \"{$db->getPrefix()}access_logs\"", Typecho_Db::WRITE);
|
||||
}
|
||||
}
|
||||
Helper::removePanel(1, self::$panel);
|
||||
Helper::removeRoute("access_track_gif");
|
||||
@@ -161,8 +166,33 @@ class Access_Plugin implements Typecho_Plugin_Interface
|
||||
} catch (Exception $e) {
|
||||
throw new Typecho_Plugin_Exception($e->getMessage());
|
||||
}
|
||||
} else if (strpos($adapterName, 'Pgsql') !== false) {
|
||||
$prefix = $db->getPrefix();
|
||||
$scripts = file_get_contents('usr/plugins/Access/sql/Pgsql.sql');
|
||||
$scripts = str_replace('typecho_', $prefix, $scripts);
|
||||
$scripts = explode(';', $scripts);
|
||||
try {
|
||||
$configLink = '<a href="' . Helper::options()->adminUrl . 'options-plugin.php?config=Access">' . _t('前往设置') . '</a>';
|
||||
# 初始化数据库如果不存在
|
||||
if (!$db->fetchRow($db->query("SELECT tablename FROM pg_tables WHERE schemaname='public' AND tablename='{$prefix}access_logs';", Typecho_Db::READ))) {
|
||||
foreach ($scripts as $script) {
|
||||
$script = trim($script);
|
||||
if ($script) {
|
||||
$db->query($script, Typecho_Db::WRITE);
|
||||
}
|
||||
}
|
||||
$msg = _t('成功创建数据表,插件启用成功,') . $configLink;
|
||||
} else {
|
||||
$msg = _t('数据表已存在,插件启用成功,') . $configLink;
|
||||
}
|
||||
return $msg;
|
||||
} catch (Typecho_Db_Exception $e) {
|
||||
throw new Typecho_Plugin_Exception(_t('数据表建立失败,插件启用失败,错误信息:%s。', $e->getMessage()));
|
||||
} catch (Exception $e) {
|
||||
throw new Typecho_Plugin_Exception($e->getMessage());
|
||||
}
|
||||
} else {
|
||||
throw new Typecho_Plugin_Exception(_t('你的适配器为%s,目前只支持Mysql和SQLite', $adapterName));
|
||||
throw new Typecho_Plugin_Exception(_t('你的适配器为%s,目前只支持Mysql、SQLite和Pgsql', $adapterName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
41
sql/Pgsql.sql
Normal file
41
sql/Pgsql.sql
Normal file
@@ -0,0 +1,41 @@
|
||||
CREATE TABLE typecho_access_logs (
|
||||
id serial PRIMARY KEY,
|
||||
ua varchar(512) DEFAULT '' ,
|
||||
browser_id varchar(32) DEFAULT '' ,
|
||||
browser_version varchar(32) DEFAULT '' ,
|
||||
os_id varchar(32) DEFAULT '' ,
|
||||
os_version varchar(32) DEFAULT '' ,
|
||||
url varchar(255) DEFAULT '' ,
|
||||
path varchar(255) DEFAULT '' ,
|
||||
query_string varchar(255) DEFAULT '' ,
|
||||
ip varchar(64) DEFAULT '' ,
|
||||
ip_country varchar(255) DEFAULT '' ,
|
||||
ip_province varchar(255) DEFAULT '' ,
|
||||
ip_city varchar(255) DEFAULT '' ,
|
||||
entrypoint varchar(255) DEFAULT '' ,
|
||||
entrypoint_domain varchar(100) DEFAULT '' ,
|
||||
referer varchar(255) DEFAULT '' ,
|
||||
referer_domain varchar(100) DEFAULT '' ,
|
||||
time int DEFAULT '0' ,
|
||||
content_id int DEFAULT NULL,
|
||||
meta_id int DEFAULT NULL,
|
||||
robot boolean DEFAULT '0' ,
|
||||
robot_id varchar(32) DEFAULT '' ,
|
||||
robot_version varchar(32) DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE INDEX idx_time ON typecho_access_logs (time);
|
||||
CREATE INDEX idx_path ON typecho_access_logs (path);
|
||||
CREATE INDEX idx_ua ON typecho_access_logs (ua);
|
||||
CREATE INDEX idx_ip_ua ON typecho_access_logs (ip, ua);
|
||||
CREATE INDEX idx_ip_province ON typecho_access_logs (ip_province);
|
||||
CREATE INDEX idx_robot ON typecho_access_logs (robot, time);
|
||||
CREATE INDEX idx_os_id ON typecho_access_logs (os_id);
|
||||
CREATE INDEX idx_robot_id ON typecho_access_logs (robot_id);
|
||||
CREATE INDEX idx_browser_id ON typecho_access_logs (browser_id);
|
||||
CREATE INDEX idx_content_id ON typecho_access_logs (content_id);
|
||||
CREATE INDEX idx_meta_id ON typecho_access_logs (meta_id);
|
||||
CREATE INDEX idx_entrypoint ON typecho_access_logs (entrypoint);
|
||||
CREATE INDEX idx_entrypoint_domain ON typecho_access_logs (entrypoint_domain);
|
||||
CREATE INDEX idx_referer ON typecho_access_logs (referer);
|
||||
CREATE INDEX idx_referer_domain ON typecho_access_logs (referer_domain);
|
||||
Reference in New Issue
Block a user