lumen 应用jwt

tech2022-09-03  115

lumen 应用jwt

In the intro, we covered the basics of PredictionIO and installed its dependencies. In this part, we’re going to build the movie recommendation app.

在介绍中,我们介绍了PredictionIO的基础知识并安装了其依赖项。 在这一部分中,我们将构建电影推荐应用程序。

环境配置文件 (Environment Config File)

Inside your app directory, create a .env file and add the following configuration:

在您的应用目录中,创建一个.env文件并添加以下配置:

APP_ENV=local APP_DEBUG=true APP_KEY=some-random-key PIO_KEY=your-pio-app-key TMDB_KEY=your-tmdb-api-key CACHE_DRIVER=file SESSION_DRIVER=file QUEUE_DRIVER=database

Make sure to replace the value of APP_KEY with a unique random key. Since we’re using Lumen, you can generate one by executing php artisan key:generate. Also, replace the value for PIO_KEY with the key of the PredictionIO app that you created, and the TMDB_KEY with the API key provided by the TMDB website.

确保用唯一的随机密钥替换APP_KEY的值。 由于我们使用的是Lumen,因此您可以通过执行php artisan key:generate来生成一个。 另外,将PIO_KEY的值替换为您创建的PredictionIO应用程序的密钥,并将TMDB_KEY替换为TMDB网站提供的API密钥。

从TMDB导入数据 (Importing Data from TMDB)

We will be importing the data using the PredictionIO SDK, so we first need to tell Lumen to use it. Create a Classes directory under lumen/app. Then inside it, create a Pio.php file and add the following code.

我们将使用PredictionIO SDK导入数据,因此我们首先需要告诉Lumen使用它。 在lumen/app下创建一个Classes目录。 然后在其中创建一个Pio.php文件,并添加以下代码。

<?php namespace App\Classes; use predictionio\EventClient; use predictionio\EngineClient; class Pio { public function eventClient() { $pio_accesskey = env('PIO_KEY'); $pio_eventserver = 'http://127.0.0.1:7070'; return new EventClient($pio_accesskey, $pio_eventserver); } public function predictionClient() { $pio_predictionserver = 'http://127.0.0.1:8192'; return new EngineClient($pio_predictionserver); } }

This class will serve as a container for the PredictionIO event client and engine client. This way, we don’t have to instantiate those classes every time we need to use them.

此类将用作PredictionIO事件客户端和引擎客户端的容器。 这样,我们不必在每次需要使用它们时都实例化这些类。

The event client is used for talking to the event server which is responsible for collecting data for our app. The client needs the app key and the URL the event server runs on. By default, it runs on port 7070.

事件客户端用于与事件服务器进行对话,事件服务器负责为我们的应用收集数据。 客户端需要应用密钥和事件服务器运行所在的URL。 默认情况下,它在端口7070上运行。

The engine client on the other hand, is used for talking to the engine which is responsible for serving the recommendations.

另一方面,引擎客户端用于与负责提供建议的引擎进行对话。

Next, open the bootstrap/app.php file and uncomment the middleware that is responsible for handling sessions. This allows us to persist a unique ID for the user who is currently rating some movies.

接下来,打开bootstrap/app.php文件,并取消注释负责处理会话的中间件。 这使我们能够为当前正在对某些电影评分的用户保留唯一的ID。

$app->middleware( [ //Illuminate\Cookie\Middleware\EncryptCookies::class, //Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, Illuminate\Session\Middleware\StartSession::class, //Illuminate\View\Middleware\ShareErrorsFromSession::class, //Laravel\Lumen\Http\Middleware\VerifyCsrfToken::class, ] );

Create a new controller and name it AdminController.php. Controllers are stored in the app/Http/Controllers directory. Set it to use the Pio class that we created earlier.

创建一个新的控制器,并将其命名为AdminController.php 。 控制器存储在app/Http/Controllers目录中。 将其设置为使用我们之前创建的Pio类。

<?php namespace App\Http\Controllers; use Laravel\Lumen\Routing\Controller as BaseController; use App\Repos\Pio; class AdminController extends BaseController { }

Create an importMovies method. We will use this method to import movies from the TMDB API:

创建一个importMovies方法。 我们将使用此方法从TMDB API导入电影:

public function importMovies(Pio $pio) { $index = 1; $pio_eventclient = $pio->eventClient(); $http_client = new \GuzzleHttp\Client(); $es_client = new \Elasticsearch\Client(); for ($x = 1; $x <= 100; $x++) { $movies_url = 'https://api.themoviedb.org/3/movie/popular?api_key=' . env( 'TMDB_KEY' ) . '&page=' . $x; $movies_response = $http_client->get($movies_url); $movies_body = $movies_response->getBody(); $movies_result = json_decode($movies_body, true); $movies = $movies_result['results']; if (!empty($movies)) { foreach ($movies as $row) { $id = $row['id']; $title = $row['title']; $poster_path = ''; if (!empty($row['poster_path'])) { $poster_path = $row['poster_path']; } $moviedetails_url = 'https://api.themoviedb.org/3/movie/' . $id . '?api_key=' . env( 'TMDB_KEY' ); $moviedetails_response = $http_client->get( $moviedetails_url ); $movie_details_body = $moviedetails_response->getBody(); $movie = json_decode($movie_details_body, true); $overview = $movie['overview']; $release_date = $movie['release_date']; $genre = ''; if (!empty($movie['genres'][0])) { $genre = $movie['genres'][0]['name']; } $popularity = $movie['popularity']; $movie_data = [ 'itypes' => 1, 'tmdb_id' => $id, 'title' => $title, 'poster_path' => $poster_path, 'overview' => $overview, 'release_date' => $release_date, 'genre' => $genre, 'popularity' => $popularity, ]; $pio_response = $pio_eventclient->setItem( $index, $movie_data ); //create elasticsearch index $params = []; $params['body'] = $movie_data; $params['index'] = 'movierecommendation_app'; $params['type'] = 'movie'; $params['id'] = $index; $es_res = $es_client->index($params); $index++; } } } }

Breaking it down:

分解:

We inject the Pio class to the importMovies method

我们将Pio类注入importMovies方法

Initialize the index to 1. This will serve as the unique ID for the movies that we’re going to import.

将索引初始化为1。这将用作我们要导入的电影的唯一ID。

Call the eventClient method in the Pio class. This initializes the PredictionIO event client which we can use for saving movie data.

在Pio类中调用eventClient方法。 这将初始化PredictionIO事件客户端,可用于保存电影数据。

Create a new instance of the Guzzle HTTP Client, the PredictionIO Event Client and the ElasticSearch Client.

创建Guzzle HTTP客户端,PredictionIO事件客户端和ElasticSearch客户端的新实例。

$index = 1; $pio_eventclient = $pio->eventClient(); $http_client = new \GuzzleHttp\Client(); $es_client = new \Elasticsearch\Client();

Create a loop that would execute 100 times. This allows us to get about 2000 movies because each request that we make to the TMDB API returns 20 movies. Each iteration of the loop changes the value for $x which we use for accessing the next page for each iteration.

创建一个将执行100次的循环。 这使我们可以获取大约2000部电影,因为对TMDB API的每个请求都返回20部电影。 循环的每次迭代都会更改$x的值,我们将使用该值访问每次迭代的下一页。

for ($x = 1; $x <= 100; $x++) { ... }

Inside the loop, we make a request to the TMDB API using Guzzle. Since we want to get as many likes as we can, we make a request for the most popular movies of all time. We get the value for api_key from the .env file. We then use the get method provided by Guzzle to perform the request. And then we get the response body using the getBody method. This is basically a JSON string containing all 20 movies and their details. We convert this to an array using json_decode and extract the results.

在循环内部,我们使用Guzzle向TMDB API发出请求。 由于我们希望获得尽可能多的喜欢,因此我们要求有史以来最受欢迎的电影。 我们从.env文件中获取api_key的值。 然后,我们使用Guzzle提供的get方法执行请求。 然后,我们使用getBody方法获得响应主体。 这基本上是一个JSON字符串,包含所有20部电影及其详细信息。 我们使用json_decode将其转换为数组并提取结果。

$movies_url = 'https://api.themoviedb.org/3/movie/popular?api_key=' . env('TMDB_KEY') . '&page=' . $x; $movies_response = $http_client->get($movies_url); $movies_body = $movies_response->getBody(); $movies_result = json_decode($movies_body, true); $movies = $movies_result['results'];

Check if it actually contains anything. If it does then we proceed with looping through all the movies returned so that we can get further details about it by making another request to the TMDB API. Once we’ve made the request we extract the details that we need.

检查它是否实际包含任何东西。 如果是这样,我们将继续遍历所有返回的电影,以便通过向TMDB API发出另一个请求来获取有关它的更多详细信息。 提出请求后,我们将提取所需的详细信息。

if (!empty($movies)) { foreach ($movies as $row) { $id = $row['id']; $title = $row['title']; $poster_path = ''; if (!empty($row['poster_path'])) { $poster_path = $row['poster_path']; } $moviedetails_url = 'https://api.themoviedb.org/3/movie/' . $id . '?api_key=' . env( 'TMDB_KEY' ); $moviedetails_response = $http_client->get($moviedetails_url); $movie_details_body = $moviedetails_response->getBody(); $movie = json_decode($movie_details_body, true); $overview = $movie['overview']; $release_date = $movie['release_date']; $genre = ''; if (!empty($movie['genres'][0]['name'])) { $genre = $movie['genres'][0]['name']; } $popularity = $movie['popularity']; } }

Construct an array that contains the details that we want to supply to PredictionIO and then call the setItem method to save it. This method accepts the unique ID that we want to assign to the item and the actual data as the second argument.

构造一个包含要提供给PredictionIO的详细信息的数组,然后调用setItem方法将其保存。 此方法接受我们要分配给项目的唯一ID和实际数据作为第二个参数。

$movie_data = array( 'itypes' => 1, 'tmdb_id' => $id, 'title' => $title, 'poster_path' => $poster_path, 'overview' => $overview, 'release_date' => $release_date, 'genre' => $genre, 'popularity' => $popularity ); $pio_response = $pio_eventclient->setItem($index, $movie_data);

Index the movie data in the ElasticSearch server. We will use this later on to show details of the random movies that we will recommend to the user as well as the actual recommendation that PredictionIO will return. Note that we’re using the $index as the ID so we also need to increment it for every iteration of the loop.

在ElasticSearch服务器中索引电影数据。 我们稍后将使用它来显示我们将向用户推荐的随机电影的详细信息以及PredictionIO将返回的实际推荐。 请注意,我们使用$index作为ID,因此我们还需要为循环的每次迭代增加它。

$params = array(); $params['body'] = $movie_data; $params['index'] = 'movierecommendation_app'; $params['type'] = 'movie'; $params['id'] = $index; $es_res = $es_client->index($params); $index++;

Finally, we can add the route that we will be accessing in order to begin importing some movies. Open up app/Http/routes.php file and add the following route:

最后,我们可以添加要访问的路由,以便开始导入一些电影。 打开app/Http/routes.php文件并添加以下路由:

$app->get('/movies/import', 'AdminController@importMovies');

After that, the /movies/import path becomes accessible in the browser to begin importing some movies from TMDB. If you don’t want to do it this way, you can also use commands.

之后,可以在浏览器中访问/movies/import路径,以开始从TMDB导入一些电影。 如果您不想这样做,也可以使用command 。

This might take a while to complete so go grab a cup of coffee or watch an episode of your favorite show.

这可能需要一段时间才能完成,所以去喝杯咖啡或观看您喜欢的节目的一集。

挑选随机电影 (Picking Random Movies)

Now that we have some movies, we’re ready to show some random ones to the user. First, create a new controller, name it HomeController.php then add the following code:

现在我们有了一些电影,我们准备向用户展示一些随机的电影。 首先,创建一个新的控制器,将其命名为HomeController.php然后添加以下代码:

<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Laravel\Lumen\Routing\Controller as BaseController; use App\Repos\Pio; class HomeController extends BaseController { public function index(Pio $pio) { } }

Inside the index method, generate a unique ID using PHP’s built-in uniqid method, then assign it to the user_id session item. Also, initialize movies_viewed with a value of 0. This will represent the number of movies that we have shown to the current user. We will increment it later on as random movies get suggested. Then, we use the event client to save the user into the database. We can do that by calling the setUser method which accepts the user ID as its argument. Finally, we render the index page.

在index方法内部,使用PHP的内置uniqid方法生成唯一ID,然后将其分配给user_id会话项。 另外,将movies_viewed初始化为0 。 这将代表我们向当前用户显示的电影数量。 我们将在以后根据随机电影的建议增加它的数量。 然后,我们使用事件客户端将用户保存到数据库中。 我们可以通过调用setUser方法来实现,该方法接受用户ID作为其参数。 最后,我们呈现index页面。

$user_id = uniqid(); session(array('user_id' => $user_id, 'movies_viewed' => 0)); $pio_eventclient = $pio->eventClient(); $pio_eventclient->setUser($user_id); return view('index');

Don’t forget to add the corresponding route in the routes file:

不要忘记在路由文件中添加相应的路由:

$app->get('/', 'HomeController@index');

Here’s the code for the index page that we we’re rendering:

这是我们正在渲染的index页面的代码:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" href="/assets/css/bootstrap.min.css"> <link rel="stylesheet" href="/assets/css/style.css"> </head> <body> <div id="wrapper"> <div class="navbar navbar-default"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-responsive-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="">Movie Recommender</a> </div> <div class="navbar-collapse collapse navbar-responsive-collapse"> <ul class="nav navbar-nav"> <li><a href="/">Home</a></li> </ul> </div> </div> <div class="container"> <div class="row"> <div id="movie-container" class="col-md-10 col-centered"> </div> </div> <script id="movie-template" type="text/x-handlebars-template"> <div class="col-md-8"> <img src="http://image.tmdb.org/t/p/w500{{_source.poster_path}}"> </div> <div class="col-md-4"> <h3>{{_source.title}}</h3> <div class="release-date"> {{_source.release_date}} </div> <div class="genre"> Genre: {{_source.genre}} </div> <div class="overview"> {{_source.overview}} </div> <div class="button-container"> <button class="btn btn-success btn-block btn-next" data-id="{{_id}}" data-action="like">Like</button> <button class="btn btn-danger btn-block btn-next" data-id="{{_id}}" data-action="dislike">Dislike</button> <a href="/movies/recommended" class="show-recommendations">Show Recommendations</a> </div> </div> </script> <span class="label label-success"></span> </div> </div> <script src="/assets/js/jquery.min.js"></script> <script src="/assets/js/bootstrap.min.js"></script> <script src="/assets/js/handlebars.min.js"></script> <script src="/assets/js/main.js"></script> </body> </html>

As you can see from the above code, we’re mainly using client-side templating to render the details for the movie. For this app, we’re using handlebars. We’re loading the details of each movie using ajax.

从上面的代码中可以看到,我们主要是使用客户端模板来呈现电影的细节。 对于此应用程序,我们使用的是handlebars 。 我们正在使用ajax加载每部电影的详细信息。

From the above code you can see that we’re using Bootstrap for styling. We also have basic styling for the whole app which is added in the style.css file:

从上面的代码中,您可以看到我们正在使用Bootstrap进行样式设置。 我们还为整个应用程序提供了基本样式,这些样式已添加到style.css文件中:

.col-centered { float: none; margin: 0 auto; } .button-container { margin-top: 20px; } .show-recommendations { display: none; } #recommended-movies > div { height: 1000px; }

For the scripts, we use jQuery, Bootstrap’s JavaScript file, Handlebars and the main JavaScript file for the app.

对于脚本,我们使用jQuery ,BootstrapJavaScript文件, Handlebars和应用程序的主JavaScript文件。

For the main JavaScript we have the following code:

对于主要JavaScript,我们有以下代码:

var movie_src = $("#movie-template").html(); var movie_template = Handlebars.compile(movie_src); function getRandomMovie(request_data){ request_data = typeof request_data !== 'undefined' ? request_data : {}; $.post('movie/random', request_data, function(response){ var data = JSON.parse(response); var movie_html = movie_template(data); $('#movie-container').html(movie_html); if(data.has_recommended){ $('.show-recommendations').show(); } }); } getRandomMovie(); $('#movie-container').on('click', '.btn-next', function(){ var self = $(this); var id = self.data('id'); var action = self.data('action'); getRandomMovie({'movie_id' : id, 'action' : action}); });

Breaking it down, we first compile the Handlebars template which is stored in the div with the ID of movie-template:

分解它,我们首先编译存储在div中的ID为movie-template的Handlebars movie-template :

var movie_src = $("#movie-template").html(); var movie_template = Handlebars.compile(movie_src);

We then declare the getRandomMovie method. This accepts the request_data as an optional parameter. Inside the function, we use jQuery’s post method to issue a POST request to the movie/random path. This returns random movie data from the server in JSON format. We then convert it to an object that can be used by JavaScript using the JSON.parse method. Once that’s done we supply it to the Handlebars template that we have compiled earlier and then update the contents of movie-container div. If the returned data has the has_recommended item, we show the link which will lead the user to the page where the movies recommended by PredictionIO are displayed.

然后,我们声明getRandomMovie方法。 这接受request_data作为可选参数。 在函数内部,我们使用jQuery的post方法向movie/random路径发出POST请求。 这将从服务器以JSON格式返回随机电影数据。 然后,我们使用JSON.parse方法将其转换为JavaScript可以使用的对象。 完成后,我们将其提供给我们先前编译的Handlebars模板,然后更新movie-container div的内容。 如果返回的数据具有has_recommended项,我们将显示链接,该链接将引导用户到显示PredictionIO推荐的电影的页面。

function getRandomMovie(request_data){ request_data = typeof request_data !== 'undefined' ? request_data : {}; $.post('movie/random', request_data, function(response){ var data = JSON.parse(response); var movie_html = movie_template(data); $('#movie-container').html(movie_html); if(data.has_recommended){ $('.show-recommendations').show(); } }); }

Once the script is loaded, we execute the function to load the first random movie.

加载脚本后,我们将执行该功能以加载第一个随机电影。

getRandomMovie();

We then listen for the click event on the button with the btn-next class. If you remember the overview of the app earlier, we have two buttons: like and dislike. Those buttons have the btn-next class. So every time those are clicked, the code below is executed. What it does is call the getRandomMovie function and supply the movie ID and the action. The action can have a value of either like or dislike:

然后,我们使用btn-next类监听按钮上的click事件。 如果您还记得以前的应用概述,我们有两个按钮: 喜欢和不喜欢 。 这些按钮具有btn-next类。 因此,每次单击它们时,都会执行以下代码。 它的作用是调用getRandomMovie函数并提供影片ID和动作。 动作的值可以为like或dislike :

$('#movie-container').on('click', '.btn-next', function(){ var self = $(this); var id = self.data('id'); var action = self.data('action'); getRandomMovie({'movie_id' : id, 'action' : action}); });

Going back to the server side, we’re now ready to write the code for getting a random movie from the database. First, declare a new route that responds to POST requests to the movie/random path:

回到服务器端,我们现在准备编写代码以从数据库中获取随机电影。 首先,声明一个新路由,该新路由响应对movie/random路径的POST请求:

$app->post('/movie/random', 'HomeController@randomMovie');

In the above code, we’re using the the same controller that we used earlier for rendering the home page of the app. But this time we’re using the randomMovie method. So go ahead and declare it in your app/Http/controllers/HomeController.php file. We’re going to make use of the Request class in this method so we pass it in as a parameter. This allows us to get the user input that was passed along in the HTTP request. And don’t forget to pass in the Pio class as well.

在上面的代码中,我们使用的控制器与我们之前渲染应用程序主页所使用的控制器相同。 但是这次我们使用randomMovie方法。 因此,继续在您的app/Http/controllers/HomeController.php文件中声明它。 我们将在此方法中使用Request类,以便将其作为参数传递。 这使我们能够获取在HTTP请求中传递的用户输入。 并且不要忘记也通过Pio类。

public function randomMovie(Request $request, Pio $pio) { ... }

Inside the randomMovie method:

在randomMovie方法内部:

We get the details of the request and then check if a user session has been set. If there’s a user session we get the number of movies that have been viewed by the current user.

我们获取请求的详细信息,然后检查是否设置了用户会话。 如果有用户会话,我们将获取当前用户已观看的电影数量。

Declare a new instance of the ElasticSearch client then we get a random movie by generating a random value from 1 to 1000 using PHP’s mt_rand function. If you remember from earlier, when we imported movies to ElasticSearch, we were using an index as the value for the ID, which we incremented by 1 for every iteration of the loop. That’s why this works. Once we get a response, we just extract the details that we need.

声明一个新的ElasticSearch客户端实例,然后使用PHP的mt_rand函数生成一个介于1到1000之间的随机值,从而获得一个随机电影。 如果您还记得以前,当我们将影片导入ElasticSearch时,我们使用的索引是ID的值,对于每次循环迭代,我们将其递增1。 这就是为什么这样。 得到答复后,我们只需提取所需的详细信息。

Check if there’s a movie_id supplied in the request. If there is, then it means that the user is rating a movie.

检查请求中movie_id提供了movie_id 。 如果存在,则表示用户正在对电影评分。

Call the recordUserActionOnItem method in the PredictionIO Event Client. This accepts the action as its first argument. If you remember, earlier we customized the engine so that it can accept like or dislike as a form of rating. That’s the action that we’re referring to. The second argument is the ID of the user performing the action, and the third is the ID of the movie being rated.

在PredictionIO事件客户端中调用recordUserActionOnItem方法。 这将动作作为其第一个参数。 如果您还记得的话,我们之前对引擎进行了自定义,因此它可以接受喜欢或不喜欢作为一种评级方式。 这就是我们所指的动作。 第二个参数是执行操作的用户的ID,第三个参数是要评级的电影的ID。

Increment the movies viewed and then check if there are already 20 movies that were viewed. If so, then we pass in an additional field has_recommended to the movie details. The existence of this field will then be checked on the client side to show the link for the recommendation page. Next, we save the movies viewed into session and then return the movie details.

增加观看的电影,然后检查是否已经观看了20部电影。 如果是这样,那么我们将附加字段has_recommended传递给电影详细信息。 然后,将在客户端检查此字段的存在以显示推荐页面的链接。 接下来,我们将观看的电影保存到会话中,然后返回电影详细信息。

if (session('user_id')) { $movies_viewed = session('movies_viewed'); $es_client = new \Elasticsearch\Client(); $search_params['index'] = 'movierecommendation_app'; $search_params['type'] = 'movie'; $search_params['id'] = mt_rand(1, 1000); $movie = $es_client->get($search_params); if (!empty($request->input('movie_id'))) { $user_id = session('user_id'); $movie_id = $request->input('movie_id'); $action = $request->input('action'); $pio_eventclient = $pio->eventClient(); $pio_eventclient->recordUserActionOnItem($action, $user_id, $movie_id); $movies_viewed += 1; if ($movies_viewed == 20) { $movie['has_recommended'] = true; } $movie['movies_viewed'] = $movies_viewed; session(['movies_viewed' => $movies_viewed]); } return $movie; }

推荐电影 (Recommending Movies)

Now that we’re done with the learning phase, it’s time to proceed with writing the code for the recommendation phase.

现在我们已经完成了学习阶段,是时候为推荐阶段编写代码了。

First, create a new route that will respond to GET requests on the /movies/recommended path:

首先,在/movies/recommended路径上创建一个新的路由来响应GET请求:

$app->get('/movies/recommended', 'HomeController@recommendedMovies');

Inside the recommendedMovies method:

里面的recommendedMovies方法:

Create a new instance of the PredictionIO Engine Client. Note that this is different from the Event Client that we have been using so far as this is used for actually getting the prediction results from the engine. With that in mind, we now make the request using the sendQuery method. This accepts an array as its argument. The array should contain the user and num as its items. user is the user ID and num is the number of movies that we want the engine to return.

创建PredictionIO Engine客户端的新实例。 请注意,这与我们一直使用的事件客户端不同,因为它实际上是用于从引擎获取预测结果的。 考虑到这一点,我们现在使用sendQuery方法发出请求。 这接受一个数组作为其参数。 该数组应包含user和num作为其项。 user是用户ID, num是我们希望引擎返回的电影数量。

If the request is successful, we use the array_map method to extract only the movie IDs. The array_map method accepts a function which returns the item that we want and the array that we want to manipulate as its second argument. This returns an array of movie IDs.

如果请求成功,我们将使用array_map方法仅提取影片ID。 array_map方法接受一个函数,该函数返回我们想要的项目和我们要操作的数组作为第二个参数。 这将返回电影ID的数组。

Create a new instance of the ElasticSearch client and perform a request to the movierecommendation_app index. We can then pass the movie IDs as a search query. Next we use the search method and pass in the search parameters. This returns the details of the movies that were returned by the PredictionIO Engine Client.

创建ElasticSearch客户端的新实例,然后对movierecommendation_app索引执行请求。 然后,我们可以将电影ID作为搜索查询传递。 接下来,我们使用search方法并传入搜索参数。 这将返回PredictionIO Engine客户端返回的电影的详细信息。

Reset the movies viewed count and set the user ID to null so that the next time someone uses the app, it will create a brand new user. Finally, we render the recommended_movies view and pass in the movie details.

重置电影观看次数,并将用户ID设置为null以便下次有人使用该应用时,它将创建一个全新的用户。 最后,我们呈现recommended_movies在电影细节查看和传递。

public function recommendedMovies(Pio $pio){ $recommended_movies = array(); try{ $user_id = session('user_id'); $pio_predictionclient = $pio->predictionClient(); $recommended_movies_raw = $pio_predictionclient->sendQuery(array('user' => $user_id, 'num' => 9)); $movie_ids = array_map(function($item){ return $item['item']; }, $recommended_movies_raw['itemScores']); $es_client = new \Elasticsearch\Client(); $search_params['index'] = 'movierecommendation_app'; $search_params['type'] = 'movie'; $search_params['body']['query']['bool']['must']['terms']['_id'] = $movie_ids; $es_response = $es_client->search($search_params); $recommended_movies = $es_response['hits']['hits']; }catch(Exception $e){ echo 'Caught exception: ', $e->getMessage(), "\n"; } session(array('movies_viewed' => 0, 'user_id' => null)); return view('recommended_movies', array('recommended_movies' => $recommended_movies)); }

Here’s the HTML for the recommended movies page:

这是推荐电影页面HTML:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" href="/assets/css/bootstrap.min.css"> <link rel="stylesheet" href="/assets/css/style.css"> </head> <body> <div id="wrapper"> <div class="navbar navbar-default"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-responsive-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="">Movie Recommender</a> </div> <div class="navbar-collapse collapse navbar-responsive-collapse"> <ul class="nav navbar-nav"> <li><a href="/">Home</a></li> </ul> </div> </div> <div class="container"> <div class="row"> <h1>Recommended Movies</h1> <div id="recommended-movies" class="col-md-12"> <?php foreach($recommended_movies as $rm){ ?> <div class="col-md-6"> <img src="http://image.tmdb.org/t/p/w500<?php echo $rm['_source']['poster_path'] ?>" alt="<?php echo $rm['_source']['title'] ?>"> <h4><?php echo $rm['_source']['title']; ?></h4> <div class="release-date"> <?php echo $rm['_source']['release_date']; ?> </div> <div class="genre"> <?php echo $rm['_source']['genre']; ?> </div> <div class="overview"> <?php echo $rm['_source']['overview']; ?> </div> </div> <?php } ?> </div> </div> </div> </div> </body> </html>

What we’re doing above is looping through the $recommended_movies array and then echoing out the values for the relevant fields: the title, release date, genre, overview and the image.

上面我们要做的是遍历$recommended_movies数组,然后回显相关字段的值:标题,发行日期,类型,概述和图像。

部署引擎 (Deploying the Engine)

At this point, we are ready to deploy the engine – we need to assign the app to it. To do that, navigate to the directory where your engine is saved and open the engine.json file. It should look something like this:

至此,我们准备部署引擎–我们需要为其分配应用程序。 为此,请导航至保存引擎的目录,然后打开engine.json文件。 它看起来应该像这样:

{ "id": "default", "description": "Default settings", "engineFactory": "wern.RecommendationEngine", "datasource": { "params" : { "appName": "INVALID_APP_NAME" } }, "algorithms": [ { "name": "als", "params": { "rank": 10, "numIterations": 20, "lambda": 0.01, "seed": 3 } } ] }

We need to change two things: the appName under the datasource.params object and the appId on that same object. If you’re not sure what the app ID is, you can execute the pio app list command in your terminal. It should look something like this:

我们需要更改两件事: datasource.params对象下的appName和同一对象上的appId 。 如果不确定应用程序ID是什么,则可以在终端中执行pio app list命令。 它看起来应该像这样:

Just copy the value under the ID column.

只需复制ID列下的值即可。

After updating, your engine.json file should look something like this:

更新后,您的engine.json文件应如下所示:

{ "id": "default", "description": "Default settings", "engineFactory": "wern.RecommendationEngine", "datasource": { "params" : { "appId": 1, "appName": "MovieRecommendationApp" } }, "algorithms": [ { "name": "als", "params": { "rank": 10, "numIterations": 20, "lambda": 0.01, "seed": 3 } } ] }

Next, we need to build the engine by executing the pio build command in the root directory of your engine. This downloads all the files that the engine needs and bakes them into the engine. This might take a while depending on your computer’s speed and internet connection. I recommend adding the --verbose option so that you can see exactly what’s going on.

接下来,我们需要通过在引擎的根目录中执行pio build命令来构建引擎。 这将下载引擎所需的所有文件,并将它们烘焙到引擎中。 这可能需要一段时间,具体取决于您计算机的速度和互联网连接。 我建议添加--verbose选项,以便您可以准确了解正在发生的事情。

Once that’s done, it should show something similar to the following:

完成后,它应该显示类似于以下内容:

[INFO] [Console$] Your engine is ready for training.

Once you see that, you can access the app in your browser and start liking and disliking movies until it shows the link that leads to the movie recommendation page. To train the data, you need to execute the pio train command. Make sure you’re still in the engine directory when you execute this.

看到该内容后,您便可以在浏览器中访问该应用程序,开始喜欢和不喜欢电影,直到它显示指向电影推荐页面的链接。 要训​​练数据,您需要执行pio train命令。 执行此操作时,请确保您仍在引擎目录中。

If the training completed successfully it should show something like this:

如果培训成功完成,则应显示以下内容:

[INFO] [CoreWorkflow$] Training completed successfully.

If not, you probably got something like the following:

如果没有,您可能会得到类似以下内容的信息:

[ERROR] [Executor] Exception in task 0.0 in stage 42.0

If that’s the case, you can try changing the numIterations under the algorithms property of your engine.json file. For me, changing it to 10 worked.

如果是这种情况,您可以尝试在engine.json文件的algorithms属性下更改numIterations 。 对我来说,将其更改为10是可行的。

"algorithms": [ { "name": "als", "params": { "rank": 10, "numIterations": 10, "lambda": 0.01, "seed": 3 } } ]

The error has something to do with the input data and the available memory in the computer: the problem is that Apache Spark needs a lot of memory in order to train the data for a specific number of iterations. The fewer iterations are required, the less memory it will need. If your system doesn’t have the memory required, you get that error. That’s why decreasing the number of iterations works.

该错误与输入数据和计算机中的可用内存有关:问题是Apache Spark需要大量内存才能为特定数量的迭代训练数据。 所需的迭代次数越少,所需的内存就越少。 如果您的系统没有所需的内存,则会出现该错误。 这就是减少迭代次数的原因。

Next, you can deploy the engine with the pio deploy --port 8192 command. This will deploy the recommendation engine and it will be accessible at port 8192 of your computer. It should show some information about the engine and the server when you access http://IP:8192 in your browser, where IP is your local machine’s IP (192.168.10.10 in the case of Homestead Improved). You can also go back to the browser and access the movie recommendation page. It should now contain some movie recommendations.

接下来,您可以使用pio deploy --port 8192命令部署引擎。 这将部署推荐引擎,并且可以在计算机的端口8192上访问它。 当您在浏览器中访问http://IP:8192 ,它应该显示有关引擎和服务器的一些信息,其中IP是您本地计算机的IP(对于Homestead改良版为192.168.10.10 )。 您也可以返回浏览器并访问电影推荐页面。 现在它应该包含一些电影推荐。

Now you can add the pio train and pio deploy commands to your crontab so that it executes every 5 minutes. This way it constantly runs in the background to train new data and deploys when ready.

现在,您可以将pio train和pio deploy命令添加到crontab中 ,以使其每5分钟执行一次。 这样,它会在后台不断运行,以训练新数据并在准备就绪时进行部署。

*/5 * * * * cd /path/to/engine; pio train */5 * * * * cd /path/to/engine; pio deploy

结论 (Conclusion)

In this tutorial, you have learned how to use PredictionIO to provide machine learning capabilities to your PHP app. You can check out this project’s Github repo to see the full source.

在本教程中,您学习了如何使用PredictionIO为PHP应用程序提供机器学习功能。 您可以查看该项目的Github存储库以查看完整源代码。

We’ve barely scratched the surface in this series and there’s much more that you can do with PredictionIO. I recommend that you check out the official docs if you want to learn more.

在本系列文章中,我们几乎没有涉及任何内容,您可以使用PredictionIO做更多的事情。 如果您想了解更多信息,我建议您查看官方文档 。

If you know of any alternatives to PredictionIO or some interesting use cases, let us know in the comments!

如果您知道PredictionIO的替代方案或一些有趣的用例,请在评论中告诉我们!

翻译自: https://www.sitepoint.com/predictionio-and-lumen-building-a-movie-recommendation-app/

lumen 应用jwt

相关资源:Laravel (Lumen) 解决JWT-Auth刷新token的问题
最新回复(0)