laravel 後臺添加管理員日誌記錄

今天抽離之前使用的 laravel 版本的 fastadmin 後臺,權限系統,當時沒有寫 '管理員日誌' 這個模塊,今天實現了下,過程中,也發現幾個問題,分享給大家。

可以先看下 fastadmin 源碼,它使用了 tp 的 behavior 功能,在應用結束後,調用了 admin log 鉤子

好久沒看 tp 了,不過還稍微瞭解點 laravel,看代碼機制,應該就是 hook 鉤子之類的,還專門搜索了下 tp 的 behavioir 和 laravel 的 event 區別,可惜沒找到...,不過兩者應該差不多的

tp 有 behavioir 這種機制,而且在 tp 內,內置了一些系統級別的 hook,但是 laravel 好像並沒有啊,這個我也簡單搜了下,好像沒有想要的,記得模型的一些操作好像有內置的 event,created、updated 等,laravel 系統好像沒。

那我們在哪個位置需要記錄日誌呢,fastadmin 是在 app_end - 應用結束,記錄的日誌,laravel 哪裏能判定應用結束,而且得是公共的地方

從 public/index.php,看過 laravel 源碼的,應該知道 laravel 的最簡單的執行機制是:
	
	/*
		$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

		$response = $kernel->handle(
		    $request = Illuminate\Http\Request::capture()
		);

		$response->send();

		$kernel->terminate($request, $response);
	 */

	實例化內核(kernel),將請求(request)傳遞給內核,供內核處理,得到響應(response),然後發送響應

看 public/index.php 的幾行代碼,就是這個意思,而且,在得到相應後,調用了 terminate(),這個是所有的 middleware 的 terminate 方法調用的時機。

我們記錄日誌,可以在入口文件的 $reponse 後,表示已經處理了請求,得到了相應,表示其實已經結束了,然後記錄日誌。

但是,覺得不應該修改 laravel 的核心代碼,不利於升級,而且 laravel 提供了更好的解決方法,就是上面說的 terminate():
	https://learnku.com/docs/laravel/6.x/middleware/5136#terminable-middleware

	Terminable 中間件

	它的執行時機就是:
		在準備好 HTTP 響應之後,中間件可能需要做一些工作。例如,Laravel 內置的「session」 中間件會在完全準備好響應後將會話數據寫入存儲。如果你在中間件上定義了一個 terminate 方法,並且你使用的是 FastCGI ,那麼它將會在響應準備發送到瀏覽器之後自動調用。

所以,我們就確定了日誌的位置。

所以,定義了一個 AdminLogMiddleware,記錄下過程中的幾個問題:
	1>只定義 terminate() 方法,不定義 handle() 方法,報錯:
		Function name must be a string

	2>terminate() 方法,調試過程中,輸出不了任何內容,這個其實就跟在 public/index.php 中,在 
		$response->send();

	  執行後面,也輸出不了任何內容,而 $kernel->terminate($request, $response); 也是在這後面,所以輸出不了內容

	3>輸出不了內容,如何調試,在 public/index.php 中調試(這個也是我在寫這邊筆記時,重新看了下 public/index.php 想到的。。。)
		我的調試,就是在 AdminLogMiddleware 中間件的 handle() 方法中調試的。

		/*
			這裏再強調個知識點,文檔中的:
				前置 & 後置中間件

			前置中間件:
				public function handle($request, Closure $next)
				{
					// 執行內容
					return $next($request);
				}

			後置中間件:
				public function handle($request, Closure $next)
				{
					$response = $next($request);

					// 執行內容

					return $response;
				}
		 */

		發現後置中間件,也是能獲取到響應的,但這裏的響應,是不是經歷了當前中間件處理後,然後得到的響應?還是所有請求結束後的響應內容?(應該是所有請求結束後的響應內容,因爲中間件,並非執行請求,只是處理請求前的,一道道過濾機制,或其他邏輯處理吧)

		還是文檔、源碼、整個機制不清楚。。。有時間我也得再好好啃,先把自己理解的記錄下來

	4>後置中間件 和 terminate() 方法,都可以收到 $response,但2者的區別到底是什麼,我還不是很清楚,但是目前看有一點是清楚的:
		後置中間件,我們得到 $response,可以輸出,而 terminate() 不行

	5>我們可以更優化下日誌處理,將其作爲一個 event 事件,解耦,邏輯還清晰。因爲可能以後還可能定義其他操作
		如果那樣的話,我們也不能定義爲 AdminLogMiddleware,但我們目前只想讓部分路由使用,中間件好像更好點。

		不過,我們也可以定義一個全局的日誌中間件,在中間件裏,通過路由匹配,來指定哪些路由想要記錄日誌

		關於這點發現好像自己都被繞進去了,是不是這麼架構合理...(架構知識很欠缺...)

臨時寫的筆記,有點亂,另外自身實力有限,勿怪,最後,記錄下代碼:
	數據庫遷移:
        Schema::create('admin_logs', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('admin_id')->unsigned()->comment('管理員ID');
            $table->string('url', 255)->comment('請求地址');
            $table->text('params')->comment('請求參數');
            $table->string('ip', 255)->comment('IP地址');
            $table->string('user_agent', 255)->comment('用戶代理');
            $table->string('content', 255)->comment('日誌描述');

            $table->timestamps();

            $table->index('admin_id');
            $table->index('ip');
        });

        DB::statement("ALTER TABLE `app_versions` comment '後臺管理日誌表'");

   	中間件 terminate() 方法: 
    public function terminate($request, $response)
    {
        $admin = Auth::guard('admin')->user();

        // 數據處理
        $admin_id = $admin ? $admin->id : 0;
        $method = $request->method();
        $uri = $request->path();

        // 1.過濾 uris(不記錄日誌的 uri)
        $guarded_uris = [
            'admin/ajax/lang',
        ];
        if(in_array($uri, $guarded_uris)){
            return true;
        }

        // 2.過濾 uris + method(某些 uri 的 get 和 post 一致,也需要過濾)
        $guards_get_method_uris = [
            'admin/account/login',
        ];
        if(in_array($uri, $guards_get_method_uris)){
            return true;
        }

        // 2.過濾 params(日誌中不記錄密碼等私密信息)
        $guarded_params = [
            'password',
        ];
        $params = $request->all();
        $params = array_filter($params, function ($key) use ($guarded_params) {
            return !in_array($key, $guarded_params);
        }, ARRAY_FILTER_USE_KEY);
        $params = json_encode($params, JSON_UNESCAPED_UNICODE);

        $ip = $request->getClientIp();
        $user_agent = Agent::getUserAgent();

        /*
        	這裏結合的是我係統裏的,記錄內容會根據權限菜單名來匹配
         */
        // 4.得到日誌內容(我們通過 uri 匹配相應的 '權限菜單 identifier',來獲取)
        // 1>後臺權限路由
        $admin_permission = AdminPermission::where('identifier', substr($uri, 6))->first();
        if($admin_permission){
            $id_chain = $admin_permission->id_chain;
            $names = AdminPermission::whereIn('id', explode('-', $id_chain))->pluck('name');
            $content = $names->join('-');

        // 2>後臺其他路由
        }else{
            switch($uri){
                case 'admin/account/login':
                    $content = '登錄';
                    break;

                case 'admin/account/logout':
                    $content = '退出登錄';
                    break;

                default:
                    $content = '其他操作';
                    break;
            }
        }

        $admin_log_data = [
            'admin_id' => $admin_id,
            'method' => $method,
            'uri' => $uri,
            'params' => $params,
            'ip' => $ip,
            'user_agent' => $user_agent,
            'content' => $content,
        ];
        AdminLog::create($admin_log_data);
    }

    最終的記錄結果:
		(`id`, `admin_id`, `uri`, `params`, `ip`, `user_agent`, `content`, `created_at`, `updated_at`)

		(26,1,'admin/auth/permission/index','{\"sort\":\"sortid\",\"order\":\"desc\",\"offset\":\"0\",\"limit\":\"10\",\"_\":\"1569680908796\"}','127.0.0.1','Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36','權限管理-菜單管理-列表','2019-09-28 14:40:43','2019-09-28 14:40:43');

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章