db = Typecho_Db::get(); $this->request = $request; } /** * 获取计数分析 * * @access private * @return ?array * @throws Exception */ private function count(): ?array { $resp = []; $type = $this->request->get('type', 'total'); # 统计类型 $dstTime = $this->request->get('time'); # 目标时间 switch($type) { # 总计数据 case 'total': $startTime = 0; $endTime = 0; break; # 按天统计 case 'day': if(!preg_match('/^\d{4}-\d{2}-\d{2}$/', $dstTime)) throw new Exception('Bad Request', 400); $startTime = strtotime(date("{$dstTime} 00:00:00")); $endTime = strtotime(date("{$dstTime} 23:59:59")); break; # 按月统计 case 'month': if(!preg_match('/^\d{4}-\d{2}$/', $dstTime)) throw new Exception('Bad Request', 400); [$year, $month] = explode('-', $dstTime); $monthDays = date('t', mktime(0, 0, 0, intval($month), 1, intval($year))); # 计算当月天数 $startTime = strtotime(date("{$dstTime}-01 00:00:00")); $endTime = strtotime(date("{$dstTime}-{$monthDays} 23:59:59")); break; default: throw new Exception('Bad Request', 400); } $resp['type'] = $type; $resp['dst'] = $dstTime; # ip数 $subQuery = $this->db ->select('DISTINCT ip') ->from('table.access_logs'); if ($endTime > 0) { $subQuery->where("time >= ? AND time <= ?", $startTime, $endTime); } if(method_exists($subQuery, 'prepare')) $subQuery = $subQuery->prepare($subQuery); $resp['count']['ip'] = intval($this->db->fetchRow( $this->db ->select('COUNT(1) AS cnt') ->from('(' . $subQuery . ') AS tmp') )['cnt']); # 访客数 $subQuery = $this->db ->select('DISTINCT ip, ua') ->from('table.access_logs'); if ($endTime > 0) { $subQuery->where("time >= ? AND time <= ?", $startTime, $endTime); } if(method_exists($subQuery, 'prepare')) $subQuery = $subQuery->prepare($subQuery); $resp['count']['uv'] = intval($this->db->fetchRow( $this->db ->select('COUNT(1) AS cnt') ->from('(' . $subQuery . ') AS tmp') )['cnt']); # 浏览数 $subQuery = $this->db ->select('COUNT(1) AS cnt') ->from('table.access_logs'); if ($endTime > 0) { $subQuery->where("time >= ? AND time <= ?", $startTime, $endTime); } $resp['count']['pv'] = intval($this->db->fetchRow($subQuery)['cnt']); return $resp; } /** * 获取文章访问统计 * * @access private * @return ?array * @throws Exception */ private function article(): ?array { $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']) ]; } return $resp; } /** * 获取访问地域统计 * * @access private * @return ?array * @throws Exception */ private function location(): ?array { $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); } foreach($fetchData as $row) { $resp[] = [ 'area' => $row['area'], 'count' => intval($row['cnt']) ]; } return $resp; } /** * 获取访问量图表数据 * * @access private * @return ?array * @throws Exception */ private function chart(): ?array { $resp = []; $type = $this->request->get('type'); # 统计类型 $dstTime = $this->request->get('time'); # 目标时间 switch($type) { # 按天统计 case 'day': if(!preg_match('/^\d{4}-\d{2}-\d{2}$/', $dstTime)) throw new Exception('Bad Request', 400); $loopStart = 0; $loopEnd = 23; break; # 按月统计 case 'month': if(!preg_match('/^\d{4}-\d{2}$/', $dstTime)) throw new Exception('Bad Request', 400); [$year, $month] = explode('-', $dstTime); $loopStart = 1; $loopEnd = date('t', mktime(0, 0, 0, intval($month), 1, intval($year))); # 计算当月天数 break; default: throw new Exception('Bad Request', 400); } $resp['type'] = $type; $resp['dst'] = $dstTime; foreach(range($loopStart, $loopEnd) as $i) { $chart = []; switch($type) { case 'day': $startTime = strtotime(date("{$dstTime} {$i}:00:00")); $endTime = strtotime(date("{$dstTime} {$i}:59:59")); break; case 'month': $startTime = strtotime(date("{$dstTime}-{$i} 00:00:00")); $endTime = strtotime(date("{$dstTime}-{$i} 23:59:59")); break; } $chart['time'] = $i; # ip数 $subQuery = $this->db ->select('DISTINCT ip') ->from('table.access_logs') ->where('time >= ? AND time <= ?', $startTime, $endTime); if(method_exists($subQuery, 'prepare')) $subQuery = $subQuery->prepare($subQuery); $chart['ip'] = intval($this->db->fetchRow( $this->db ->select('COUNT(1) AS count') ->from('(' . $subQuery . ') AS tmp') )['count']); # 访客数 $subQuery = $this->db ->select('DISTINCT ip,ua') ->from('table.access_logs') ->where('time >= ? AND time <= ?', $startTime, $endTime); if(method_exists($subQuery, 'prepare')) $subQuery = $subQuery->prepare($subQuery); $chart['uv'] = intval($this->db->fetchRow( $this->db ->select('COUNT(1) AS count') ->from('(' . $subQuery . ') AS tmp') )['count']); # 浏览数 $chart['pv'] = intval($this->db->fetchRow( $this->db ->select('COUNT(1) AS count') ->from('table.access_logs') ->where('time >= ? AND time <= ?', $startTime, $endTime) )['count']); $resp['chart'][] = $chart; } # 计算各平均值 $pvSum = 0; $uvSum = 0; $ipSum = 0; $cnt = count($resp['chart']); foreach($resp['chart'] as $i) { $pvSum += $i['pv']; $uvSum += $i['uv']; $ipSum += $i['ip']; } $resp['avg']['pv'] = round($pvSum / $cnt, 2); $resp['avg']['uv'] = round($uvSum / $cnt, 2); $resp['avg']['ip'] = round($ipSum / $cnt, 2); return $resp; } /** * 获取来源统计 * * @access private * @return ?array * @throws Exception */ private function referer(): ?array { $resp = []; $type = $this->request->get('type', 'url'); # 统计类型 $ps = $this->request->get('ps', 10); $pn = $this->request->get('pn', 1); switch($type) { case 'url': $fetchData = $this->db->fetchAll( $this->db ->select('DISTINCT entrypoint AS value, COUNT(1) as cnt') ->from('table.access_logs') ->where("entrypoint != ''") ->group('entrypoint') ->order('cnt', Typecho_Db::SORT_DESC) ->page($pn, $ps) ); break; case 'domain': $fetchData = $this->db->fetchAll($this->db ->select('DISTINCT entrypoint_domain AS value, COUNT(1) as cnt') ->from('table.access_logs') ->where("entrypoint_domain != ''") ->group('entrypoint_domain') ->order('cnt', Typecho_Db::SORT_DESC) ->page($pn, $ps) ); break; default: throw new Exception('Bad Request', 400); } foreach($fetchData as $i) { $resp[] = [ 'value' => ($type == 'url') ? urldecode($i['value']) : $i['value'], 'count' => $i['cnt'] ]; } return $resp; } /** * 业务调度入口 * * @access public * @param string rpcType 调用过程类型 * @return ?array * @throws Exception */ public function invoke(string $rpcType): ?array { if(!method_exists($this, $rpcType) || in_array($rpcType, ['__construct', 'invoke'])) throw new Exception('Bad Request', 400); return $this->$rpcType(); # 方法指针 } }