使用 Laravel 计算页面浏览量

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/45422497/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 16:25:16  来源:igfitidea点击:

Counting page views with Laravel

laravellaravel-5

提问by Ariando Miller

I want to implement page view counter in my app. What I've done so far is using this method :

我想在我的应用程序中实现页面查看计数器。到目前为止我所做的是使用这种方法:

public function showpost($titleslug) {
        $post = Post::where('titleslug','=',$titleslug)->firstOrFail();
        $viewed = Session::get('viewed_post', []);
        if (!in_array($post->id, $viewed)) {
            $post->increment('views');
            Session::push('viewed_post', $post->id);
        }
        return view('posts/show', compact('post', $post));
    }

I retrieve the popular posts list like this :

我像这样检索流行的帖子列表:

$popular_posts = Post::orderBy('views', 'desc')->take(10)->get();

However, I'd like to know if there are any better ways to do this ? And with my current method, can I get a list of most viewed posts in the past 24 hours ? That's all and thanks!

但是,我想知道是否有更好的方法来做到这一点?使用我目前的方法,我可以获得过去 24 小时内查看次数最多的帖子的列表吗?这就是全部,谢谢!

回答by Jean Marcos

As quoted in @ milo526's comment, you can record all hits to your pages in a unique way instead of an increment. With this you have many possibilities to search for access information, including the listing of the posts sorted by most viewed.

正如@ milo526 的评论中所引用的那样,您可以以独特的方式而不是增量记录页面的所有点击。有了它,您就有多种可能来搜索访问信息,包括按查看次数最多的帖子列表。

Create a table to save your view records:

创建一个表来保存您的视图记录:

Schema::create("posts_views", function(Blueprint $table)
        {
            $table->engine = "InnoDB";

            $table->increments("id");
            $table->increments("id_post");
            $table->string("titleslug");
            $table->string("url");
            $table->string("session_id");
            $table->string("user_id");
            $table->string("ip");
            $table->string("agent");
            $table->timestamps();
        });

Then, create the corresponding model:

然后,创建相应的模型:

<?php namespace App\Models;

class PostsViews extends \Eloquent {

    protected $table = 'posts_views';

    public static function createViewLog($post) {
            $postsViews= new PostsViews();
            $postsViews->id_post = $post->id;
            $postsViews->titleslug = $post->titleslug;
            $postsViews->url = \Request::url();
            $postsViews->session_id = \Request::getSession()->getId();
            $postsViews->user_id = \Auth::user()->id;
            $postsViews->ip = \Request::getClientIp();
            $postsViews->agent = \Request::header('User-Agent');
            $postsViews->save();
    }

}

Finally, your method:

最后,你的方法:

public function showpost($titleslug)
{
    $post = PostsViews::where('titleslug', '=' ,$titleslug)->firstOrFail();

    PostsViews::createViewLog($post);

    //Rest of method...
}

To search the most viewed posts in the last 24 hours:

要搜索过去 24 小时内查看次数最多的帖子:

$posts = Posts::join("posts_views", "posts_views.id_post", "=", "posts.id")
            ->where("created_at", ">=", date("Y-m-d H:i:s", strtotime('-24 hours', time())))
            ->groupBy("posts.id")
            ->orderBy(DB::raw('COUNT(posts.id)', 'desc'))
            ->get(array(DB::raw('COUNT(posts.id) as total_views'), 'posts.*'));

Note that in PostsViews, you have data that can help further filter your listing, such as the session id, in case you do not want to consider hits from the same session.

请注意,在 PostsViews 中,您有一些数据可以帮助进一步过滤您的列表,例如会话 ID,以防您不想考虑来自同一会话的点击。

You may need to adapt some aspects of this solution to your final code.

您可能需要将此解决方案的某些方面调整到您的最终代码中。

回答by Learner

2020 Update

2020 更新

First of all, thanks a lot "Jean Marcos" for his awesome answer. All credit goes to him, I am just pasting a slightly modified answer combining my knowledge of Laravel as well.

首先,非常感谢“Jean Marcos”的精彩回答。所有的功劳都归功于他,我只是粘贴了一个稍微修改过的答案,结合我对 Laravel 的了解。

Create a table to save your view records and name it with snake_case plural: post_views

创建一个表来保存你的视图记录并用snake_case复数命名:post_views

Schema::create("post_views", function(Blueprint $table)
{
      $table->engine = "InnoDB";//this is basically optional as you are not using foreign key relationship so you could go with MyISAM as well

      $table->increments("id");

      //please note to use integer NOT increments as "Jean Marcos' answer" because it will throw error "Incorrect table definition; there can be only one auto column and it must be defined as a key" when running migration.
      $table->unsignedInteger("post_id");//note that the Laravel way of defining foreign keys is "table-singular-name_id", so it's preferable to use that

      $table->string("titleslug");
      $table->string("url");
      $table->string("session_id");
      $table->unsignedInteger('user_id')->nullable();//here note to make it nullable if your page is accessible publically as well not only by logged in users. Also its more appropriate to have "unsignedInteger" type instead of "string" type as mentioned in Jean Marcos' answer because user_id will save same data as id field of users table which in most cases will be an auto incremented id.
      $table->string("ip");
      $table->string("agent");
      $table->timestamps();
});

Then, create the corresponding model. Please note to create "camelCase" model name with the first capital letter and singular form of the tableso it should be like: PostView

然后,创建相应的模型。请注意使用表格的第一个大写字母和单数形式创建“camelCase”模型名称,因此它应该像:PostView

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class PostView extends Model
{
    public static function createViewLog($post) {
        $postViews= new PostView();
        $postViews->listing_id = $post->id;
        $postViews->url = \Request::url();
        $postViews->session_id = \Request::getSession()->getId();
        $postViews->user_id = (\Auth::check())?\Auth::id():null; //this check will either put the user id or null, no need to use \Auth()->user()->id as we have an inbuild function to get auth id
        $postViews->ip = \Request::getClientIp();
        $postViews->agent = \Request::header('User-Agent');
        $postViews->save();//please note to save it at lease, very important
    }
}

Then run the migration to generate this table

然后运行迁移以生成此表

php artisan migrate

Finally, your method:

最后,你的方法:

public function showpost($titleslug)
{
    $post = PostView::where('titleslug', '=' ,$titleslug)->firstOrFail();

    \App\PostView::createViewLog($post);//or add `use App\PostView;` in beginning of the file in order to use only `PostView` here 

    //Rest of method...
}

To search the most viewed posts in the last 24 hours:

要搜索过去 24 小时内查看次数最多的帖子:

$posts = Posts::join("post_views", "post_views.id_post", "=", "posts.id")
            ->where("created_at", ">=", date("Y-m-d H:i:s", strtotime('-24 hours', time())))
            ->groupBy("posts.id")
            ->orderBy(DB::raw('COUNT(posts.id)'), 'desc')//here its very minute mistake of a paranthesis in Jean Marcos' answer, which results ASC ordering instead of DESC so be careful with this line
            ->get([DB::raw('COUNT(posts.id) as total_views'), 'posts.*']);

Note that in PostView, you have data that can help further filter your listing, such as the session id, in case you do not want to consider hits from the same session.

请注意,在 PostView 中,您有一些数据可以帮助进一步过滤您的列表,例如会话 ID,以防您不想考虑来自同一会话的命中。

You may need to adapt some aspects of this solution to your final code.

您可能需要将此解决方案的某些方面调整到您的最终代码中。

So those were few modifications I wanted to point out, also you might want to put an additional column client_internet_ipin which you can store \Request::ip()which can be used as a filter as well if required.

所以这些是我想指出的一些修改,您可能还想放置一个额外的列client_internet_ip,您可以在其中存储\Request::ip()它,如果需要,它也可以用作过滤器。

I hope it helps

我希望它有帮助

回答by Ardati

2020 Update (2)/ With Eloquent Relationships for Laravel 6

2020 更新(2)/ 与 Laravel 6 的 Eloquent 关系

If you don't want to add a package to your application. I have developed the following solution based on "Jean Marcos" and "Learner" contribution to the question and my own research.

如果您不想向应用程序添加包。我根据“Jean Marcos”和“Learner”对问题的贡献以及我自己的研究开发了以下解决方案。

All credit goes to "Jean Marcos" and "Learner", I felt like I should do the same as Learner and update the code in a way the would be beneficial to others.

所有功劳都归功于“Jean Marcos”和“Learner”,我觉得我应该和Learner一样,并以对其他人有益的方式更新代码。

First of all, make sure you have a sessions table in the database. Otherwise, follow the steps in Laravel documentations to do so: HTTP Session

首先,确保您在数据库中有一个会话表。否则,请按照 Laravel 文档中的步骤进行操作:HTTP Session

Make sure that the sessions are stored in the table. If not, make sure to change the SESSION_DRIVER variable at the .env set to 'database' not 'file' and do composer dump-autoload afterwards.

确保会话存储在表中。如果没有,请确保将 .env 中的 SESSION_DRIVER 变量设置为“数据库”而不是“文件”,然后执行 composer dump-autoload。

After that, you are all set to go. You can start by running the following console command:

之后,你就可以开始了。您可以首先运行以下控制台命令:

php artisan make:model PostView -m 

This will generate both the model and migration files.

这将生成模型和迁移文件。

Inside of the migration file put the following Schema. Be cautious with the columns names. For example, my posts table have the "slug" column title name instead of the "titleslug" which was mentioned in the question.

在迁移文件中放置以下 Schema。小心列名。例如,我的帖子表具有“slug”列标题名称,而不是问题中提到的“titleslug”。

  Schema::create('post_views', function (Blueprint $table) {

        $table->increments("id");
        $table->unsignedInteger("post_id");
        $table->string("titleslug");
        $table->string("url");
        $table->string("session_id");
        $table->unsignedInteger('user_id')->nullable();
        $table->string("ip");
        $table->string("agent");
        $table->timestamps();
    });

Then put the following code inside the PostView model file.

然后将以下代码放入 PostView 模型文件中。

<?php

namespace App;

use App\Post;
use Illuminate\Database\Eloquent\Model;

class PostView extends Model
{

    public function postView()
    {
        return $this->belongsTo(Post::class);
    }

    public static function createViewLog($post) {
        $postViews= new PostView();
        $postViews->post_id = $post->id;
        $postViews->slug = $post->slug;
        $postViews->url = request()->url();
        $postViews->session_id = request()->getSession()->getId();
        $postViews->user_id = (auth()->check())?auth()->id():null; 
        $postViews->ip = request()->ip();
        $postViews->agent = request()->header('User-Agent');
        $postViews->save();
    }
}

Now inside the Post model write the following code. This to create the relation between the posts table and the post_views table.

现在在 Post 模型中编写以下代码。这将创建 posts 表和 post_views 表之间的关系。

use App\PostView;

   public function postView()
    {
        return $this->hasMany(PostView::class);
    }

In the same Post model you should put the following code. If the user is not logged in the the code will test the IP match. Otherwise, it will test both the session ID and the user ID as each user might have multiple sessions.

在同一个 Post 模型中,您应该放置以下代码。如果用户未登录,代码将测试 IP 匹配。否则,它将测试会话 ID 和用户 ID,因为每个用户可能有多个会话。

public function showPost()
{
    if(auth()->id()==null){
        return $this->postView()
        ->where('ip', '=',  request()->ip())->exists();
    }

    return $this->postView()
    ->where(function($postViewsQuery) { $postViewsQuery
        ->where('session_id', '=', request()->getSession()->getId())
        ->orWhere('user_id', '=', (auth()->check()));})->exists();  
}

You are ready now to run the migration.

您现在已准备好运行迁移。

php artisan migrate

When the user ask for the post. The following function should be targeted inside the PostController file:

当用户要求发布时。应在 PostController 文件中定位以下函数:

use App\PostView;

     public function show(Post $post)
        {
//Some bits from the following query ("category", "user") are made for my own application, but I felt like leaving it for inspiration. 
            $post = Post::with('category', 'user')->withCount('favorites')->find($post->id);

            if($post->showPost()){// this will test if the user viwed the post or not
                return $post;
            }

            $post->increment('views');//I have a separate column for views in the post table. This will increment the views column in the posts table.
            PostView::createViewLog($post);
            return $post;
        }

As I have a separate column for views in the post table. To search the most viewed posts in the last 24 hours you write this code in the controller. Remove paginate if you don't have pagination:

因为我在 post 表中有一个单独的视图列。要搜索过去 24 小时内查看次数最多的帖子,您可以在控制器中编写此代码。如果没有分页,请删除分页:

public function mostViwedPosts()
{
    return Posts::with('user')->where('created_at','>=', now()->subdays(1))->orderBy('views', 'desc')->latest()->paginate(5);
}

I hope this would help/save someones time.

我希望这会帮助/节省某人的时间。

回答by user33192

Eloquent Viewablepackage can be used for this purpose. It provides more flexible ways to do stuff like this(counting page views).

Eloquent Viewable包可用于此目的。它提供了更灵活的方法来做这样的事情(计算页面浏览量)。

Note:The Eloquent Viewable package requires PHP 7+ and Laravel 5.5+.

注意:Eloquent Viewable 包需要 PHP 7+ 和 Laravel 5.5+。

Make Model viewable:

使模型可见:

Just add the Viewabletrait to the model definition like:

只需将Viewable特征添加到模型定义中,例如:

use Illuminate\Database\Eloquent\Model;
use CyrildeWit\EloquentViewable\Viewable;

class Post extends Model
{
    use Viewable;

    // ...
}

Then in the controller:

然后在控制器中:

public function show(Post $post)
{
    $post->addView();

    return view('blog.post', compact('post'));
}

After that you can do stuff like this:(see package installation guide for more details)

之后,您可以执行以下操作:(有关更多详细信息,请参阅软件包安装指南

// Get the total number of views
$post->getViews();

// Get the total number of views since the given date
$post->getViews(Period::since(Carbon::parse('2014-02-23 00:00:00')));

// Get the total number of views between the given date range
$post->getViews(Period::create(Carbon::parse('2014-00-00 00:00:00'), Carbon::parse('2016-00-00 00:00:00')));

// Get the total number of views in the past 6 weeks (from today)
$post->getViews(Period::pastWeeks(6));

// Get the total number of views in the past 2 hours (from now)
$post->getViews(Period::subHours(2));

// Store a new view in the database
$post->addView();

Implements same kind of idea as in the accepted answer, but provides more features and flexibilities.

实现与接受的答案相同的想法,但提供更多功能和灵活性。