💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 发布前台 首先,我们看一下点点的发布界面 ![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)