Laravel中的日誌與上傳

PHP中的框架衆多,我本身就接觸了好幾個。大學那會啥也不懂啥也不會,拿了一個ThinkPHP學了。也許有好多人吐槽TP,可是我的感受不能說哪一個框架好,哪一個框架很差,再很差的框架你能把源碼讀上一遍,框架的設計思想理解了也能學到好多東西。何況有好多東西本身還不理解,因此認真學習一個框架這仍是能夠學很多東西的。php

  仍是先說說Laravel吧,如今已經到5.2了。就我本身來講以前沒有接觸過laravel,可是學習過laravel以後感受這個框架確實不錯,而且老外用的不亦樂乎。他的開發社區還能夠,文檔比較齊全,可是官網文檔不咋地,從上面讀不出多少東西(本身感受),好多東西還得閱讀源碼,對於我這種英語很差的人還更喜歡中文文檔(之後得改)。Laravel是使用Composer(https://getcomposer.org)來管理依賴,確實比較方便,可是由於鏡像被牆的緣由在訪問或者更新的時候比較慢(幾乎失敗),這裏有解決辦法:http://pkg.phpcomposer.com/#tip1。laravel

  最近由於工做須要項目要重構(重構緣由不用多說,你們懂得),須要遷移到新的框架上。Laravel是一個不錯的框架,強大的路由,便捷的配置,高可用的模塊依賴,確實爲開發省了很多力氣。考慮到咱們這個項目主要是接口部分,對性能有必定的要求(可是不是苛刻),而且路由不能改,要兼容老的邏輯,因此Laravel是首選,可是有一個問題就是咱們是寫接口,那麼要依賴的東西就少不少,好比view層幾乎用不到,還有就是測試模塊,上傳模塊(有圖牀),本地化模塊文件系統等也用不到,因此使用Laravel仍是比較浪費的,說白了他比較重。因此咱們就考慮了基於laravel的一個框架Lumen,相比Laravel這個全棧框架而言Lumen精簡了很多,而且Lumen是面向Api的,因此最後就選擇Lumen了。數據庫

  laravel也不是全能的有優勢也有缺點,好比他的依賴太多,能夠看一下安裝好的laravel框架默認的依賴源包就有30M左右,確實有點大。直到今天在使用過程當中發現Lumen也是有點力不從心,隨着業務邏輯愈來愈複雜,訪問速度各方面也下來了,咱們有時候考慮 slim 等更輕量級的,其實吧新浪這邊很多人是鳥哥粉,很多人推崇yaf,yaf確實牛逼,實踐證實快的不止一點兩點,估計之後還得遷移到yaf。這段時間PHP7不是出了嗎,可是測試結果代表Bug很多,把接口遷移到7上應該有很多的性能提高,聽說是提升100%,還沒敢嘗試,等穩定了再說吧!json

  說了很多廢話,下面我就介紹一下Laravel中的日誌與上傳,慢慢來,這篇文章先寫一部分,由於Laravel東西比較多,其餘的我會慢慢寫出來。我就說說本身在使用過程當中遇到的問題,遇到的坑,幫助你們學習。數組

1、日誌php框架

一、說明:服務器

日誌重要性不言而喻,咱們這邊的日誌是人工推薦,興趣愛好,推薦位的依賴。作推薦的同窗比寫接口的還要多,日誌出了問題,推薦就會不許確甚至沒法推薦,可見日誌的重要性。app

Laravel框架初始化好了之後錯誤和異常處理已經默認配置好了,他的日誌是基於一款很好用的日誌管理工具Monolog,composer

  首先說一下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',

二、使用Log記錄日誌

Laravel提供了Log方法記錄日誌,Log實際上使用的 Illuminate\Log\Writer,應爲在其中 Writer 的構造函數中注入了Monolog\Logger。生成的日誌文件存放在storage/logs目錄下。

以下:

Log::emergency($error); //緊急,如系統掛掉
Log::alert($error);     //須要當即採起行動,如數據庫異常等
Log::critical($error);  //嚴重問題,如異常
Log::error($error);     //運行時錯誤,不須要當即處理但須要被記錄和監控
Log::warning($error);   //警告但不是錯誤,好比使用了被廢棄的API
Log::notice($error);    //普通但值得注意的事件
Log::info($error);      //感興趣的事件,好比登陸、退出
Log::debug($error);     //詳細的調試信息

三、按照本身的需求記錄日誌

Laravel中若是按照原來的配置貌似不能按照本身的需求記錄日誌,我就按照本身的需求寫了一個,供你們參考,固然你能夠跳過他提供的日誌處理方法Log,在容器中把 Monolog對象寫入容器,能夠寫成單例的形式,這樣在加載的時候只實例化一次,而後按照monolog來配置本身想要的記錄日誌的方法。

class Save_log
{
    //存放每一個級別實例
    private static $obj_log = [];

    //日誌類型映射
    private static $classify_arr = ['default', 'debug_log','error_log'];

    /**
     * 單利初始化以及調取對象
     * @param $classify 日誌的的頻道,對應不一樣的目錄
     * @param $max_num  日誌記錄的最大數量
     */
    public static function get_log_instance($classify = 'default', $max_num = 0)
    {
        if(empty(self::$obj_log[$classify])) {
            self::$obj_log[$classify] = new Writer(new Logger($classify));
            self::$obj_log[$classify]->useDailyFiles(self::get_path($classify), $max_num);
        }
        return self::$obj_log[$classify];
    }

    /**
     * 映射對應的目錄
     * @param $classify 日誌的不一樣的頻道
     */
    private static function get_path($classify)
    {
        $root_path = public_path();
        $path = $root_path . '/../../logs/'; //能夠是本身的任意路徑
        $log_arr = self::$classify_arr;
        if(!empty($log_arr) && !empty($classify)) {
            if(in_array($classify, $log_arr)) {
                return $path . $classify. '/' . $classify . '.log';
            }
        }
        return $path . 'default/default.log';
    }

    /**
     * 映射對應的目錄
     * @param $func 調用的方法
     * @param $arguments 參數,包括數據和日誌等級
     */
    public static function __callStatic($func, $arguments)
    {
        $get_obj = self::get_log_instance($func);
        if(empty($get_obj)) {
            log::error('Save Log Error!');
        }
        if(empty($arguments) || !is_array($arguments) || !isset($arguments[0])) {
            $get_obj->info('No Data Save!');
        } else if(!isset($arguments[1])) {
            $get_obj->info($arguments[0]);
        } else {
            $get_obj->{$arguments[1]}($arguments[0]);
        }
    }
}

使用的時候能夠指定,以下:

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

日誌內容以下:

2、上傳文件。

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 Intervention\Image\ImageServiceProvider::class

在aliases數組中添加別名:

1 'Image'     => Intervention\Image\Facades\Image::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');
 });

最後附上整個源碼,其中生成縮略圖部分能夠抽象出來,由於有好幾個地方都須要用到,而且水印還有看圖片大小等等。


/**
 * 上傳文件
 * @param  Object Request
 * @return Json result
 */
public function upload_file(Request $request)
{
    $user_id = $request->get('user_id');
    $width = $request->get('width');
    $height = $request->get('height');
    $upload_type = $request->get('upload_type');
    $watermark = $request->get('watermark');

    //參數檢查
    if(empty($user_id)) {
        return response()->json(['code' => 1001, 'msg' => '參數錯誤']);
    }

    //獲得上傳文件名
    if(!empty($_FILES)) {
        $key_arr =  array_keys($_FILES);
        $file_key = $key_arr[0];
    }
    
    $file_key = !isset($file_key) || empty($file_key) ? 'fileselect' : $file_key;

    if(!$request->hasFile($file_key)) {
        return response()->json(['code' => 1002, 'msg' => '上傳文件爲空']);
    }

    $upload_files = $request->file();
    if(empty($upload_files) || !is_array($upload_files)) {
        return response()->json(['code' => 1003, 'msg' => '上傳失敗']);
    }

    //兼容單文件上傳
    if(Utils::arrayLevel($upload_files) < 2) {
        $files[$file_key][0] = $upload_files[$file_key];
    } else {
        $files = $upload_files;
    }

    if($upload_type == 'userphoto' && count($files[$file_key]) > 1) {
        return response()->json(['code' => 1004, 'msg' => '頭像只能上傳一張']);
    }

    if(count($files[$file_key]) > MAX_UPLOAD_FILE) {
        return response()->json(['code' => 1005, 'msg' => '大於最大上傳數限制']);
    }

    //過濾大於MAX_FILE_SIZE的狀況
    foreach ($files[$file_key] as $key => $file) {
        if($file-> getClientSize() > MAX_FILE_SIZE * 1024 * 1024) {
            return response()->json(['code' => 1006, 'msg' => '文件大小不能超過']);
        }
    }

    $file_info = [];
    $length = strlen($user_id . '');
    //兼容批量上傳
    foreach ($files[$file_key] as $key => $file) {
        if(!$file->isValid()) {
            return response()->json(['code' => 1007, 'msg' => '上傳出錯']);
        }

        if($upload_type == 'userpic') {
            $file_dir = 'userpic';
        } else {
            $type = $file->getMimeType();
            if(empty($type) && !is_array($type)) {
                return response()->json(['code' => 1008, 'msg' => '獲得文件類型出錯']);
            }

            //映射文件類型
            $type_arr = explode("/", $type);
            switch($type_arr[0]){
                case "image"      : $file_dir = "image"; break;
                case "video"      : $file_dir = "video"; break;
                case "audio"      : $file_dir = "voice"; break;
                case "text"       : $file_dir = "doc";   break;
                case "application": $file_dir = "doc";   break;
                default           : $file_dir = "other"; break;
            }
        }

        //文件後綴
        $postfix = $file->getClientOriginalExtension();
        $save_dir = UPLOAD_FILE_PATH;
        $file_date = date('Ym');
        $file_name = $file_dir . '_' . $file_date . '_' . rand(111111, 999999) . $user_id;
        $save_name = $file_name . '.' . $postfix;
        $save_path = $file_dir . '/' . $file_date . '/' . $save_name;
        Storage::put(
            $save_path, 
            file_get_contents($file->getRealPath())
        );
        if(!Storage::exists($save_path)) {
            return response()->json(['code' => 1009, 'msg' => '保存文件失敗']);
        }

        //生成縮略圖
        if($file_dir == 'image' && (!empty($width) || !empty($height))) {
            $Image = Image::make($save_dir . $save_path);
            $img_width = $Image->width();
            $img_height = $Image->height();

            //若是有一個爲空,則與另外一個相等;
            if(empty($width)) {
                //傳入的高度若是比實際高度大,就取實際高度
                $height = $img_height < $height ? $img_height : $height;
                $width = $height;
            } else if(empty($height)) {
                $width = $img_width < $width ? $img_width : $width;
                $height = $width;
            } else {
                $height = $img_height < $height ? $img_height : $height;
                $width = $img_width < $width ? $img_width : $width;
            }

            //拼接縮略圖路徑
            $Image->resize($width, $height);
            $save_name_s = $file_name . '_s.' . $postfix;
            $save_path_s = $save_dir . $file_dir . '/' . $file_date . '/' . $save_name_s;
            $file_path_s = $request->root() . '/' . $save_path_s;

            if($watermark != 1) {
                //添加縮略圖水印
                $news_width = $Image->width();
                $news_height = $Image->height();
                if($news_width > 100) {
                    $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');
                    });
                }
            }

            //保存縮略圖
            $Image->save($save_path_s, 100);
            $file_size_s = round($Image->filesize() / 1024 ,2) . 'K';
        }

        $file_path = $request->root() . '/' . $save_dir . $save_path;
        $file_size = round($file-> getClientSize() / 1024 ,2) . 'K';
        $file_info[] = compact(
            'save_name', 'file_size', 'file_path', 'save_name_s', 'file_size_s', 'file_path_s'
        );
    }

    if(empty($file_info)) {
        return response()->json(['code' => 1010, 'msg' => '異常出錯']);
    } else {
        return response()->json(['code' => 0, 'msg' => '', 'data' => $file_info]);
    }
}


結束語:

以上是我學習中遇到的一部分問題,不對之處歡迎指正,這篇文章只是說了日誌和上傳,之後會持續更新,包括路由,中間件,容器等等,還有好多須要說的。另外會同步更新到個人我的網站:www.zhaoyafei.cn,歡迎訪問

轉載註明出處

相關文章
相關標籤/搜索