php实现API接口调用/页面访问计数器(PV)

欢迎访问我的博客:https://blog.luvying.com

代码已托管在Github:https://github.com/luvying/api-call-counter.git
写的还是比较简单易懂的,大佬的话可以绕道了233333~
本文不展示全部代码,仅记录一些关键点和自己当初想法而已,需要使用的直接到git拉取即可
废话有点多…大家不要介意…

1 前言

用php写了个接口,为了统计开放给外界的接口调用情况,就想写个脚本来统计下接口的调用情况,但是又不想让这个统计代码影响到原来接口,经过尝试,然后就有了这个脚本

2 统计访问数据

2.1 初步想法

既然是要统计php接口的调用情况,而不是页面(当然能统计接口,统计页面就更不是问题了~),那么就不能够采用js调用统计代码的方式了,毕竟不能要求接口调用方在他们的页面上加上js代码吧?于是就只能把统计代码放到php脚本内部,让php脚本去引用统计脚本的方式

2.2 尝试

一开始参考了网上其他童鞋的统计代码,大都的是简单的创建了个txt文件,加个字段如:total,然后每访问一次就+1,如果需要统计更详细的信息,如每月访问,昨日访问等还得重新加字符串进行拼接,考虑重置的时间等等,不够灵活,但是优点是实现简单,十几行代码搞定(如果需求增加代码量又得成倍增长=。=),用了几天了感觉不行,得换个方法

2.3 改进

无意中发现有SqlLite这个东西,之前只是有听过,但没有实际去用过,原本还觉得不就个统计嘛,还得跑个数据库,没必要。了解后才发现,原来SqlLite的数据库是基于文件的,也就是说本地一个.db的文件就是数据库了,并不需要跑一个Sql的服务,再加上php本身支持SqlLite,有了数据库,要什么字段加什么字段,想怎么统计就怎么统计,哇这么香的东西我难道不用?
So~上SqlLite,建库建表,库表用了ID ,IP ,TIME, REFERER四个字段,然后写了个pv.php,每次请求pv.php就往表里写数据就可以了,就是简单的INSERT

// 1.连接数据库
class MyDB extends SQLite3
{
    function __construct()
    {
        $this->open('db/pv.db');
    }
}
$db = new MyDB();
if(!$db){
    echo $db->lastErrorMsg();
} else {
    // 2.连接成功,获取当前时间、ip并存入数据库
    date_default_timezone_set("Asia/Shanghai");
    $now = time();
    //$ip = getIP();
    //$ref = getRef();
    // 注意严格按照EOF的格式,否则编译不通过
    $sql =<<<EOF
    INSERT INTO DETAIL (ID ,IP ,TIME, REFERER)
    VALUES (null, "$ip", $now, "$ref");
EOF;

    $ret = $db->exec($sql);
    if(!$ret){
        echo $db->lastErrorMsg();
    } else {
        //echo "Records created successfully\n";
    }
    $db->close(); 
    //echo "Opened database successfully\n";
}

然后被统计的接口通过include/requird把pv.php引入即可

// require 会生成致命错误(E_COMPILE_ERROR)并停止脚本
// include 只生成警告(E_WARNING),并且脚本会继续
include('pv.php');

3 新的问题

用了一两天,感觉还不错,然后给自己博客“大规模”地使用了,然后就发现了一个新的问题:当刷新博客首页的时候,由于同一个页面同时调用了某接口,而这个接口里面又include了pv.php,不知道什么原因,这个接口不正常了,出现了请求实际没被处理,而直接返回200的情况。(原本不管怎样都会返回点什么东西的,但现在就只有状态码200,其他啥也没有)
找了好久原因,后来把include去掉了,接口又正常了,突然间反应过来了,可能是因为出现了并发,并发时pv.php不能够同时对同一个数据库进行写入,而php的include是阻塞的,必须要等include的.php文件执行完毕后才能继续后面的内容,而一时间SqlLite就处理不过来了(毕竟人家数据库基于文件,总不能让它和有独立服务的数据库比吧=。=),就出问题了
没办法,那就只能找找看php有没有多线程啦,异步啦,非阻塞之类的调用方式了
最后找了了比较好实现的:popen

fclose(popen('php '.$path.' -a '.$ip.' -b '.$ref.' &', 'r'));

popen可以异步执行命令行,不需要等待结果,第一个参数是命令内容,第二个’r’指的是只读,这条方法其实就是让程序自己的系统上执行命令行
$path是pv.php在本系统的绝对路径,然后通过-a,-b给pv.php传入ip和来源
在pv.php中就可以这样获取到这两个值了(也有可以传url链接的方式,不过懒得改配置文件了…就直接用绝对路径的方式了)

$params = getopt('a:b:');
$ip = $params['a'];
$ref = $params['b'];

改造完毕后,再试下,嗯,这下没问题了,虽然并发的时候pv.php还是少算了一些请求,但是至少不会影响到被统计接口了。
再封装多一层,加一个runpv.php,然后就可以这样用了(仅展示关键代码)
被统计接口:

include('runpv.php');

runpv.php:

$ip = getIP();
$ref = getRef();
// include/require的方式会阻塞,导致并发时有些请求来不及写入pv.php中的数据库,无法正常重定向,所以用这种方式实现非阻塞
// 但是此时相关信息已经丢失了,需要手动获取ip和调用地址传入
fclose(popen('php pv.php -a '.$ip.' -b '.$ref.' &', 'r'));

pv.php:

<?php
// 从最上层php传过来的参数
$params = getopt('a:b:');
$ip = $params['a'];
$ref = $params['b'];

// 1.连接数据库
class MyDB extends SQLite3
{
    function __construct()
    {
        $this->open('db/pv.db');
    }
}
$db = new MyDB();
if(!$db){
    echo $db->lastErrorMsg();
} else {
    // 2.连接成功,获取当前时间、ip并存入数据库
    date_default_timezone_set("Asia/Shanghai");
    $now = time();
    //$ip = getIP();
    //$ref = getRef();
    // 注意严格按照EOF的格式,否则编译不通过
    $sql =<<<EOF
    INSERT INTO DETAIL (ID ,IP ,TIME, REFERER)
    VALUES (null, "$ip", $now, "$ref");
EOF;

    $ret = $db->exec($sql);
    if(!$ret){
        echo $db->lastErrorMsg();
    } else {
        //echo "Records created successfully\n";
    }
    $db->close(); 
    //echo "Opened database successfully\n";
}
?>

4 查询访问数据

既然有数据库了,那查询数据还不简单吗,想怎么统计怎么统计
我把它设计成了url注入sql语句的方式来执行,然后屏蔽了一些关键词,如insert、delete等敏感操作,还设置了一个key,作为密码,万一哪天自己想修改数据库,也不至于被限制住,有了这个key就可以执行insert、delete等敏感操作了
查询请求示例

yourdomain/query.php?query=select * from table              --> allow
yourdomain/query.php?query=delete from table where id=1     --> forbidden
yourdomain/query.php?query=delete from table where id=1&key=yoursecret    --> allow
yourdomain/query.php                                        --> error

查询效果如下
在这里插入图片描述
后续可改进的地方:
目前存数据库还是会因为并发而导致少统计,自己博客日常统计没什么所谓,需要精确的话就可能得用Redis了

整体代码这里不贴出来了,请转到Git:https://github.com/luvying/api-call-counter.git
欢迎访问我的博客:https://blog.luvying.com
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页