zoukankan      html  css  js  c++  java
  • Laravel中的日志与上传

      PHP中的框架众多,我自己就接触了好几个。大学那会啥也不懂啥也不会,拿了一个ThinkPHP学了。也许有好多人吐槽TP,但是个人感觉不能说哪个框架好,哪个框架不好,再不好的框架你能把源码读上一遍,框架的设计思想理解了也能学到好多东西。况且有好多东西自己还不理解,所以认真学习一个框架这还是可以学不少东西的。

      还是先说说Laravel吧,现在已经到5.2了。就我自己来说之前没有接触过laravel,但是学习过laravel之后感觉这个框架确实不错,并且老外用的不亦乐乎。他的开发社区还可以,文档比较齐全,但是官网文档不咋地,从上面读不出多少东西(自己感觉),好多东西还得阅读源码,对于我这种英语不好的人还更喜欢中文文档(以后得改)。Laravel是使用Composer(https://getcomposer.org)来管理依赖,确实比较方便,但是因为镜像被墙的原因在访问或者更新的时候比较慢(几乎失败),这里有解决办法:http://pkg.phpcomposer.com/#tip1。

      最近因为工作需要项目要重构(重构原因不用多说,大家懂得),需要迁移到新的框架上。Laravel是一个不错的框架,强大的路由,便捷的配置,高可用的模块依赖,确实为开发省了不少力气。考虑到我们这个项目主要是接口部分,对性能有一定的要求(但是不是苛刻),并且路由不能改,要兼容老的逻辑,所以Laravel是首选,但是有一个问题就是我们是写接口,那么要依赖的东西就少很多,比如view层几乎用不到,还有就是测试模块,上传模块(有图床),本地化模块文件系统等也用不到,所以使用Laravel还是比较浪费的,说白了他比较重。所以我们就考虑了基于laravel的一个框架Lumen,相比Laravel这个全栈框架而言Lumen精简了不少,并且Lumen是面向Api的,所以最后就选择Lumen了。

      laravel也不是全能的有优点也有缺点,比如他的依赖太多,可以看一下安装好的laravel框架默认的依赖源包就有30M左右,确实有点大。直到今天在使用过程中发现Lumen也是有点力不从心,随着业务逻辑越来越复杂,访问速度各方面也下来了,我们有时候考虑 slim 等更轻量级的,其实吧新浪这边不少人是鸟哥粉,不少人推崇yaf,yaf确实牛逼,实践证明快的不止一点两点,估计以后还得迁移到yaf。这段时间PHP7不是出了吗,但是测试结果表明Bug不少,把接口迁移到7上应该有不少的性能提升,据说是提高100%,还没敢尝试,等稳定了再说吧!

      说了不少废话,下面我就介绍一下Laravel中的日志与上传,慢慢来,这篇文章先写一部分,因为Laravel东西比较多,其他的我会慢慢写出来。我就说说自己在使用过程中遇到的问题,遇到的坑,帮助大家学习。

    一、日志

    1、说明:

    日志重要性不言而喻,我们这边的日志是人工推荐,兴趣爱好,推荐位的依赖。做推荐的同学比写接口的还要多,日志出了问题,推荐就会不准确甚至无法推荐,可见日志的重要性。

    Laravel框架初始化好了以后错误和异常处理已经默认配置好了,他的日志是基于一款很好用的日志管理工具Monolog,

      首先说一下Monolog,是php下比较全又容易扩展的记录日志组件。其中Symfony 、 CakePHP等知名php框架都内置了Monolog,有兴趣的可以看一下。每个Logger实例都有一个通道和日志处理器栈。每当你添加一条日志记录,它会被发送到日志处理器栈。 你可以创建很多Logger每个Logger定义一个通道(db,请求,路由),每个Logger有很多日志处理器。这些通道会过滤日志。每个日志处理器都有一个Formatter(内置的日志显示格式处理器)。你还可以设定日志级别。(官网解释)

      日志配置:Laravel目前支持四种日志处理器,

    1 Single(将日志记录到单个文件中。该日志处理器对应,对应StreamHandler),
    2 
    3 Daily (以日期为单位将进行日志记录对应RotatingFileHandler)
    4 
    5 Syslog(将日志记录到Syslog中。对应SysLogHandler)
    6 
    7 Errorlog(将日志记录到PHP的error_log中。对应ErrorLogHandler)

    明白了日志的处理方式我们就可以设置自己需要的方式,在 config/app.php中的对应项设置(默认的):

    1 'log' => 'single',

    2、使用Log记录日志

    Laravel提供了Log方法记录日志,Log实际上使用的 IlluminateLogWriter,应为在其中 Writer 的构造函数中注入了MonologLogger。生成的日志文件存放在storage/logs目录下。

    如下:

    1 Log::emergency($error); //紧急,如系统挂掉
    2 Log::alert($error);     //需要立即采取行动,如数据库异常等
    3 Log::critical($error);  //严重问题,如异常
    4 Log::error($error);     //运行时错误,不需要立即处理但需要被记录和监控
    5 Log::warning($error);   //警告但不是错误,比如使用了被废弃的API
    6 Log::notice($error);    //普通但值得注意的事件
    7 Log::info($error);      //感兴趣的事件,比如登录、退出
    8 Log::debug($error);     //详细的调试信息

    3、按照自己的需求记录日志

    Laravel中如果按照原来的配置貌似不能按照自己的需求记录日志,我就按照自己的需求写了一个,供大家参考,当然你可以跳过他提供的日志处理方法Log,在容器中把 Monolog对象写入容器,可以写成单例的形式,这样在加载的时候只实例化一次,然后按照monolog来配置自己想要的记录日志的方法。

     1 class Save_log
     2 {
     3     //存放每个级别实例
     4     private static $obj_log = [];
     5 
     6     //日志类型映射
     7     private static $classify_arr = ['default', 'debug_log','error_log'];
     8 
     9     /**
    10      * 单利初始化以及调取对象
    11      * @param $classify 日志的的频道,对应不同的目录
    12      * @param $max_num  日志记录的最大数量
    13      */
    14     public static function get_log_instance($classify = 'default', $max_num = 0)
    15     {
    16         if(empty(self::$obj_log[$classify])) {
    17             self::$obj_log[$classify] = new Writer(new Logger($classify));
    18             self::$obj_log[$classify]->useDailyFiles(self::get_path($classify), $max_num);
    19         }
    20         return self::$obj_log[$classify];
    21     }
    22 
    23     /**
    24      * 映射对应的目录
    25      * @param $classify 日志的不同的频道
    26      */
    27     private static function get_path($classify)
    28     {
    29         $root_path = public_path();
    30         $path = $root_path . '/../../logs/'; //可以是自己的任意路径
    31         $log_arr = self::$classify_arr;
    32         if(!empty($log_arr) && !empty($classify)) {
    33             if(in_array($classify, $log_arr)) {
    34                 return $path . $classify. '/' . $classify . '.log';
    35             }
    36         }
    37         return $path . 'default/default.log';
    38     }
    39 
    40     /**
    41      * 映射对应的目录
    42      * @param $func 调用的方法
    43      * @param $arguments 参数,包括数据和日志等级
    44      */
    45     public static function __callStatic($func, $arguments)
    46     {
    47         $get_obj = self::get_log_instance($func);
    48         if(empty($get_obj)) {
    49             log::error('Save Log Error!');
    50         }
    51         if(empty($arguments) || !is_array($arguments) || !isset($arguments[0])) {
    52             $get_obj->info('No Data Save!');
    53         } else if(!isset($arguments[1])) {
    54             $get_obj->info($arguments[0]);
    55         } else {
    56             $get_obj->{$arguments[1]}($arguments[0]);
    57         }
    58     }
    59 }

    使用的时候可以指定,如下:

    1 Save_log::error_log($info, 'error');
    2 Save_log::debug_log($info);

    日志内容如下:

    二、上传文件。

    Laravel中的上传文件是基于Flysystem提供的文件系统来实现上传,删除,移动。他支持多种驱动,还有一个值得看的云存储,在SAE上需要用到。

    文件系统配置位于Config/filesystems.php,我使用的试本地驱动。Laravel中的上传目录有两个:public和Storage两个,有人说这两个一样,其实是有区别的,应该说是各有好处,如果放在public中,服务器可以直接控制访问,方便效率高,放在Storage中可以加上用户控制比如权限等。

    上传需要的函数如下:

    判断是否进行了上传,是否存在文件:

    1 $request->hasFile('file')

    判断上传是否出错:

    1 $file = $request->file('file');
    2 //判断文件上传过程中是否出错
    3 if(!$file->isValid()) {
    4      exit('文件上传出错!');
    5 }

    确定上传:

    1 $bytes = Storage::put(
    2       $savePath,
    3       file_get_contents($file->getRealPath())
    4 );

    你也可以使用:

    $path = $file -> move('storage/uploads');

    生成缩略图

    Laravel木有提供函数生成缩略图,但是我们可以借助强大的Composer来引入图片处理库 Integration/Image

    在项目根目录中的composer.json中的require中添加:"intervention/image": "dev-master",如下图:

    然后在config/app.php中providers数组中添加:

    1 InterventionImageImageServiceProvider::class

    在aliases数组中添加别名:

    1 'Image'     => InterventionImageFacadesImage::class,

    这样就可以使用了,在类文件中添加:

    1 use Image;

    下面是添加水印并且生成缩略图:

    $Image->text('@ u/'. $user_id, $news_width - 40 - $length * 10, $news_height - 24, function($font) {
           $font->file('public/foos.ttf');
           $font->size(14);
           $font->color('#ffffff');
     });

    最后附上整个源码,其中生成缩略图部分可以抽象出来,因为有好几个地方都需要用到,并且水印还有看图片大小等等。

      1 /**
      2  * 上传文件
      3  * @param  Object Request
      4  * @return Json result
      5  */
      6 public function upload_file(Request $request)
      7 {
      8     $user_id = $request->get('user_id');
      9     $width = $request->get('width');
     10     $height = $request->get('height');
     11     $upload_type = $request->get('upload_type');
     12     $watermark = $request->get('watermark');
     13 
     14     //参数检查
     15     if(empty($user_id)) {
     16         return response()->json(['code' => 1001, 'msg' => '参数错误']);
     17     }
     18 
     19     //得到上传文件名
     20     if(!empty($_FILES)) {
     21         $key_arr =  array_keys($_FILES);
     22         $file_key = $key_arr[0];
     23     }
     24     
     25     $file_key = !isset($file_key) || empty($file_key) ? 'fileselect' : $file_key;
     26 
     27     if(!$request->hasFile($file_key)) {
     28         return response()->json(['code' => 1002, 'msg' => '上传文件为空']);
     29     }
     30 
     31     $upload_files = $request->file();
     32     if(empty($upload_files) || !is_array($upload_files)) {
     33         return response()->json(['code' => 1003, 'msg' => '上传失败']);
     34     }
     35 
     36     //兼容单文件上传
     37     if(Utils::arrayLevel($upload_files) < 2) {
     38         $files[$file_key][0] = $upload_files[$file_key];
     39     } else {
     40         $files = $upload_files;
     41     }
     42 
     43     if($upload_type == 'userphoto' && count($files[$file_key]) > 1) {
     44         return response()->json(['code' => 1004, 'msg' => '头像只能上传一张']);
     45     }
     46 
     47     if(count($files[$file_key]) > MAX_UPLOAD_FILE) {
     48         return response()->json(['code' => 1005, 'msg' => '大于最大上传数限制']);
     49     }
     50 
     51     //过滤大于MAX_FILE_SIZE的情况
     52     foreach ($files[$file_key] as $key => $file) {
     53         if($file-> getClientSize() > MAX_FILE_SIZE * 1024 * 1024) {
     54             return response()->json(['code' => 1006, 'msg' => '文件大小不能超过']);
     55         }
     56     }
     57 
     58     $file_info = [];
     59     $length = strlen($user_id . '');
     60     //兼容批量上传
     61     foreach ($files[$file_key] as $key => $file) {
     62         if(!$file->isValid()) {
     63             return response()->json(['code' => 1007, 'msg' => '上传出错']);
     64         }
     65 
     66         if($upload_type == 'userpic') {
     67             $file_dir = 'userpic';
     68         } else {
     69             $type = $file->getMimeType();
     70             if(empty($type) && !is_array($type)) {
     71                 return response()->json(['code' => 1008, 'msg' => '得到文件类型出错']);
     72             }
     73 
     74             //映射文件类型
     75             $type_arr = explode("/", $type);
     76             switch($type_arr[0]){
     77                 case "image"      : $file_dir = "image"; break;
     78                 case "video"      : $file_dir = "video"; break;
     79                 case "audio"      : $file_dir = "voice"; break;
     80                 case "text"       : $file_dir = "doc";   break;
     81                 case "application": $file_dir = "doc";   break;
     82                 default           : $file_dir = "other"; break;
     83             }
     84         }
     85 
     86         //文件后缀
     87         $postfix = $file->getClientOriginalExtension();
     88         $save_dir = UPLOAD_FILE_PATH;
     89         $file_date = date('Ym');
     90         $file_name = $file_dir . '_' . $file_date . '_' . rand(111111, 999999) . $user_id;
     91         $save_name = $file_name . '.' . $postfix;
     92         $save_path = $file_dir . '/' . $file_date . '/' . $save_name;
     93         Storage::put(
     94             $save_path, 
     95             file_get_contents($file->getRealPath())
     96         );
     97         if(!Storage::exists($save_path)) {
     98             return response()->json(['code' => 1009, 'msg' => '保存文件失败']);
     99         }
    100 
    101         //生成缩略图
    102         if($file_dir == 'image' && (!empty($width) || !empty($height))) {
    103             $Image = Image::make($save_dir . $save_path);
    104             $img_width = $Image->width();
    105             $img_height = $Image->height();
    106 
    107             //如果有一个为空,则与另一个相等;
    108             if(empty($width)) {
    109                 //传入的高度如果比实际高度大,就取实际高度
    110                 $height = $img_height < $height ? $img_height : $height;
    111                 $width = $height;
    112             } else if(empty($height)) {
    113                 $width = $img_width < $width ? $img_width : $width;
    114                 $height = $width;
    115             } else {
    116                 $height = $img_height < $height ? $img_height : $height;
    117                 $width = $img_width < $width ? $img_width : $width;
    118             }
    119 
    120             //拼接缩略图路径
    121             $Image->resize($width, $height);
    122             $save_name_s = $file_name . '_s.' . $postfix;
    123             $save_path_s = $save_dir . $file_dir . '/' . $file_date . '/' . $save_name_s;
    124             $file_path_s = $request->root() . '/' . $save_path_s;
    125 
    126             if($watermark != 1) {
    127                 //添加缩略图水印
    128                 $news_width = $Image->width();
    129                 $news_height = $Image->height();
    130                 if($news_width > 100) {
    131                     $Image->text('@ u/'. $user_id, $news_width - 40 - $length * 10, $news_height - 24, function($font) {
    132                         $font->file('public/foos.ttf');
    133                         $font->size(14);
    134                         $font->color('#ffffff');
    135                     });
    136                 }
    137             }
    138 
    139             //保存缩略图
    140             $Image->save($save_path_s, 100);
    141             $file_size_s = round($Image->filesize() / 1024 ,2) . 'K';
    142         }
    143 
    144         $file_path = $request->root() . '/' . $save_dir . $save_path;
    145         $file_size = round($file-> getClientSize() / 1024 ,2) . 'K';
    146         $file_info[] = compact(
    147             'save_name', 'file_size', 'file_path', 'save_name_s', 'file_size_s', 'file_path_s'
    148         );
    149     }
    150 
    151     if(empty($file_info)) {
    152         return response()->json(['code' => 1010, 'msg' => '异常出错']);
    153     } else {
    154         return response()->json(['code' => 0, 'msg' => '', 'data' => $file_info]);
    155     }
    156 }

    结束语:

    以上是我学习中遇到的一部分问题,不对之处欢迎指正,这篇文章只是说了日志和上传,以后会持续更新,包括路由,中间件,容器等等,还有好多需要说的。

    注意:
    1、本博客同步更新到我的个人网站:http://www.zhaoyafei.cn
    2、本文属原创内容,为了尊重他人劳动,转载请注明本文地址:
    http://www.cnblogs.com/zyf-zhaoyafei/p/5076515.html
     
  • 相关阅读:
    POJ2774 Long Long Message
    Lyndon Word相关
    后缀自动机(SAM)
    后缀数组(SA)
    [THUSC2016]补退选
    [HNOI2008]GT考试
    CF1080E Sonya and Matrix Beauty
    [JSOI2008]火星人
    两道FFT题目略解
    网络流概念+EdmondKarp算法+Dinic(Dinitz)
  • 原文地址:https://www.cnblogs.com/zyf-zhaoyafei/p/5076515.html
Copyright © 2011-2022 走看看