FFMPEG获取视频播放时长

function video_info($file,$ffmpeg) {
    ob_start();
    passthru(sprintf($ffmpeg.' -i "%s" 2>&1', $file));
    $info = ob_get_contents();
    ob_end_clean();
  // 通过使用输出缓冲,获取到ffmpeg所有输出的内容。
   $ret = array();
    // Duration: 01:24:12.73, start: 0.000000, bitrate: 456 kb/s
    if (preg_match("/Duration: (.*?), start: (.*?), bitrate: (\d*) kb\/s/", $info, $match)) {
        $ret['duration'] = $match[1]; // 提取出播放时间
        $da = explode(':', $match[1]); 
        $ret['seconds'] = $da[0] * 3600 + $da[1] * 60 + $da[2]; // 转换为秒
        $ret['start'] = $match[2]; // 开始时间
        $ret['bitrate'] = $match[3]; // bitrate 码率 单位 kb
    }

    // Stream #0.1: Video: rv40, yuv420p, 512x384, 355 kb/s, 12.05 fps, 12 tbr, 1k tbn, 12 tbc
    if (preg_match("/Video: (.*?), (.*?), (.*?)[,\s]/", $info, $match)) {
        $ret['vcodec'] = $match[1]; // 编码格式
        $ret['vformat'] = $match[2]; // 视频格式 
        $ret['resolution'] = $match[3]; // 分辨率
        $a = explode('x', $match[3]);
        $ret['width'] = $a[0];
        $ret['height'] = $a[1];
    }

    // Stream #0.0: Audio: cook, 44100 Hz, stereo, s16, 96 kb/s
    if (preg_match("/Audio: (\w*), (\d*) Hz/", $info, $match)) {
        $ret['acodec'] = $match[1];       // 音频编码
        $ret['asamplerate'] = $match[2];  // 音频采样频率
    }

    if (isset($ret['seconds']) && isset($ret['start'])) {
        $ret['play_time'] = $ret['seconds'] + $ret['start']; // 实际播放时间
    }

    $ret['size'] = filesize($file); // 文件大小
    return $ret;
}
2017/3/7 posted in  PHP

Yii提交表单出现bad request错误解决方案

在表单提交按钮之前添加如下代码:

<?php echo \yii\helpers\Html::hiddenInput(Yii::$app->request->csrfParam, Yii::$app->request->getCsrfToken()) ?>
2017/3/1 posted in  Yii/Yii2

apache和nginx虚拟主机配置文件示例

apache虚拟主机配置文件示例

<VirtualHost *:80>
 ServerName  test.com
 DocumentRoot  "d:\www\test"  
 DirectoryIndex   index.php
 <Directory "d:\www\qlmall">
     Options +FollowSymLinks
     Order Allow,Deny
     allow from all
     AllowOverride All
 </Directory>
</VirtualHost>

nginx配置文件示例

server {
    server_name test.com;
    listen 80;
    root /data/webroot/test;

        location = /favicon.ico {
                log_not_found off;
                access_log off;
        }

        location = /robots.txt {
                allow all;
                log_not_found off;
                access_log off;
        }

        location / {
            index index.php index.html index.htm;
                # This is cool because no php is touched for static content.
                # include the "?$args" part so non-default permalinks doesn't break when using query string
                try_files $uri $uri/ /index.php?$args;
        }

        location ~ \.php$ {
                #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
                include fastcgi.conf;
                fastcgi_intercept_errors on;
            fastcgi_pass 127.0.0.1:9000;
        }

        location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
                expires max;
                log_not_found off;
        }

    error_log /var/log/nginx/test.com.error.log;
    access_log /var/log/nginx/test.com.access.log;
}
2017/2/28 posted in  Apache/Nginx

PHP分段下载文件

function download($file)
{
    $fp = @fopen($file, 'rb');
    $size   = filesize($file); // File size
    $length = $size;           // Content length
    $start  = 0;               // Start byte
    $end    = $size - 1;       // End byte
    header('Content-type: octet-stream');
    header("Accept-Ranges: 0-$length");
    if (isset($_SERVER['HTTP_RANGE'])) {
        $cStart = $start;
        $cEnd   = $end;
        list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
        if (strpos($range, ',') !== false) {
            header('HTTP/1.1 416 Requested Range Not Satisfiable');
            header("Content-Range: bytes $start-$end/$size");
            exit;
        }
        if ($range == '-') {
            $cStart = $size - substr($range, 1);
        }else{
            $range  = explode('-', $range);
            $cStart = $range[0];
            $cEnd   = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
        }
        $cEnd = ($cEnd > $end) ? $end : $cEnd;
        if ($cStart > $cEnd || $cStart > $size - 1 || $cEnd >= $size) {
            header('HTTP/1.1 416 Requested Range Not Satisfiable');
            header("Content-Range: bytes $start-$end/$size");
            exit;
        }
        $start  = $cStart;
        $end    = $cEnd;
        $length = $end - $start + 1;
        fseek($fp, $start);
        header('HTTP/1.1 206 Partial Content');
    }
    header("Content-Range: bytes $start-$end/$size");
    header("Content-Length: " . $length);
    $buffer = 1024 * 8;
    while(!feof($fp) && ($p = ftell($fp)) <= $end) {
        if ($p + $buffer > $end) {
            $buffer = $end - $p + 1;
        }
        set_time_limit(0);
        echo fread($fp, $buffer);
        flush();
    }
    fclose($fp);
    exit();
}
2017/2/23 posted in  PHP

Laravel的api接口包dingo的用法要点

准备环境(php7+laravel)

-> composer create-project --prefer-dist laravel/laravel laravel-dingo`

安装dingo

-> composer require dingo/api:1.0.x@dev

编辑app/config.php、在provider配置块里注册dingo包到laravel框架中

Laravel\Tinker\TinkerServiceProvider::class,
Dingo\Api\Provider\LaravelServiceProvider::class,
```php
在config目录生成dingo的配置配置文件
```shell
-> php artisan vendor:publish --provider="Dingo\Api\Provider\LaravelServiceProvider"
Read more   2017/2/21 posted in  LARAVEL

Linux系统常用管理命令

2017/2/21 posted in  OPS

面试题-使用IE的ActiveX对象过滤敏感词

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script type="text/javascript">
        // -------------- 全局变量,用来判断文本域中是否包含脏词,默认为false,即不包含脏词-------
        var isDirty = false;
     //读取本地dirtyword词库   
    function readFile(filename){
    /*函数编辑区*/
        try {
            var fso = new ActiveXObject("Scripting.FileSystemObject");
            var file = fso.OpenTextFile(filename, 1);
            var fileContent = file.ReadAll();
            file.Close();
            return fileContent.split(' ');
        } catch (e) {
            alert('请开启ActiveX'); 
        }
    }

    

        /*
        * 提交表单的主方法
        * 在提交表单的时候对内容进行过滤并在文本域显示过滤后的内容
        */
        function submitForm1() {
            /*函数编辑区*/
            var messageValue = document.getElementById('message').value;
            messageValue = filterWord(messageValue);
            if (isDirty) {
            alert(messageValue);
                document.getElementById('message').value = messageValue;
                if (confirm('你的留言中含有不恰当的词语,系统已自动为你修改,是否继续提交?')) {
                    return true;
                }
                return false;
            }
            return true;
        }
        /*
        * 对传进来的messageValue过滤并返回新内容
        
        */
        function filterWord(messageValue) {
            // 根据文本域的id获取文本域对象内容
            /*函数编辑区*/
            var dirtyWords = readFile('c:/ciku.txt');
            for (var i = 0; i < dirtyWords.length; i++) {
                isDirty = true;
                messageValue = filterOneWord(messageValue, dirtyWords[i]);
            }
            return messageValue;
        }
        /*
        * 这个函数用来过滤单个词语, 如果messageValue中含有oneDirtyWord, 则用"**"替换这个oneDirtyWord
        * messageValue --- 要过滤的语句

        */
        function filterOneWord(messageValue, oneDirtyWord) {
        /*函数编辑区*/
        var regexp = new RegExp(oneDirtyWord,  'gi');
        return messageValue.replace(regexp, '**');
        }
 </script>
</head>
<body>
    <form name="message_board" id="message_board" action="aaa.html">
        <textarea name="message" id="message" cols="50" rows="10">
"In a world that's changing really quickly, the biggest risk is not taking any risk."
—— Mark Zuckerberg
        </textarea><br/>
        <input type="button" value="提交留言" id="submitMessage" onclick="submitForm1()"/>
    </form>
</body>
</html>
2017/2/21 posted in  面试题 PHP

数据api接口的要点

因为最近面试反馈被问到了接口,所以在此总结接口涉及到的内容。

在工作中、接口涉及到编写app接口、因为编写app接口涉及到Java(安卓app)、Object C(IOSapp)与php后端的数据交换、所以编写app接口的核心数据交换格式是JSON。

编写app接口还有一点难点是保持已登录的用户的状态、因为app程序和浏览器不同的一点是、app默认是不支持cookie、所以默认app无法保持登录状态、所以常见的有以下三种解决方式

app在发送请求时、每次请求都模仿浏览器携带cookie,在cookie中包含session_id
app端登录成功后、php服务器端为登录的用户生成一个唯一的随机字符串、并返回给app、app在后面的请求里都携带上这个随机字符串、服务器通过这个随机字符串寻找对应的用户。
其它的基于唯一字符串的验证方式:比如JWT 
https://jwt.io/
除了app接口、还有不需要用户和用户状态保持的接口、比如支付宝的支付、没有状态的保持、只需要保证和接口通信的安全性。支付宝采用的是非对称加密和使用https协议进行数据传输、避免中间人攻击。

app接口设计要点
app接口设计包含以下4点

接口的作用
接口的地址
接口的参数、参数通常是post请求携带payload方式传递的json字符串、需要通过file_get_contents('php://input')进行接收、当然也可以模拟普通的html表单进行提交、在php中使用$_POST接收。
接口的返回值、通常会返回一个状态码表示接口是否成功执行
{
code:0,
message: "success",
data: { key1: value1, key2: value2, ... }
}
 其中的code表示状态码、状态码举例、其中为0的状态码表示成功、其它均表示错误:

0:成功
100:请求错误
101:缺少appKey
102:缺少签名
103:缺少参数
200:服务器出错
201:服务不可用
202:服务器正在重启
其中的message表示消息、成功、表示成功的消息、失败、返回失败的消息、这个消息一般用于直接显示给用户。

还有的接口简化了状态码的设计、只返回成功或者失败的状态、所以、状态由true和false表示,true表示接口执行成功、false表示错误。

还有的直接使用http状态码作为接口的状态码返回给客户端。

接口的安全性
我们的接口暴露在互联网上、所以存在有人恶意使用程序进行调用、程序可以以极快的速度请求我们的接口、造成我们的接口无法服务其它正常用户、所以针对这个问题、可以对一个ip的调用次数在单位时间内进行限制、比如一个每分钟只允许60次请求、超过60次即认为非法请求、后面的请求拒绝提供服务。接口请求的次数可以保存在redis、memcache等高速缓存中、从而不影响接口的性能。

同时为了禁止非授权用户调用我们的接口、可以要求需要调用接口的第三方在我们的平台上进行注册、注册成功后、我们分配给一个固定的唯一的随机字符串、要求用户请求时必须携带此参数、并在我们的接口里进行校验、不匹配直接不提供服务。比如有道接口,百度地图接口

Restful接口
restful接口的要点就是利用请求动词(get,post)表示出接口调用方的意图(获取、删除、还是保存数据)

GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。(客户端提供资源的信息)
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。
HEAD:获取资源的元数据。
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
其中最常用的是前面5个请求动词、分别对应增删改查。其余用法和普通api没有区别。

接口调试工具
chrome扩展postman:https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=zh-CN

抓包工具fiddler:http://www.telerik.com/fiddler

php请求第三方接口
可以使用file_get_contents()默认以get方式请求、但是在实际开发中、为了增强对请求过程的控制、一般使用curl进行发起请求。

使用curl发起post请求

function curl_post(\(data, \)url, \(headers = array())
{
    \)ch = curl_init();
    \(res= curl_setopt (\)ch, CURLOPT_URL, \(url);
    curl_setopt(\)ch, CURLOPT_SSL_VERIFYHOST, FALSE);
    curl_setopt(\(ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt(\)ch, CURLOPT_HEADER, 0);
    curl_setopt(\(ch, CURLOPT_POST, 1);
    curl_setopt(\)ch, CURLOPT_POSTFIELDS, \(data);
    curl_setopt(\)ch, CURLOPT_RETURNTRANSFER, 1);
    \(result = curl_exec(\)ch);
    curl_close(\(ch);
    if (\)result == NULL) {
        return 0;
    }
    return $result;
}
使用curl发起get请求

function curl_get(\(data, \)url, \(headers = array())
{
    \)ch = curl_init();
    \(res= curl_setopt (\)ch, CURLOPT_URL, \(url);
    curl_setopt(\)ch, CURLOPT_SSL_VERIFYHOST, FALSE);
    curl_setopt(\(ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt(\)ch, CURLOPT_HEADER, 0);
    curl_setopt(\(ch, CURLOPT_POSTFIELDS, \)data);
    curl_setopt(\(ch, CURLOPT_RETURNTRANSFER, 1);
    \)result = curl_exec(\(ch);
    curl_close(\)ch);
    if (\(result == NULL) {
        return 0;
    }
    return \)result;

 第三方接口一般返回json字符串、我们对json字符串使用json_decode进行处理成php的对象或数组、就可以使用php操作变量的方式对返回的字符串进行处理。

友盟数据接口的使用方式

授权接口

\(url = 'http://api.umeng.com/authorize' ;
\)data = 'email=你的邮箱&password=你的密码';
echo curl_post(\(data, \)url);

获取app基本数据的接口

\(url = "http://api.umeng.com/apps/base_data";
\)data = '';
\(headers = array(
    'Authorization: Basic 你的auth_token'
);
echo curl_get(\)data, \(url, \)headers);

参考资料

Keegan小钢

2017/2/21 posted in  面试题

安装composer的三种方式

2017/2/21 posted in  PHP

使用Laravel框架开发核心技术要点和开发技巧

2017/2/21 posted in  LARAVEL

面试题-求多叉树两节点的最短路径

第一种解法

function findParentNode($nodes, $id)
{
    foreach ($nodes as $node) {
        if (in_array($id, $node['childrens'])) {
            return $node;
        }
    }
    return false;
}

function minimum2($nodes, $from, $to)
{
    $fromToTop = array($from);
    while ($parentNode = findParentNode($nodes, $from)) {
        $fromToTop[] = $parentNode['id'];
        $from = $parentNode['id'];
    }
    $toToTop = array($to);
    while ($parentNode = findParentNode($nodes, $to)) {
        $fromToTop[] = $parentNode['id'];
        $to = $parentNode['id'];
    }
    $path = false;
    $fromPath = array();
    $toPath = array();
    $countf = count($fromToTop);
    $countt = count($toToTop);
    for ($i=0; $i < $countf; $i++) {
        $fromPath[] = $fromToTop[$i];
        for ($j=0; $j < $countt; $j++) { 
            $toPath[] = $toToTop[$j];
            if ($fromToTop[$i] == $toToTop[$j]) {
                // 共同点。
                // 去掉一个共同点
                array_pop($fromPath);
                $path = implode('=>', $fromPath) . '=>' . implode('=>', $toPath);
                break 2;
            }
        }
        $toPath = array();
    }
    return $path;
}

第二种解法

function findParentNode($nodes, $id)
{
    foreach ($nodes as $node) {
        if (in_array($id, $node['childrens'])) {
            return $node;
        }
    }
    return false;
}

function minimum($nodes, $from, $to, $path = '', $tryParent = true)
{
    static $finds = array();
    static $excludeIds = array();
    if ($from == $to) {
        $path = $path . $from;
        $finds[] = $path;
        return $finds;
    }
    $path = $path . $from . '=>';

    $startNode = $nodes[$from];
    // 在子节点中找
    foreach ($startNode['childrens'] as $childId) {
        if (in_array($childId, $excludeIds)) {
            continue;
        }
        $childNode = $nodes[$childId];
        minimum($nodes, $childNode['id'], $to, $path, false);
    }

    $parentNode = findParentNode($nodes, $startNode['id']);
    $excludeIds[] = $startNode['id'];
    if ($tryParent && $parentNode) {
        minimum($nodes, $parentNode['id'], $to, $path, true);
    }

    usort($finds, function($a, $b) {
        $strLenA = strlen($a);
        $strLenB = strlen($b);
        if ($strLenA == $strLenB) {
            return 0;
        }
        return ($strLenA < $strLenB) ? -1 : 1;
    });
    return isset($finds[0]) ? $finds[0] : false;
}

测试

$nodes=array(
    array(
        'id' => 'A',
        'childrens' => array('B', 'C', 'D'),
    ),
    array(
        'id' => 'B',
        'childrens' => array('E', 'F', 'G'),
    ),
    array(
        'id' => 'C',
        'childrens' => array(),
    ),
    array(
        'id' => 'D',
        'childrens' => array('H', 'I', 'J'),
    ),
    array(
        'id' => 'E',
        'childrens' => array(),
    ),
    array(
        'id' => 'F',
        'childrens' => array('K', 'L', 'N'),
    ),
    array(
        'id' => 'G',
        'childrens' => array(),
    ),
    array(
        'id' => 'H',
        'childrens' => array('O', 'P', 'Q'),
    ),
    array(
        'id' => 'I',
        'childrens' => array(),
    ),
    array(
        'id' => 'J',
        'childrens' => array(),
    ),
    array(
        'id' => 'K',
        'childrens' => array(),
    ),
    array(
        'id' => 'L',
        'childrens' => array(),
    ),
    array(
        'id' => 'N',
        'childrens' => array('R', 'S', 'T'),
    ),
    array(
        'id' => 'O',
        'childrens' => array(),
    ),
    array(
        'id' => 'P',
        'childrens' => array(),
    ),
    array(
        'id' => 'Q',
        'childrens' => array(),
    ),
    array(
        'id' => 'R',
        'childrens' => array(),
    ),
    array(
        'id' => 'S',
        'childrens' => array(),
    ),
    array(
        'id' => 'T',
        'childrens' => array(),
    ),
);
$idNodeMap = array();
foreach($nodes as $key => $value) {
    $idNodeMap[$value['id']] = $value;
}

var_dump(minimum($idNodeMap, 'T', 'A'));
var_dump(minimum2($idNodeMap, 'T', 'A'));
2017/2/14 posted in  面试题 PHP

Yii2学习资料

2017/2/10 posted in  Yii/Yii2