PHP版百度云加速API/SDK封装

<?php

/**
 * Author: anrip <https://www.arnip.com>
 * Update: 2020-1-8
 */

class Yunjiasu
{
    private $api_base = 'https://api.su.baidu.com/';

    private $access_key = '123456';
    private $secret_key = 'abcdef';

    public function zones($data)
    {
        $path = 'v31/yjs/zones';
        return $this->api_call('GET', $path, $data);
    }

    public function purge_cache($id, $data)
    {
        $path = 'v31/yjs/zones/' . $id . '/purge_cache';
        return $this->api_call('DELETE', $path, $data);
    }

    private function api_call($method, $path, $data = NULL)
    {
        $url = $this->api_base . $path;
        $header = $this->api_header($path, $data);
        $result = $this->http_repuest($method, $url, $header, $data);

        if (!empty($result['errors'])) {
            $error = array_pop($result['errors']);
            $error['error'] = 1;
            return $error;
        }

        if (!empty($result['result'])) {
            return $result['result'];
        }

        if (!empty($result['success'])) {
            return ['success' => 1];
        }

        return $result;
    }

    private function api_header($path, $data = NULL)
    {
        $params = [
            'X-Auth-Access-Key' => $this->access_key,
            'X-Auth-Nonce' => uniqid(),
            'X-Auth-Path-Info' => $path,
            'X-Auth-Signature-Method' => 'HMAC-SHA1',
            'X-Auth-Timestamp' => time(),
        ];

        if (is_array($data)) {
            ksort($data);
            $params = array_merge($params, $data);
        }

        $header = $signls = [];

        foreach ($params as $k => $v) {
            if (is_bool($v)) {
                $v = $v ? 'true' : 'false';
            }
            if (is_array($v)) {
                $v = str_replace('","', '", "', json_encode($v, JSON_UNESCAPED_SLASHES));
            }
            if (strpos($k, 'X-Auth') === 0) {
                $header[] = $k . ':' . $v;
            }
            if ($v !== '') {
                $signls[] = $k . '=' . $v;
            }
        }

        $header[] = 'X-Auth-Sign:' . base64_encode(
            hash_hmac('sha1', implode('&', $signls), $this->secret_key, true)
        );

        return $header;
    }

    private function http_repuest($method, $url, $header = NULL, $body = NULL)
    {
        $ch = curl_init();

        if ($method == 'GET' && $body) {
            $url .= '?' . http_build_query($body);
            $body = NULL;
        }

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);

        if ($header) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        }

        if ($body) {
            if (is_array($body)) {
                $body = json_encode($body);
            }
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        }

        $result = curl_exec($ch);
        $errno = curl_errno($ch);
        $error = curl_error($ch);

        curl_close($ch);

        if ($errno) {
            return ['error' => $errno, 'message' => $error];
        }

        return json_decode($result, true);
    }
}

#######################################################################

$su = new Yunjiasu();

$rs_zones = $su->zones(array(
    'name' => 'anrip.com'
));

if (isset($rs_zones['error'])) {
    exit(json_encode($rs_zones));
}

print_r($su->purge_cache($rs_zones['id'], array(
    'files' => array('https://www.anrip.com/test.jpg')
)));

手动删除Piwik历史日志(MySQL删除大量数据)

不知不觉间,Piwik数据的容量已经达到500GB,虽然并没有感觉到性能有什么影响,但也是时候考虑如何清理其最占空间的访问记录数据了。于是决定拿备份库演练一番:

首先想到的是使用后台的自动清理功能,但是发现其每次10000条的删除能力,对于此等量级的数据可谓是杯水车薪。最后只能翻看源码,得出最基本的两条删除语句。

DELETE FROM `wk_log_visit` WHERE visit_last_action_time < '2018-11-05 00:00:00';
DELETE FROM `wk_log_link_visit_action` WHERE server_time < '2018-11-05 00:00:00';

至于 wk_log_action 表一则也就10G多些,无伤大雅;二则这个表可以根据上面的两个表对其引用情况执行自动清理。

https://your.domain.com/index.php?action=executeDataPurge&date=today&format=html&idSite=1&module=PrivacyManager&period=day

至此,我们将遇到第二个问题:

如果要从InnoDB大表中删除许多行,则可能会超出表的锁定表大小InnoDB。也许删除500G的数据需要8个小时左右。

为了避免这个问题,或者只是为了最小化表保持锁定的时间,官方给出以下策略(根本不使用 DELETE):

  • 选择不要删除的行到与原始表具有相同结构的空表中

    INSERT INTO t_copy SELECT * FROM t WHERE ... ;
  • 使用RENAME TABLE以原子移动原始表的方式进行,并重新命名拷贝到原来的名称

    RENAME TABLE t TO t_old, t_copy TO t;
  • 删除原始表

    DROP TABLE t_old;

最终,我们优化查询如下

CREATE TABLE wk_log_visit_copy LIKE wk_log_visit;
INSERT INTO wk_log_visit_copy SELECT * FROM wk_log_visit WHERE visit_last_action_time >= '2018-11-05 00:00:00';
RENAME TABLE wk_log_visit TO wk_log_visit_old, wk_log_visit_copy TO wk_log_visit;

CREATE TABLE wk_log_link_visit_action_copy LIKE wk_log_link_visit_action;
INSERT INTO wk_log_link_visit_action_copy SELECT * FROM wk_log_link_visit_action WHERE server_time >= '2018-11-05 00:00:00';
RENAME TABLE wk_log_link_visit_action TO wk_log_link_visit_action_old, wk_log_link_visit_action_copy TO wk_log_link_visit_action;

DROP TABLE wk_log_visit_old, wk_log_link_visit_action_old;

解决iOS拍照上传图片旋转问题

html5应用,可以直接使用file功能调用相机拍照并上传,但是在iOS上有个奇葩的问题,图片不会自动翻转,上传到服务器上的图片可能是倒立的。

解决此问题有2种思路:

1.使用客户端JS检测图片信息,旋转后再上传。此方法实现需消耗客户端资源。

懒得整理js代码了,暂时按下不表。

2.使用服务端PHP检测图片信息,旋转后保存。此方法需要消耗少量的服务器资源。

function correct_image_orientation($target) {
    if(!function_exists('exif_read_data')) {
        return false;
    }
    $exif = exif_read_data($target);
    if($exif && isset($exif['Orientation']) && $exif['Orientation'] != 1) {
        switch ($exif['Orientation']) {
            case 3: $deg = 180; break;
            case 6: $deg = 270; break;
            case 8: $deg = 90; break;
            default: $deg = 0;
        }
        if($deg > 0) {
            $img = imagecreatefromjpeg($target);
            $img = imagerotate($img, $deg, 0);
            imagejpeg($img, $target, 95);
        }
    }
}

纯CSS3实现瀑布流效果

1、该效果使用CSS3的column-width实现,和js版的瀑布流不同:图片将纵向排列。

2、代码中使用了一小段JS,和瀑布流效果无关,主要用来动态插入元素,并实现模拟翻页。

<!Doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>瀑布流(html5,css3,column) - by http://www.anrip.com</title>
<style>
* {
    padding: 0;
    margin: 0;
}
#waterfall {
    margin: 15px 15px -15px 15px;
    position: relative;
    -webkit-column-width: 200px;
       -moz-column-width: 200px;
            column-width: 200px;
}
#waterfall div {
    width: 100%;
    background: #eee;
    margin-bottom: 15px;
    display: inline-block;
}
</style>
</head>
<body>
<div id="waterfall"></div>
<input type="button" onclick="more()" value="加载更多..."/>
<script type="text/javascript">
    var i = 0;
    function more() {
        var w = document.getElementById('waterfall');
        for(var n = i + 30; i < n; i++) {
            height = Math.floor( Math.random()*200 + 200 );
            w.innerHTML += '<div style="height:' + height + 'px;">'+i+'</div>';
        }
    }
    more();
</script>
</body>
</html>

js中escape、encodeURI、encodeURIComponent的区别

三个函数均可把字符串作为 URI 组件进行编码。

escape/unescape

该方法不会对 ASCII 字母和数字进行编码,也不会对下面这些 ASCII 标点符号进行编码: * @ – _ + . / 。其他所有的字符都会被转义序列替换。

ECMAScript v3 反对使用该方法,应用使用 encodeURI() 和 encodeURIComponent() 替代它。

encodeURI/decodeURI

该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: – _ . ! ~ * ‘ ( ) 。

该方法的目的是对 URI 进行完整的编码,因此对以下在 URI 中具有特殊含义的 ASCII 标点符号,encodeURI() 函数是不会进行转义的:;/?:@&=+$,#

如果 URI 组件中含有分隔符,比如 ? 和 #,则应当使用 encodeURIComponent() 方法分别对各组件进行编码。

encodeURIComponent/decodeURIComponent

该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: – _ . ! ~ * ‘ ( ) 。
其他字符(比如 :;/?:@&=+$,# 这些用于分隔 URI 组件的标点符号),都是由一个或多个十六进制的转义序列替换的。

请注意 encodeURIComponent() 函数 与 encodeURI() 函数的区别之处,前者假定它的参数是 URI 的一部分(比如协议、主机名、路径或查询字符串)。因此 encodeURIComponent() 函数将转义用于分隔 URI 各个部分的标点符号。

使用php实现mysql转sqlite语句

在写ArkPlus框架过程中一直使用的基于PDO驱动的MySQL,因为项目需求,要转一个SQLite版本,于是写了这个简单的转换函数,实现MySQL建表语句到SQLite的平滑转换,有需要的童鞋们可以拿去。

/**
 * mysql(ctreate_table)转sqlite语句
 * @author anrip[mail@anrip.com]
 * @version 2.1, 2013-01-18 17:02
 * @link http://www.anrip.com/?arkplus
 */
function ark_table_mysql2sqlite($sql) {
  $expr = array(
    '/`(\w+)`\s/' => '[$1] ',
    '/\s+UNSIGNED/i' => '',
    '/\s+[A-Z]*INT(\([0-9]+\))/i' => ' INTEGER$1',
    '/\s+INTEGER\(\d+\)(.+AUTO_INCREMENT)/i' => ' INTEGER$1',
    '/\s+AUTO_INCREMENT(?!=)/i' => ' PRIMARY KEY AUTOINCREMENT',
    '/\s+ENUM\([^)]+\)/i' => ' VARCHAR(255)',
    '/\s+ON\s+UPDATE\s+[^,]*/i' => ' ',
    '/\s+COMMENT\s+(["\']).+\1/iU' => ' ',
    '/[\r\n]+\s+PRIMARY\s+KEY\s+[^\r\n]+/i' => '',
    '/[\r\n]+\s+UNIQUE\s+KEY\s+[^\r\n]+/i' => '',
    '/[\r\n]+\s+KEY\s+[^\r\n]+/i' => '',
    '/,([\s\r\n])+\)/i' => '$1)',
    '/\s+ENGINE\s*=\s*\w+/i' => ' ',
    '/\s+CHARSET\s*=\s*\w+/i' => ' ',
    '/\s+AUTO_INCREMENT\s*=\s*\d+/i' => ' ',
    '/\s+DEFAULT\s+;/i' => ';',
    '/\)([\s\r\n])+;/i' => ');',
  );
  $sql = preg_replace(array_keys($expr), array_values($expr), $sql);
  return $sql === null ? '{table_mysql2sqlite_error}' : $sql;
}

使用php计算一些时间段

在审查项目代码的过程中,发现一处计算时间的地方很是难懂,于是耐着性子看了下去,看完那300多行代码,我终于明悟了它们只是为了获取几个时间段而存在,苦闷!

下面给出我的计算方法(或许还有更简洁的方法,烦请告知);免得有些人再写300行代码去实现~~

//年/月/日/星期/本月天数
list($year, $month, $day, $week, $days) = explode('/', date('Y/m/d/w/t'));
//计算今日时间范围
echo '<br/>本日开始 '.date('Y-m-d H:i:s w', strtotime($year.'-'.$month.'-'.$day));
echo '<br/>本日结束 '.date('Y-m-d H:i:s w', strtotime($year.'-'.$month.'-'.$day)+86399);
//计算本周时间范围
echo '<br/>本周开始 '.date('Y-m-d H:i:s w', strtotime($year.'-'.$month.'-'.$day)-86400*$week);
echo '<br/>本周结束 '.date('Y-m-d H:i:s w', strtotime($year.'-'.$month.'-'.$day)+86400*(7-$week)-1);
//计算本月时间范围
echo '<br/>本月开始 '.date('Y-m-d H:i:s w', strtotime($year.'-'.$month.'-1'));
echo '<br/>本月结束 '.date('Y-m-d H:i:s w', strtotime($year.'-'.$month.'-'.$days)+86399);

纯CSS带方向箭头消息提示框

这是一个纯CSS写的带方向箭头的消息提示框,支持左上、右上、左下、右下、纯左、纯右五种方向箭头,对应的Class分别为 ark-poptip-arrow-a/b/c/d/e/f ,效果图如下:

效果图

CSS部分

.ark-poptip{ position: absolute; color: #db7c22; z-index: 101; line-height: 1.5; zoom: 1; }
.ark-poptip-shadow{ background-color: rgba(229,169,107,0.15); FILTER: progid:DXImageTransform.Microsoft.Gradient(startColorstr=#26e5a96b,endColorstr=#26e5a96b); border-radius: 2px; padding: 2px; zoom: 1; _display: inline; }
.ark-poptip-container{ position: relative; background-color: #fffcef; border: 1px solid #ffbb76; border-radius: 2px; padding: 5px 22px 5px 10px; zoom: 1; _display: inline; }
.ark-poptip:after,.ark-poptip-shadow:after,.ark-poptip-container:after{ visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; }
.ark-poptip a.ark-poptip-close{ position: absolute; right: 3px; top: 3px; border: 1px solid #ffc891; text-decoration: none; border-radius: 3px; width: 12px; height: 12px; font-family: tahoma; color: #dd7e00; line-height: 10px; line-height: 10px\0; *line-height: 12px; text-align: center; font-size: 14px; background: #ffd7af; background: -webkit-gradient(linear,left top,left bottom,from(#fff0e1),to(#ffe7cd)); background: -moz-linear-gradient(top,#fff0e1,#ffe7cd); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFF0E1',endColorstr='#FFE7CD'); background: -o-linear-gradient(top,#fff0e1,#ffe7cd); background: linear-gradient(top,#fff0e1,#ffe7cd); overflow: hidden; }
.ark-poptip a.ark-poptip-close:hover{ border: 1px solid #ffb24c; text-decoration: none; color: #dd7e00; background: #ffd7af; background: -webkit-gradient(linear,left top,left bottom,from(#ffe5ca),to(#ffcc98)); background: -moz-linear-gradient(top,#ffe5ca,#ffcc98); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFE5CA',endColorstr='#FFCC98'); background: -o-linear-gradient(top,#ffe5ca,#ffcc98); background: linear-gradient(top,#ffe5ca,#ffcc98); }
.ark-poptip-arrow,.ark-poptip-arrow em,.ark-poptip-arrow span{ position: absolute; font-size: 14px; font-family: SimSun,Hiragino Sans GB; font-style: normal; line-height: 21px; z-index: 10; *zoom: 1; }
.ark-poptip-arrow em{ color: #ffbb76; }
.ark-poptip-arrow span{ color: #fffcef; top: 0; left: 0; }
.ark-poptip-arrow-a{ left: 14px; top: -10px; top: -9px\0; }
.ark-poptip-arrow-a em{ top: -1px; left: 0; }
.ark-poptip-arrow-b{ right: 28px; top: -10px; top: -9px\0; }
.ark-poptip-arrow-b em{ top: -1px; left: 0; }
.ark-poptip-arrow-c{ left: 14px; bottom: 10px; }
.ark-poptip-arrow-c em{ top: 1px; left: 0; }
.ark-poptip-arrow-d{ right: 28px; bottom: 10px; }
.ark-poptip-arrow-d em{ top: 1px; left: 0; }
.ark-poptip-arrow-e{ top: 6px; left: -6px; }
.ark-poptip-arrow-e em{ top: 0; left: -1px; }
.ark-poptip-arrow-f{ top: 6px; right: 7px; }
.ark-poptip-arrow-f em { top: 0; left: 1px; }
.ark-poptip-content{ float: none; display: inline; *zoom: 1; }
.ark-poptip-content:after{ visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; }
:root .ark-poptip-shadow{ FILTER: none\9; }

HTM部分

<div class="ark-poptip" style="top: 10px;">
    <div class="ark-poptip-shadow">
        <div class="ark-poptip-container">
            <div class="ark-poptip-arrow ark-poptip-arrow-a">
                *◆*<span>◆</span>
            </div>
            <div class="ark-poptip-content">欢迎访问技术写真 の 若海 & 尐岢 http://www.anrip.com</div>
        </div>
    </div>
</div>

源码下载
css-tips

PHP常用header状态代码

PHP的header状态代码,经常用,却总是忘记;今天整理了一下,备忘!

//200 正常状态
header('HTTP/1.1 200 OK');

//301 永久重定向,记得在后面要加重定向地址 Location:$url
header('HTTP/1.1 301 Moved Permanently');

//重定向,其实就是302 暂时重定向
header('Location: http://www.maiyoule.com/');

//设置页面304 没有修改
header('HTTP/1.1 304 Not Modified');

//显示登录框,
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="登录信息"');
echo '显示的信息!';

//403 禁止访问
header('HTTP/1.1 403 Forbidden');

//404 错误
header('HTTP/1.1 404 Not Found');

//500 服务器错误
header('HTTP/1.1 500 Internal Server Error');

//3秒后重定向指定地址(也就是刷新到新页面与 <meta http-equiv="refresh" content="10;http://www.maiyoule.com/ /> 相同)
header('Refresh: 3; url=http://www.maiyoule.com/');
echo '10后跳转到http://www.maiyoule.com';

//重写 X-Powered-By 值
header('X-Powered-By: PHP/5.3.0');
header('X-Powered-By: Brain/0.6b');

//设置上下文语言
header('Content-language: en');

//设置页面最后修改时间(多用于防缓存)
$time = time() - 60; //建议使用filetime函数来设置页面缓存时间
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $time).' GMT');

//设置内容长度
header('Content-Length: 39344');

//设置头文件类型,可以用于流文件或者文件下载
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="example.zip"'); 
header('Content-Transfer-Encoding: binary');
readfile('example.zip');//读取文件到客户端

//禁用页面缓存
header('Cache-Control: no-cache, no-store, max-age=0, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); 
header('Pragma: no-cache');

//设置页面头信息
header('Content-Type: text/html; charset=iso-8859-1');
header('Content-Type: text/html; charset=utf-8');
header('Content-Type: text/plain'); 
header('Content-Type: image/jpeg'); 
header('Content-Type: application/zip'); 
header('Content-Type: application/pdf'); 
header('Content-Type: audio/mpeg');
header('Content-Type: application/x-shockwave-flash'); 

正则表达式 – 模式修饰符(PHP)

i (PCRE_CASELESS)
如果设置了这个修饰符,模式中的字母会进行大小写不敏感匹配。

m (PCRE_MULTILINE)
默认情况下,PCRE 认为目标字符串是由单行字符组成的(然而实际上它可能会包含多行),"行首"元字符 ^ 仅匹配字符串的开始位置, 而"行末"元字符 $ 仅匹配字符串末尾,或者最后的换行符(除非设置了 D 修饰符)。这个行为和 perl 相同。当这个修饰符设置之后,“行首”和“行末”就会匹配目标字符串中任意换行符之前或之后,另外,还分别匹配目标字符串的最开始和最末尾位置。这等同于 perl 的 /m 修饰符。如果目标字符串中没有 \n 字符,或者模式中没有出现 ^$,设置这个修饰符不产生任何影响。

s (PCRE_DOTALL)
如果设置了这个修饰符,模式中的点号元字符匹配所有字符,包含换行符。如果没有这个修饰符,点号不匹配换行符。这个修饰符等同于 perl 中的 /s修饰符。 一个取反字符类比如 [^a] 总是匹配换行符,而不依赖于这个修饰符的设置。

x (PCRE_EXTENDED)
如果设置了这个修饰符,模式中的没有经过转义的或不在字符类中的空白数据字符总会被忽略, 并且位于一个未转义的字符类外部的#字符和下一个换行符之间的字符也被忽略。 这个修饰符等同于 perl 中的 /x 修饰符,使被编译模式中可以包含注释。
Note: 这仅用于数据字符。空白字符还是不能在模式的特殊字符序列中出现。

e (PREG_REPLACE_EVAL)
如果这个修饰符设置了, preg_replace() 在进行了对替换字符串的后向引用替换之后, 将替换后的字符串作为 php 代码评估执行(eval 函数方式),并使用执行结果作为实际参与替换的字符串。单引号、双引号、反斜线(\)和 NULL 字符在后向引用替换时会被用反斜线转义。
Note:preg_replace() 使用此修饰符。PHP5.5之后使用 preg_replace_callback() 代替此修饰符。

A (PCRE_ANCHORED)
如果设置了这个修饰符,模式被强制为"锚定"模式,也就是说约束匹配使其仅从目标字符串的开始位置搜索。这个效果同样可以使用适当的模式构造出来,并且这也是 perl 种实现这种模式的唯一途径。

D (PCRE_DOLLAR_ENDONLY)
如果这个修饰符被设置,模式中的元字符美元符号仅仅匹配目标字符串的末尾。如果这个修饰符没有设置,当字符串以一个换行符结尾时, 美元符号还会匹配该换行符(但不会匹配之前的任何换行符)。如果设置了修饰符m,这个修饰符被忽略. 在 perl 中没有与此修饰符等同的修饰符。

S
当一个模式需要多次使用的时候,为了得到匹配速度的提升,值得花费一些时间对其进行一些额外的分析。如果设置了这个修饰符,这个额外的分析就会执行。当前, 这种对一个模式的分析仅仅适用于非锚定模式的匹配(即没有单独的固定开始字符)。

U (PCRE_UNGREEDY)
这个修饰符逆转了量词的"贪婪"模式。 使量词默认为非贪婪的,通过量词后紧跟? 的方式可以使其成为贪婪的。这和 perl 是不兼容的。 它同样可以使用 模式内修饰符设置 (?U)进行设置, 或者在量词后以问号标记其非贪婪(比如.*?)。
Note: 在非贪婪模式,通常不能匹配超过 pcre.backtrack_limit 的字符。

X (PCRE_EXTRA)
这个修饰符打开了 PCRE 与 perl 不兼容的附件功能。模式中的任意反斜线后就 ingen 一个没有特殊含义的字符都会导致一个错误,以此保留这些字符以保证向后兼容性。 默认情况下,在 perl 中,反斜线紧跟一个没有特殊含义的字符被认为是该字符的原文。当前没有其他特性由这个修饰符控制。

J (PCRE_INFO_JCHANGED)
内部选项设置(?J)修改本地的PCRE_DUPNAMES选项。允许子组重名,(译注:只能通过内部选项设置,外部的 /J 设置会产生错误。)

u (PCRE_UTF8)
此修正符打开一个与 perl 不兼容的附加功能。 模式字符串被认为是utf-8的. 这个修饰符从 unix 版 php 4.1.0 或更高,win32版 php 4.2.3 开始可用。 php 4.3.5 开始检查模式的 utf-8 合法性。