## 发布前台
首先,我们看一下点点的发布界面
![2015-06-14/557d43946f503](http://box.kancloud.cn/2015-06-14_557d43946f503.png)
基本上他的发布框都是左右结构,左边主要内容,右边辅助字段。
主要有3个组件:文本编辑器、标签、日期选择器。
### 文本编辑器
经过我的研究是ueditor。
![2015-06-14/557d44d0b4bf4](http://box.kancloud.cn/2015-06-14_557d44d0b4bf4.png)
看console,里编辑器按钮的图片资源目录。那么我们也用ueditor。
本人比较喜欢这个编辑器,因为他提供了下图我看中的三个功能:
![2015-06-14/557d45b3d76a9](http://box.kancloud.cn/2015-06-14_557d45b3d76a9.png)
当然这需要我们去整合到应用里。
这对于新手有点难度,幸好我找到了一个完美的轮子:[Ueditor-thinkphp](https://github.com/Nintendov/Ueditor-thinkphp)
![2015-06-14/557d471050fb6](http://box.kancloud.cn/2015-06-14_557d471050fb6.png)
我就下载了他的资源文件,将编辑器本身放入Public/static/ueditor里。
![2015-06-14/557d47eab7911](http://box.kancloud.cn/2015-06-14_557d47eab7911.png)
公共项目配置里放上他的配置。
然后UploadController里写上他的 ueditor方法:
![2015-06-14/557d4820f3702](http://box.kancloud.cn/2015-06-14_557d4820f3702.png)
然后文本编辑器的模板部分,因为不同文章发布的类型要多次使用到编辑器,我就提取成了模板:
![2015-06-14/557d48ca92ec2](http://box.kancloud.cn/2015-06-14_557d48ca92ec2.png)
base/editor.html
表单名和表单值读取了模板引入include的的属性`form_name`和`form_value`。
并且精简了按钮做到和点点的一致。
至于编辑器里的上传,后面“文件上传”会讲。
### 标签
找了许多标签库插件,最后还是发现之前自己OneBlog里的 [jquery.tagsinput](https://github.com/xoxco/jQuery-Tags-Input)
好用一些。
bower安装,引入资源,表单隐藏提交,js代码区里
~~~
<div class="form-group">
<textarea name="tags" id="tags" placeholder="添加标签,用逗号或回车分割" class="form-control hide-data" style="height:90px"></textarea>
</div>
~~~
~~~
//标签
$('#tags').tagsInput({
'height':'90px',
'width':'182px',
'defaultText': '添加标签'
});
~~~
最后美化一下,就搞定了:
![2015-06-14/557d4bc742035](http://box.kancloud.cn/2015-06-14_557d4bc742035.png)
输入标签词后回车添加上标签。
### 日期选择器
点点可以定时发布,发布时间下拉里选择定时发布:
![2015-06-14/557d4cac10360](http://box.kancloud.cn/2015-06-14_557d4cac10360.png)
会显示一个日期+小时+分钟的 输入控件:
![2015-06-14/557d4cde02291](http://box.kancloud.cn/2015-06-14_557d4cde02291.png)
由于bootstrap默认没有日期时间选择器,我就去找了一个 [bootstrap-datetimepicker](http://www.malot.fr/bootstrap-datetimepicker/)
引入资源后,
先是默认隐藏的表单
~~~
<div class="form-group input-group hidden">
<input type="datetime" name="deadline" class="form-control hide-data" id="deadline">
<span class="input-group-addon glyphicon glyphicon-calendar" aria-hidden="true" style="top:0;"></span>
</div>
~~~
然后js代码:
~~~
//发布时间切换
$('#create_time_choose').on('change', function(){
var choosen = this.value;
if('choose' == choosen)
$('#deadline').parent().removeClass('hidden');
else
$('#deadline').parent().addClass('hidden');
});
//定时使用支持 日期 小时 分钟选择
$('#deadline').datetimepicker({
format: 'yyyy-mm-dd hh:ii',
autoclose: true,
language: 'zh-CN'
});
~~~
这样,实现了和点点差不多的定时发布字段控件:
![2015-06-14/557d4e9d56263](http://box.kancloud.cn/2015-06-14_557d4e9d56263.png)
![2015-06-14/557d4eb5a0f1a](http://box.kancloud.cn/2015-06-14_557d4eb5a0f1a.png)
## 整体代码
### 前台模板目录
![2015-06-14/557d50902eaf9](http://box.kancloud.cn/2015-06-14_557d50902eaf9.png)
前台主要分为 列表页、详情页、发布页及用户个人中心页。
列表页主要就是Index目录下的,base 及其各种子详情页。
红框中的editor是我尝试之前ueditor的demo写的demo页。
然后详情页是Detail目录下的。
发布页是Post目录下的base 及其发布类型对应的子模板继承页。
### 模板继承
观察了Bootstrap的博客案列,整体来说左右结构,发布页面变的是左边。
所以我写了个base.html 供继承使用:
~~~
<html>
<head>
<meta charset="UTF-8">
<title>Freelog - 自由的轻博客</title>
<script src="__BOWER__/jquery/dist/jquery.min.js"></script>
<script src="__BOWER__/bootstrap/dist/js/bootstrap.min.js"></script>
<link href="__BOWER__/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<block name="style"></block>
<link rel="stylesheet" href="__CSS__/blog.css">
<script type="text/javascript">
var is_login = '{:is_login()}';
</script>
<script type="text/javascript" src="__JS__/common.js"></script>
<script type="text/javascript">
// var url = window.location.pathname + window.location.search;
// url = url.replace(/(\/(p)\/\d+)|(&p=\d+)|(\/(id)\/\d+)|(&id=\d+)|(\/(group)\/\d+)|(&group=\d+)/, "");
var url = '__SELF__';
var no_pic = '__PUBLIC__/images/no-cover.png';
(function(){
var ThinkPHP = window.Think = {
"ROOT" : "__ROOT__", //当前网站地址
"APP" : "__APP__", //当前项目地址
"PUBLIC" : "__PUBLIC__", //项目公共目录地址
"DEEP" : "{:C('URL_PATHINFO_DEPR')}", //PATHINFO分割符
"MODEL" : ["{:C('URL_MODEL')}", "{:C('URL_CASE_INSENSITIVE')}", "{:C('URL_HTML_SUFFIX')}"],
"VAR" : ["{:C('VAR_MODULE')}", "{:C('VAR_CONTROLLER')}", "{:C('VAR_ACTION')}"]
}
})();
$(function(){
//单页高亮
$('.navbar-nav li').removeClass('active');
$('a.blog-nav-item[href="'+url+'"]').parent().addClass('active');
})
</script>
<link rel="stylesheet" href="__BOWER__/smalot-bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css">
<script type="text/javascript" src="__BOWER__/smalot-bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js"></script>
<script type="text/javascript" src="__BOWER__/smalot-bootstrap-datetimepicker/js/locales/bootstrap-datetimepicker.zh-CN.js"></script>
<link rel="stylesheet" href="__BOWER__/jqnotifybar/css/jquery.notifyBar.css">
<script type="text/javascript" src="__BOWER__/jqnotifybar/jquery.notifyBar.js"></script>
<script src="__BOWER__/jquery.tagsinput/jquery.tagsinput.js"></script>
<link rel="stylesheet" href="__BOWER__/jquery.tagsinput/jquery.tagsinput.css" />
<script type="text/javascript" src="__STATIC__/ueditor/ueditor.config.js"></script>
<!-- 编辑器源码文件 -->
<script type="text/javascript" src="__STATIC__/ueditor/ueditor.all.js"></script>
<!-- 实例化编辑器 -->
<!-- Your site ends -->
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top blog-masthead">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="javascript:;">Freelog</a>
</div>
<div class="blog-nav collapse navbar-collapse" id="example-navbar-collapse">
<ul class="nav navbar-nav">
<li><a class="blog-nav-item" href="{:U('/')}">首页</a></li>
<?php if (is_login()): ?>
<li><a class="blog-nav-item" href="{:U('/mine/')}">我的文章</a></li>
<?php endif ?>
<li>
<form class="navbar-form navbar-right" id="search" method="GET" action="/Search" role="search">
<div class="form-group input-group">
<input type="text" name="kw" class="form-control" placeholder="输入关键字搜索" value="{$kw|default=""}">
<span class="input-group-btn">
<button class="btn btn-default">
<span class="glyphicon glyphicon-search f20" aria-hidden="true"></span>
</button>
</span>
</div>
</form>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="javascript:;" role="button" aria-expanded="false"><if condition="is_login()">{:session('user.nickname')}</if></a></li>
<if condition="is_login()">
<li><a class="blog-nav-item" href="{:U('/user/profile')}">个人信息</a></li>
<li><a class="blog-nav-item" href="{:U('/user/logout')}" class="ajax-get">退出</a></li>
<else/>
<li><a class="blog-nav-item" href="{:U('/user/login')}">登录</a></li>
<li><a class="blog-nav-item" href="{:U('/user/reg')}">注册</a></li>
</if>
</ul>
</div>
</div>
</div>
<div class="container" id="main">
<block name="header"></block>
<div class="row">
<form class="post">
<div class="col-sm-8 blog-main">
<block name="main"></block>
</div><!-- /.blog-main -->
<div class="col-sm-3 col-sm-offset-1 blog-sidebar">
<block name="sidebar">
<include file="Index/post_btn" />
<div class="sidebar-module sidebar-module-inset">
<h4>选项</h4>
<div class="form-group">
<label for="title">发布时间</label>
<select name="create_time_choose" class="form-control hide-data" id="create_time_choose">
<option value="now">现在发布</option>
<option value="choose">定时发布</option>
</select>
</div>
<div class="form-group input-group hidden">
<input type="datetime" name="deadline" class="form-control hide-data" id="deadline">
<span class="input-group-addon glyphicon glyphicon-calendar" aria-hidden="true" style="top:0;"></span>
</div>
<div class="form-group">
<textarea name="tags" id="tags" placeholder="添加标签,用逗号或回车分割" class="form-control hide-data" style="height:90px"></textarea>
</div>
</div>
<script type="text/javascript">
$(function(){
//发布时间切换
$('#create_time_choose').on('change', function(){
var choosen = this.value;
if('choose' == choosen)
$('#deadline').parent().removeClass('hidden');
else
$('#deadline').parent().addClass('hidden');
});
//定时使用支持 日期 小时 分钟选择
$('#deadline').datetimepicker({
format: 'yyyy-mm-dd hh:ii',
autoclose: true,
language: 'zh-CN'
});
//标签
$('#tags').tagsInput({
'height':'90px',
'width':'182px',
'defaultText': '添加标签'
});
})
</script>
</block>
</div><!-- /.blog-sidebar -->
</form>
</div><!-- /.row -->
</div><!-- /.container -->
<footer class="blog-footer">
<p>Blog template built for <a href="http://getbootstrap.com">Bootstrap</a> by <a href="https://twitter.com/mdo">@mdo</a>.Modified by <a href="http://weibo.com/u/1342658313">@黑白世界4648</a></p>
<p>© {:date('Y', time())} Freelog. 由 Thinkphp 强力驱动.</p>
<p>
<a href="#">Back to top</a>
</p>
</footer>
<block name="script"></block>
</body>
</html>
~~~
### 模板包含
由于模板继承里的每个block只是结构上的替换,一些组件级的html代码和js。还是封装成小html片段模板,用include 标签复用才好。
像文字类发布页、图片发布页和其他发布页,文本编辑器都有,只不过文字类发布页的内容字段,就和其他类型发布页的描述字段一样是文本编辑器,只不过字段名变了。
在text.html 中:
`<include file="Post/editor" form_name="content" form_value="" height="286" />`
一句话就完成了文本编辑器的引入。父模板里引入编辑器资源,因为都要用到,封装js 部分就好了。用include属性传参。是不是和最近热门概念web component 类似,TP早就有了。
### 整体代码
文字页发布Post/text.html的代码如下:
~~~
<extend name="Post/base" />
<block name="header">
<div class="blog-header">
<h1 class="blog-title">发布文字</h1>
</div>
</block>
<block name="main">
<div class="blog-post" id="text">
<div class="form-group">
<label for="title">标题 <span class="optional">(可不填)</span></label>
<input type="text" class="form-control hide-data" id="title" name="title">
</div>
<div class="form-group">
<label for="content">内容</label>
<include file="Post/editor" form_name="content" form_value="" height="286" />
</div>
<div class="form-group">
<input type="hidden" name="type" value="{$type}" class="hide-data">
<input type="hidden" name="member_id" value="{:is_login()}" class="hide-data">
<a class="btn btn-default pull-left" href="{:U('/')}">取消</a>
<button type="button" class="btn btn-primary pull-right ajax-post no-refresh" hide-data="true" target-form="post" href="/api.php/post">保存</button>
</div>
</div><!-- /.blog-post -->
<script type="text/javascript">
$(function(){
})
</script>
</block>
<block name="script">
</block>
~~~
这时候,整个文字类发布文章页面的效果就出来了:
![2015-06-14/557d97a6e0a2e](http://box.kancloud.cn/2015-06-14_557d97a6e0a2e.png)
怎么样和点点很像吧。
### 等等,还有ajax
在页面的父模板里,我们只看到一个form标签,没有action,也没有type,却你那提交数据,表单怎么提交的呢?
别急,我们看看Public里的common.js里有这么一段:
~~~
//ajax post submit请求
$('.ajax-post').on('click', function(){
var target,query,form;
var target_form = $(this).attr('target-form');
var that = this;
var nead_confirm = false;
if( ($(this).attr('type')=='submit') || (target = $(this).attr('href')) || (target = $(this).attr('url')) ){
form = $('.'+target_form);
if(form.lenght < 1 )
alert('表单选择不正确');
if ($(this).attr('hide-data') === 'true'){//无数据时也可以使用的功能
form = $('.hide-data');
query = form.serialize();
}else if (form.get(0)==undefined){
return false;
}else if ( form.get(0).nodeName=='FORM' ){
if ( $(this).hasClass('confirm') ) {
if(!confirm('确认要执行该操作吗?')){
return false;
}
}
if($(this).attr('url') !== undefined){
target = $(this).attr('url');
}else{
target = form.get(0).action;
}
query = form.serialize();
}else if( form.get(0).nodeName=='INPUT' || form.get(0).nodeName=='SELECT' || form.get(0).nodeName=='TEXTAREA') {
form.each(function(k,v){
if(v.type=='checkbox' && v.checked==true){
nead_confirm = true;
}
})
if ( nead_confirm && $(this).hasClass('confirm') ) {
if(!confirm('确认要执行该操作吗?')){
return false;
}
}
query = form.serialize();
}else{
if ( $(this).hasClass('confirm') ) {
if(!confirm('确认要执行该操作吗?')){
return false;
}
}
query = form.find('input,select,textarea').serialize();
}
$(that).addClass('disabled').attr('autocomplete','off').prop('disabled',true);
$.post(target,query).success(function(data){
if (data.code >= 200 && data.code < 400) {
console.log(data);
console.log('success');
if (data.url) {
notify(data.info + ' 页面即将自动跳转~','success');
}else{
notify(data.info ,'success');
}
setTimeout(function(){
$(that).removeClass('disabled').prop('disabled',false);
if(data.url)
location.href = data.url;
},1500);
}else{
notify(data.info, 'error');
setTimeout(function(){
$(that).removeClass('disabled').prop('disabled',false);
if (data.url) {
location.href = data.url;
}
}, 1500);
}
}).error(function(jqXHR, textStatus, errorThrown) {
$(that).removeClass('disabled').prop('disabled',false);
var code = jqXHR.status;
if(404 == code ){
notify(data.info ,'error');
}else if(412 == code){
notify('记录已经被删除了' ,'error');
}
});
}
return false;
});
~~~
再看 提交按钮的代码:
`<button type="button" class="btn btn-primary pull-right ajax-post no-refresh" hide-data="true" target-form="post" href="/api.php/post">保存</button>`
有这ajax-post类名和 target-form属性。而父模板里有一个post类名的form标签。
原来用了ajax提交序列化form了。地址是href属性。
后面代码了也些了ajax-put和ajax-delete。
这段代码是我从OneThink里借过来的,修改了部分,添加了自己的提示信息代码,以及响应码判断,以前的都是200。
成功以后的显示是这样的:
![2015-06-14/557d99a656d0e](http://box.kancloud.cn/2015-06-14_557d99a656d0e.png)
## 模型
api部分只是提供了新增数据,具体文章数据的添加有什么处理,还得看模型代码。
我们看Common下的PostModel:
~~~
<?php
namespace Common\Model;
Use Think\Model;
class PostModel extends Model{
protected $_auto = array (
array('status','1'), // 新增的时候把status字段设置为1
array('create_at','datetime', self::MODEL_INSERT, 'function'), // 对create_at字段在更新的时候写入当前时间戳
array('deadline','getDeadline', self::MODEL_INSERT, 'callback'), // 对name字段在新增的时候回调getName方法
array('update_at','datetime', self::MODEL_BOTH, 'function'), // 对update_at字段在更新的时候写入当前时间戳
array('content', 'encode', self::MODEL_BOTH, 'callback'), //对内容字段 根据type选择进行json_encode
array('view', 0, self::MODEL_INSERT),
array('comment', 0, self::MODEL_INSERT),
);
protected $_validate = array(
);
//截止日期处理
public function getDeadline($deadline){
if('now' == I('request.create_time_choose')){
return datetime('now');
}else{
return datetime($deadline);
}
}
//内容字段加密
public function encode($content){
if(in_array(I('post.type'), array('picture', 'music', 'video')))
return json_encode($content);
else
return $content;
}
protected function _after_find(&$result, $options) {
if(in_array($result['type'], array('picture', 'music', 'video'))){
$result['content'] = json_decode($result['content'], 1);
}
}
protected function _after_select(&$result, $options){
foreach($result as &$record){
$this->_after_find($record, $options);
}
if($result){
$member_ids = array_column($result, 'member_id');
$authors = M('Member')->where(array('id'=>array('in', $member_ids)))->getField('id,nickname');
foreach ($result as &$record) {
if(isset($authors[$record['member_id']]))
$record['author'] = $authors[$record['member_id']];
else
$record['author'] = '系统发布';
}
}
}
// 新增数据前的回调方法
protected function _after_insert($data, $options) {
$this->_after_update($data, $options);
}
// 更新成功后的回调方法
protected function _after_update($data, $options) {
//处理标签
$tags = $data['tags'];
$tags = explode(',', $tags);
if($tags === FALSE){
$tags = array();
}
$tagsModel = M('Tags');
if(!empty($_POST['id'])){
//更新时候先将原有标签统计减去1
$orignal_tags = $this->getFieldById('tags', $data['id']);
if($orignal_tags)
$tagsModel->where(array('title'=>array('in', $orignal_tags)))->setDec('count');
}
foreach ($tags as $value) {
if($tagsModel->where("title = '{$value}'")->find())
$tagsModel->where("title = '{$value}'")->setInc('count');
else
if($value)
$tagsModel->add(array('title'=>$value, 'count'=>1));
}
}
}
~~~
模型做了几件事:
1.完成字段
status 插入时为1;create_at插入写入日期时间文本;deadline在为选择时,是当前时间,选择了定时时是传递过来的文本;更新时间新增和更新时都更新为当前时间文本;content内容,在某些类型里json序列化,那时文本编辑器存的值到description里,content是数组表单;view和comment 查看数和评论数默认为0。
2.后置查询时反序列化和字段值显示文本处理
反序列化一看就知道,显示文本的是用户名处理。存的member_id 显示时候显示昵称。
~~~
if($result){
$member_ids = array_column($result, 'member_id');
$authors = M('Member')->where(array('id'=>array('in', $member_ids)))->getField('id,nickname');
foreach ($result as &$record) {
if(isset($authors[$record['member_id']]))
$record['author'] = $authors[$record['member_id']];
else
$record['author'] = '系统发布';
}
}
~~~
这里用了array_column获取查出来的`member_id` 数组集合,然后查询该集合赢的用户数据,并以`member_id`为键名。然后拼接作者字段显示发布者昵称。
3.后置更新时处理标签表的统计
~~~
// 更新成功后的回调方法
protected function _after_update($data, $options) {
//处理标签
$tags = $data['tags'];
$tags = explode(',', $tags);
if($tags === FALSE){
$tags = array();
}
$tagsModel = M('Tags');
if(!empty($_POST['id'])){
//更新时候先将原有标签统计减去1
$orignal_tags = $this->getFieldById('tags', $data['id']);
if($orignal_tags)
$tagsModel->where(array('title'=>array('in', $orignal_tags)))->setDec('count');
}
foreach ($tags as $value) {
if($tagsModel->where("title = '{$value}'")->find())
$tagsModel->where("title = '{$value}'")->setInc('count');
else
if($value)
$tagsModel->add(array('title'=>$value, 'count'=>1));
}
}
~~~
标签的处理思路是,先将原有文章的所有标签数量-1,然后新增或者更新时的标签遍历时再去更新统计数,或者新增标签。这样统计不会乱。
## 其他格式
其他格式,之前说了,`'picture', 'music', 'video'`这三个类型 存description。content内容字段是数组序列化json的。
图片和音乐都可能多数据,视频只能一种。
### 图片
![2015-06-14/557d9f9786935](http://box.kancloud.cn/2015-06-14_557d9f9786935.png)
![2015-06-14/557d9fb7cd304](http://box.kancloud.cn/2015-06-14_557d9fb7cd304.png)
图片的content是个数组,每个元素里存图片的path和alt。
图片上传,不说了就是pupload。后js动态添加图片数组。
### 音乐
![2015-06-14/557da02089832](http://box.kancloud.cn/2015-06-14_557da02089832.png)
![搜索](http://box.kancloud.cn/2015-06-14_557da05081bf5.png)
![添加数据](http://box.kancloud.cn/2015-06-14_557da08a43208.png)
音乐比图片多了一个id,alt换为title了。
音乐支持本地上传mp3和百度搜索。
百度音乐搜索,其实就是参考了ueditor里的上传,只不过自己用bootstrap-table拼接了搜索结果,然后选择后,js处理了返回一个多维数组的格式。其他没啥。
### 视频
视频支持本地上传mp4和在线视频解析。
在线解析用的是云边轻博客商业版2.0里的类。
然后经过优化了,支持优酷/土豆/酷六/56/搜狐/新浪视频网站的播放页面地址解析插入(注意不是FLASH地址)。
![2015-06-22/5587743fd115a](http://box.kancloud.cn/2015-06-22_5587743fd115a.png)
![2015-06-22/558778da6b572](http://box.kancloud.cn/2015-06-22_558778da6b572.png)
mp4本地视频上传
![2015-06-22/5587796d41b6d](http://box.kancloud.cn/2015-06-22_5587796d41b6d.png)
#### 在线视频的地址获取
我们都知道传统视频播放要一个flash播放器,然后播放的一般都是flv文件。
后来html5标准出来后 mp4一般成为了标准格式,因为ios平台这个格式支持的最好。
我们看一下要分析的视频
<http://v.youku.com/v_show/id_XMTI2NzQ3NTY2MA==_ev_10.html?from=y1.3-idx-uhome-1519-20887.205985-205986-205830-205987.5-3>
![2015-06-22/55877bae73d90](http://box.kancloud.cn/2015-06-22_55877bae73d90.png)
用chrome看下播放器代码:
![2015-06-22/55877c466642b](http://box.kancloud.cn/2015-06-22_55877c466642b.png)
然而这里的地址不是我们想要的是个参数给那个播放器object读取的。
我们要的地址可以通过分享获取到,视频网站为例视频播放量,一般会提供分享功能。
![2015-06-22/55877d99d8f73](http://box.kancloud.cn/2015-06-22_55877d99d8f73.png)
我们复制flash地址 粘贴在地址栏后访问
![2015-06-22/55877e49ba252](http://box.kancloud.cn/2015-06-22_55877e49ba252.png)
怎么样?出来独立的播放器了吧。
![2015-06-22/55877e778315d](http://box.kancloud.cn/2015-06-22_55877e778315d.png)
而我们在线播放的原理就是在当前网页里嵌入这么一个embed的标签,地址是在线视频的真实地址。
`<embed type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" src="http://player.youku.com/player.php/sid/XOTE1NDg4OTE2/v.swf" width="617" height="480" wmode="transparent" play="true" loop="false" menu="false" allowscriptaccess="never" allowfullscreen="true">`
一般来说php程序就是curl请求平播放页面,然后想办法解析出这个地址。
具体程序大家可以看我的那个视频解析类 urlParse.php。
我只展示部分的代码,以解析优酷视频地址为例:
~~~
/**
* 优酷网
* http://v.youku.com/v_show/id_XMjI4MDM4NDc2.html
* http://player.youku.com/player.php/sid/XMjU0NjI2Njg4/v.swf
*/
private function _parseYouku($url) {
preg_match("#id\_(\w+)#", $url, $matches);
if (empty($matches)) {
preg_match("#v_playlist\/#", $url, $mat);
if (!$mat)
return false;
$html = self::_fget($url);
preg_match("#videoId2\s*=\s*\'(\w+)\'#", $html, $matches);
if (!$matches)
return false;
}
$link = "http://v.youku.com/player/getPlayList/VideoIDS/{$matches[1]}/timezone/+08/version/5/source/out?password=&ran=2513&n=3";
$retval = self::_cget($link);
if ($retval) {
$json = json_decode($retval, true);
$data['img'] = $json['data'][0]['logo'];
$data['title'] = $json['data'][0]['title'];
$data['url'] = $url;
$data['id'] = "http://player.youku.com/player.php/sid/{$matches[1]}/v.swf";
$data['type'] = 'youku';
return $data;
} else {
return false;
}
}
~~~
前面的部分是获取vid,然后通过后门地址 拼接getPlayList那个去获取视频元数据,也就是标题和封面等信息,每个视频网站后门地址不一样,元数据也不一样,我们只获得通用的就好。
具体优酷视频地址原理参见:[优酷真实视频地址解析](http://my.oschina.net/u/727843/blog/390872)
- 序
- 前言
- 内容简介
- 目录
- 基础知识
- 起步
- 控制器
- 模型
- 模板
- 命名空间
- 进阶知识
- 路由
- 配置
- 缓存
- 权限
- 扩展
- 国际化
- 安全
- 单元测试
- 拿来主义
- 调试方法
- 调试的步骤
- 调试工具
- 显示trace信息
- 开启调试和关闭调试的区别
- netbeans+xdebug
- Socketlog
- PHP常见错误
- 小黄鸭调试法,每个程序员都要知道的
- 应用场景
- 第三方登录
- 图片处理
- 博客
- SAE
- REST实践
- Cli
- ajax分页
- barcode条形码
- excel
- 发邮件
- 汉字转全拼和首字母,支持带声调
- 中文分词
- 浏览器useragent解析
- freelog项目实战
- 需求分析
- 数据库设计
- 编码实践
- 前端实现
- rest接口
- 文章发布
- 文件上传
- 视频播放
- 音乐播放
- 图片幻灯片展示
- 注册和登录
- 个人资料更新
- 第三方登录的使用
- 后台
- 微信的开发
- 首页及个人主页
- 列表
- 归档
- 搜索
- 分页
- 总结经验
- 自我提升
- 进行小项目的锻炼
- 对现有轮子的重构和移植
- 写技术博客
- 制作视频教程
- 学习PHP的知识和新特性
- 和同行直接沟通、交流
- 学好英语,走向国际
- 如何参与
- 浏览官网和极思维还有看云
- 回答ThinkPHP新手的问题
- 尝试发现ThinkPHP的bug,告诉官方人员或者push request
- 开发能提高效率的ThinkPHP工具
- 尝试翻译官方文档
- 帮新手入门
- 创造基于ThinkPHP的产品,进行连带推广
- 展望未来
- OneThink
- ThinkPHP4
- 附录