首先,什么话也不说,直接上效果图。
php
查看效果: html
电影记忆》 插件是在onethink 平台上开发的一个用于记录本身观看电影、电视媒体的展现工具。git
bootstrap2.3.1 ui程序员
onethink cmf框架web
curlajax
QueryList php采集组件thinkphp
又拍云存储bootstrap
搜索电影媒体浏览器
看了电影缓存
后台列表(搜索、删除)
编辑观看状态、观看时间
后台插件列表中安装、启用插件
插件配置中设置又拍云帐号
在要显示的单个页面 调用single钩子
{:hook('single', array('name'=>'MovieLog'))}
4.0 后台管理
4.1 后台列表搜索、删除
4.2 后台编辑单条记录
4.3 后台批量更新已看记录的简介
好了。下面开始从技术上讲重点。
做为一个onethink 最新带后台插件的例子。
启发upyun开发者如何对接接口。从采集图片、上传图片、更新缓存和批量进行数据管理更新。
启发你们如何进行bootstrap的几个插件优雅的使用。
实时web进度条如何实现。
SAE 上对又拍云的策略。
onethink 的介绍。你们看官网:http://www.onethink.cn/ 众所周知,thinkphp是国内最流行的PHP框架。可是因为更新频繁,使用者众多。致使各类在此基础上的产品太多,质量良莠不齐。二次开发项目难度大、成本高。
所以,thinkphp官方决定出一个产品,制定一个标准,解决这个问题。虽然只要会php,任何问题都是能解决和实现的。那样开发就像高考自由做文。文体不限。虽然自由,可是奇葩零分做文也多。
可是若是命个主题、限制个文体就容易多了。但愿你们有空能研究下。
本人在官方onethink的基础上移植了typecho的风格,制做了本身的博客。也是开源的。做为程序员,技术博客怎么能用他人的呢?
又拍云上传接口的使用。
thinkphp3.2里面的上传类改进成驱动形式了。支持上传时指定各类类型好比local、ftp、upyun、qiniu、sae、bcs之类的。
因此我就插件里面配置存储了下驱动的必填项。而后用本身以前对接upyun的帐号里又建了个空间bucket: movie-log。开始对接使用。
config.php
return array( 'host'=>array( 'title'=>'又拍云服务器:', 'type'=>'text', 'value'=>'', ), 'username'=>array( 'title'=>'又拍云用户:', 'type'=>'text', 'value'=>'', ), 'password'=>array( 'title'=>'又拍云密码:', 'type'=>'password', 'value'=>'', ), 'bucket'=>array( 'title'=>'又拍云空间名称:', 'type'=>'text', 'value'=>'MovieLog', ), 'timeout'=>array( 'title'=>'又拍云上传超时时间:', 'type'=>'text', 'value'=>'90', ), );
开始准备使用Upload驱动类的,后来因为上传驱动里有check文件方法,里面 is_uploaded_file 始终没法经过,毕竟不是传统表单上传的。为了避免改核心代码,我把upyun的驱动放到插件根目录里并对其进行修改。用里面的save方法足以。后来加了个更新缓存的方法。
/ThinkPHP/Library/Think/Upload.class.php
/** * 检查上传的文件 * @param array $file 文件信息 */ private function check($file) { /* 文件上传失败,捕获错误代码 */ if ($file['error']) { $this->error($file['error']); return false; } /* 无效上传 */ if (empty($file['name'])){ $this->error = '未知上传错误!'; } /* 检查是否合法上传 */ if (!is_uploaded_file($file['tmp_name'])) { $this->error = '非法上传文件!'; return false; }
在个人插件MovieLogModel里写了个方法,进行图片的上传。
include_once ONETHINK_ADDON_PATH.'MovieLog/function.php'; $dir = ONETHINK_ADDON_PATH.'MovieLog/Upload/'; if(APP_MODE == 'sae') $dir = SAE_TMP_PATH; $UpyunConfig = get_addon_config('MovieLog'); $uploader = new Upyun('/', $UpyunConfig); $name = strstr($this->data['alt'],'subject/'); $name = ltrim($name,'subject/'); $name = trim($name,'/'); if(!is_dir($dir)) @mkdir($dir); $images = array( 's'=>$this->curl_download($images['small'],"{$dir}s_{$name}.jpg"), 'm'=>$this->curl_download($images['medium'],"{$dir}m_{$name}.jpg"), 'l'=>$this->curl_download($images['large'],"{$dir}l_{$name}.jpg") ); foreach ($images as $key => $value) { $file = array( 'name' =>"{$key}_{$name}.jpg", 'savepath'=>'', 'savename'=>"{$key}_{$name}.jpg", 'type' =>"image/jpeg", 'size' =>filesize($value), 'tmp_name' =>$value, 'md5'=>md5_file($value), 'error' =>0, ); $info = $uploader->save($file); if($info) @unlink($value); } $refresh = array(); $return = array('small'=>"s_{$name}.jpg",'medium'=>"m_{$name}.jpg",'large'=>"l_{$name}.jpg"); foreach($return as $r){ $refresh[] = movie_cover($r); } $uploader->refreshCache(join('\n', $refresh)); return serialize($return); }
因此插件图片其实是经过保存远程图片到本地,而后用驱动上传到upyun空间里,而后删除的。你们都知道海量图片占用本地磁盘大,且访问慢。这时候又拍云就起到做用了。
我首页200多部电影的封面,依然打开嗖嗖的。
这里有两点要注意。一个是图片重复上传的问题,因为个人图片采集下来规则是定的一个图片不会重复,有个对应的电影id。因此无需判断。正常为了节省流量和时间。先判断空间上是否有一样的图片,有就返回图片地址信息,没有就才上传。判断惟一经过md5_file和sha1_file函数判断就能够了。
curl 采集用的QueryList。注意选择器正确,没法找到内容,去掉rang参数
再一个就是图片缓存的问题。图片上传后,经过http://空间.b0.upaiyun.com/文件名 访问,不必定立马能访问到。这个须要手动去刷新它。虽然又拍云管理空间里有传多个连接(换行)而后更新缓存的工具。从人性化的角度来讲,这应该是自动的。或者访问时后触发的。所以我扩展里onethink的Upyun驱动类加上了刷新的方法。而后开始是在插件访问的前台首页,绑定图片error事件,去ajax刷新的。
//刷新upaiyun图片缓存 $('img[src*="http://{$config.bucket}"]').error(function(e){ setTimeout(function () { var url = "{:addons_url('MovieLog://MovieLog/refreshImg')}"; var _this = this; this.src = ''; $(this).parents('.post-li').addClass('mega-loading'); $.ajax({ url: url, data: {file : this.src}, success:function(data){ if(data.status) _this.src = _this.src; $(this).parents('.post-li').removeClass('mega-loading'); } }); }, 1000); });
可是因为图太多了,不断ajax 有可能致使浏览器卡死。后来在调试sae上上传的时候传完手动刷新了一次。上面代码中
$uploader->refreshCache(join('\n', $refresh));
就是调用接口刷新。
批量进行电影简介更新。首先批量只是小批量,只选取了观看后的记录,而且要更新的summary字段is null or = ''的记录。由于本插件是在搜索时候采集数据,更新观看状态字段时候采集图片和写如简介的。为了效果,手动加入记录后。已经有3000条了。所有更新的话太慢。想一想一个记录查curl都要2s左右。
而后就是经过一个按钮弹出更新的窗口。进行第一次查询总更新记录集合,缓存主,在返回的第一次查询里返回更新的table里的tr 的 process js函数。用eval 执行一次。而后执行的js函数里再调用ajax从新请求下一个和改进度条宽度。最后一次返回判断是否结束,将进度条宽度直接设满。
js端:
('#batch_update_btn').click(function(){ var url = $(this).attr('url'); $('#myModal').html($('#loading_tpl').html()); var $btn = $(this); $btn.button('loading'); $('#myModal').modal('show'); $.ajax({ url: url, success:function(data){ eval(data); }, error:function(XMLHttpRequest, textStatus, errorThrown){ $('#myModal').html('网络出错了'); } }); $('#myModal').modal('show'); return false; });
function process(id, msg){ var $btn = $('#batch_update_btn'); if(id != 0){ var url = $btn.attr('url'); $('#myModal .progress .bar').width(function(n,c){ return c+5; }); $('#file_list tbody').prepend(msg); $.ajax({ url: url, data: {id: id}, success:function(data){ eval(data); }, error:function(XMLHttpRequest, textStatus, errorThrown){ $('#myModal').html('网络出错了'); } }); }else{ $('#myModal .progress .bar').width('670'); $('#file_list').before('<p>所有更新完毕</p>'); $btn.button('reset'); } }
php端方法
//批量更新简介 public function update_batch(){ $id = I('id', 0); include_once ONETHINK_ADDON_PATH.'MovieLog/DoubanMovie.class.php'; $obj = new \DoubanMovie(); if($id == 0){ $movie = D('Addons://MovieLog/Movie'); $list = $movie->where("(summary is null OR summary = '') AND is_published = 1")->getField('id,id,title'); $record = array_pop($list); $id = $record['id']; }else{ $list = session('batch_list'); $record = $list[$id]; unset($list[$id]); } $msg = "<tr><td>{$record['id']}</td><td>{$record['title']}</td><td class='success'> √</td></tr>"; $detail = $obj->subject($id); $movie = D('Addons://MovieLog/Movie'); if($detail['summary'] && $movie->save(array('id'=>$id, 'summary'=>$detail['summary'])) !== false) $result = "<td class='success'> √</td>"; else $result = "<td class='error'> X</td>"; $msg = "<tr><td>{$record['id']}</td><td>{$record['title']}</td>{$result}</tr>"; if(count($list)){ $new = array_slice($list, 0 ,1); echo "process({$new[0]['id']}, \"{$msg}\");"; session('batch_list', $list); }else{ session('batch_list', null); exit("process(0,'');"); } }
c bootstrap插件使用:
首先用了ScrollSpy 这个js组件。用于首页月份随内容高度变化而高亮的。这个效果是参考 点点网的 archive归档效果。这个插件注意点是他高亮的元素必须是.nav 的li 而且加的是active类名。在监听容器上能够指定偏移高度。
而后是popover组件。默认的在移动到浮动窗上的容易消失。找了段别人写好的manual 事件的
//下方图片墙提示 $('#mega-content .post-li').popover({ trigger:'manual', delay:{show:1000, hide: 1000}, content:function(e){ var src = $(this).attr('src'); var summary = $(this).attr('summary'); return "<div class='cover_popover'><img src='"+src+"'><p>"+summary+"</p></div>"; }, html : true, title: this.title }).on("mouseover", function () { var _this = this; $(this).popover("show"); $(this).siblings(".popover").on("mouseleave", function () { $(_this).popover('hide'); }); }).on("mouseleave", function () { var _this = this; setTimeout(function () { if (!$(".popover:hover").length) { $(_this).popover("hide") } }, 100); });
最后是弹窗。bootstrap的弹窗美观,可是提供的接口少。就那么几个方法。不过在个人努力下实现了动态加载内容。
一种是指定remote属性。可是这种不能动态传参数。
第二种就是我用的,先页面上放容器,而后弹出时候ajax返回页面内容。
注意:1.弹窗样式 页面上控制。2.默认弹窗会缓存住,绑定hide事件,将加载的页面删除掉。
$('#myModal').on('shown.bs.modal', function (e) { }).on('hidden.bs.modal',function(e){ $(this).removeData('bs.modal'); });
d 实时进度条代码上面有了,就是轮询ajax 设置进度条宽度。
e sae 上开始参考官方手册说有wrapper saekv://、 saemc 都试了没用。最后发现有临时目录常量 判断环境sae时改下临时存储目录就好了。原本还准备曲线救国用ftp接口呢!话说sae第三方服务有又拍云,结果不能开通。怒了。
if(APP_MODE == 'sae') $dir = SAE_TMP_PATH;
以前本身写测试写入能够 ajax访问就不行了。最后发现是缓存的问题。最后上传完就刷新缓存,什么疑难杂症都没了。O(∩_∩)O哈哈~
好了,总结就这么多,但愿你们多给我投票 http://upyun.gitcafe.com/projects?category=top50 onethink-MovieLog-for-upyun 那个。“深度云服务,编译新将来”,欢迎你们关注比赛。