Содержание


Использование IBM Cloud и PHP для создания базы данных резюме с функцией поиска, часть 1

Разработка оптимизированного для мобильных устройств, управляемого данными приложения для поиска резюме

Начните с индексации содержимого PDF-файлов с использованием микросреды PHP Slim в сочетании с Bootstrap и Cloud

Comments

Серия контента:

Этот контент является частью # из серии # статей: Использование IBM Cloud и PHP для создания базы данных резюме с функцией поиска, часть 1

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Использование IBM Cloud и PHP для создания базы данных резюме с функцией поиска, часть 1

Следите за выходом новых статей этой серии.

Работа менеджеров по подбору и найму персонала — неблагодарное занятие. Компании требуют от них поиска специалистов с конкретной квалификацией (обычно срочно), а кандидатам нужна работа с максимальной заработной платой и полным социальным пакетом. Им необходимо просеивать огромные объемы данных в попытке подобрать подходящего кандидата. Обычно такие данные содержатся в резюме, онлайновых профилях и записях собеседований.

Я покажу вам, как создать web-приложение, позволяющее перегруженным работой специалистам по подбору и найму персонала проще хранить резюме и находить среди них идеальный вариант для клиента или работодателя.

Я также расскажу о некоторых интересных службах IBM Cloud® и покажу, как развернуть готовое приложение на платформе IBM Cloud.

Запустить приложениеПолучить код с GitHub

Что вам потребуется

Описываемое приложение позволяет пользователям загружать со своих компьютеров резюме в виде PDF-файлов в онлайновое хранилище документов. Как только резюме загружено, его содержимое автоматически извлекается и сохраняется в поисковом индексе. Приложение также предлагает интерфейс поиска по индексу, чтобы пользователи могли выбирать резюме по значимым ключевым словам (например, PHP или Node.js для базы данных резюме разработчиков) и быстро находить кандидатов, навыки которых могут соответствовать требованиям компании. Конечно же, приложение оптимизировано для мобильных устройств и может использоваться как на смартфонах, так и на настольных компьютерах.

Работа приложения основывается на использовании двух служб, доступных в IBM Cloud:

  • Служба Object Storage предоставляет защищенное онлайновое хранилище для загруженных резюме
  • Служба Searchly обеспечивает индексацию и поиск в реальном времени.

Интерфейс приложения создается с использованием Bootstrap — многофункционального инструмента для разработки мобильных пользовательских интерфейсов. Для управления потоком приложения будет использоваться микросреда PHP Slim, для извлечения содержимого PDF-файлов — библиотека PDF Parser, для инфраструктуры и развертывания — платформа IBM Cloud.

Как видите, используется множество технологий, и вот что вам потребуется:

Примечание: Любое приложение, использующее службу Searchly, должно удовлетворять требованиям Searchly Terms of Service. Любое приложение, использующее службу Object Storage, должно удовлетворять условиям ее использования. Перед тем как приступить к проекту, уделите несколько минут изучению этих требований и убедитесь в том, что ваше приложение им соответствует.

Шаг 1. Создание каркаса приложения

Первым шагом является инициализация базового приложения с микросредой Slim. Для извлечения содержимого PDF-файлов, использования индекса Elasticsearch и доступа к хранилищу IBM Cloud Object Storage необходимы дополнительные пакеты. Все эти компоненты можно легко загрузить и установить с использованием менеджера PHP-зависимостей Composer. Используйте предлагаемый файл конфигурации Composer, который нужно сохранить в каталог $APP_ROOT/composer.json (где $APP_ROOT — это каталог вашего проекта):

{
    "require": {
        "php": ">=5.6.0",
        "slim/slim": "^3.1",
        "slim/php-view": "^2.0",
        "smalot/pdfparser": "*",
        "elasticsearch/elasticsearch": "~1.0",
        "php-opencloud/openstack": "*"
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}

Следующей командой выполните установку с использованием Composer:

shell> php composer.phar install

Как только необходимые компоненты будут загружены через Composer, создайте следующие каталоги: $APP_ROOT/public — для всех файлов, доступных из браузера, $APP_ROOT/templates — для всех представлений и $APP_ROOT/src — для всех конфигурационных и не общедоступных файлов (где $APP_ROOT — это каталог приложения).

shell> cd myapp
shell> mkdir src public templates

Создайте файл $APP_ROOT/src/settings.php со следующим содержимым:

<?php
return [
    'settings' => [
        'displayErrorDetails' => true, // изменить на false в рабочей среде
        'addContentLengthHeader' => false, // разрешение web-серверу отправлять заголовок content-length header

        // настройки рендеринга
        'renderer' => [
            'template_path' => '../templates/',
        ],
        
        'indexer' => [

        ],
        
        'object-store' => [

        ],
        
    ],
];

Следующим шагом является создание сценария контроллера, который будет инициализировать среду Slim. Он также будет содержать обратные вызовы для всех маршрутов приложения. Каждый обратный вызов определяет код, который должен выполняться, если маршрут соответствует входящему запросу. Создайте файл сценария $APP_ROOT/public/index.php со следующим содержимым:

<?php
set_time_limit(600);

use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Client;

// загрузка требуемых файлов
require __DIR__ . '/../vendor/autoload.php';

// инициализация приложения
$settings = require '../src/settings.php';

$app = new \Slim\App($settings);

// конфигурирование зависимостей
$container = $app->getContainer();

// отображение представлений
$container['renderer'] = function ($c) {
  $config = $c->get('settings');
  return new Slim\Views\PhpRenderer($config['renderer']['template_path']);
};


$app->get('/', function ($request, $response, $args) {
  // вставьте код сюда 
});

$app->get('/index', function ($request, $response, $args) {
  // вставьте код сюда 
})->setName('index');

$app->get('/add', function ($request, $response, $args) {
  // вставьте код сюда 
})->setName('add');

$app->post('/add', function ($request, $response, $args) {
  // вставьте код сюда 
});

$app->get('/search', function ($request, $response, $args) {
  // вставьте код сюда 
})->setName('search');

$app->get('/download/{id}', function ($request, $response, $args) {
// вставьте код сюда 
})->setName('download');

// запуск приложения
$app->run();

Slim работает через определение функций обратного вызова для методов и конечных точек HTTP. Вызывается соответствующий метод Slim — —get() для запросов GET, post() для запросов POST и т.д. — и передается маршрут для сопоставления как первый аргумент метода. Вторым аргументом метода является функция, указывающая действия, которые необходимо выполнить, если маршрут соответствует входящему запросу.

Поскольку приложение будет поддерживать составление списков, добавление, удаление, загрузку и отправку счетов по электронной почте, сценарий определяет маршруты и поля для конечных точек /index, /add, /search и /download. Эти элементы будут включаться по мере продвижения по руководству. Сценарий также считывает настройки из созданного ранее файла конфигурации приложения, инициализирует механизм отображения шаблонов и регистрирует в Slim.

И в заключение остается только создать простой пользовательский интерфейс на базе Bootstrap — с областями заголовка, нижнего колонтитула и контента. В следующем листинге приведен пример кода, который будет использоваться для всех страниц приложения.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>CV Database</title>
    <link rel="stylesheet" 
      href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
    <link rel="stylesheet" 
      href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->    
  </head>
  <body>

    <div class="container">

      <div class="panel panel-default">
        <div class="panel-heading clearfix">
          <h4 class="pull-left">Skill Database</h4>
          <a href="<?php echo $data['router']->pathFor('index'); ?>"
            class="pull-right btn btn-primary btn">Home</a>
        </div>
      </div> 

      <!-- page content here -->            

    </div>
      
    <div class="container">
      <!-- page footer here -->         
    </div> 
    
  </body>
</html>

Шаг 2. Создание формы загрузки резюме

После создания каркаса приложения следующим шагом будет определение того, какие входные данные приложение будет поддерживать и в какой форме. Предположим, приложение будет поддерживать резюме в формате PDF, поскольку это самый популярный формат среди соискателей, и кроме того, система должна предоставлять специалистам по найму возможность вводить имя кандидата, его адрес электронной почты, ссылку на онлайновый профиль, а также комментарии или примечания в свободной форме.

Для таких входных данных создайте форму $APP_DIR/templates/add.phtml со следующим содержимым (обратите внимание, в этом и последующих листингах удалены общие верхние и нижние области из предыдущего шага):

<?php if (!isset($_POST['submit'])): ?>
<div>
  <form method="post" enctype="multipart/form-data" 
    action="<?php echo $data['router']->pathFor('add'); ?>">
    <input type="hidden" name="MAX_FILE_SIZE" value="10000000" />
    <div class="form-group">
      <label for="name">Candidate's name</label>
      <input type="text" name="name" id="name" class="form-control" />
    </div>
    <div class="form-group">
      <label for="email">Candidate's email address</label>
      <input type="text" name="email" id="email" class="form-control" />
    </div>
    <div class="form-group">
      <label for="url">Candidate's profile URL</label>
      <input type="text" name="url" id="url" class="form-control" />
    </div>
    <div class="form-group">
      <label for="upload">Candidate's CV</label> <br/>
      <span class="btn btn-default btn-file">
        <input type="file" name="upload" />
      </span>
    </div>  
    <div class="form-group">
      <label for="notes">Notes</label>
      <textarea name="notes" id="notes" 
        class="form-control"></textarea>
    </div>
  <div class="form-group">
    <button type="submit" name="submit" 
      class="btn btn-primary">Add</button>
  </div>          
  </form>
</div>
<?php else: ?>
<div>
  <div class="alert alert-success">
    <strong>Success!</strong> 
      The CV was successfully added with identifier 
      <strong><?php echo $data['id']; ?></strong>. 
      <a role="button" class="btn btn-primary" 
      href="<?php echo $data['router']->pathFor('add'); ?>">
      Add another?</a>
  </div>
</div>
<?php endif; ?>

Эта форма содержит поля для имени, адреса электронной почты, адреса web-сайта и резюме кандидата. Она также включает текстовую область для дополнительных комментариев.

Загрузите управляющий сценарий приложения по адресу $APP_ROOT/public/index.php, чтобы отображать этот шаблон каждый раз, когда пользователь запрашивает URL-маршрут /add:

<?php
// инициализация приложения Slim - пропущено
$app->get('/add', function ($request, $response, $args) {
  return $this->renderer->render($response, 
    'add.phtml', array('router' => $this->router));
})->setName('add');
// другие обратные вызовы - пропущено

Переход к конечной точке /add теперь должен отображать форму, показанную на следующем рисунке.

Рисунок 1. Форма загрузки резюме
Форма загрузки резюме
Форма загрузки резюме

При отправке формы введенные данные передаются как запрос POST в обработчик обратных вызовов /add. Этот обработчик обратных вызовов будет выполнять большую часть нагрузки в приложении. А именно:

  • проверять входные данные в форме;
  • извлекать загруженный файл и проверять, что это именно PDF-файл;
  • извлекать содержимое PDF-файла и передавать его в службу индексации, присваивая ему уникальный идентификатор;
  • сохранять PDF-файл в хранилище Object Storage.

Понятно, что перед реализацией обработчика обратных вызовов необходимо инициализировать службу индексации и службу Object Storage. В следующем разделе обсуждается служба индексации, а служба Object Storage будет рассматриваться во второй части статьи.

Шаг 3. Инициализация службы Searchly

IBM Cloud предлагает множество служб, среди которых Searchly — служба поиска на базе Elasticsearch. Бесплатный план Starter предоставляет доступ к этой службе с ограничениями на количество индексов и размер дискового пространства для хранения.

Чтобы увидеть, как это работает, инициализируйте новый экземпляр службы Searchly в IBM Cloud. Для этого войдите в IBM Cloud с использованием своей учетной записи и нажмите на панели кнопку Catalog. Из списка служб выберите Application Services, затем Searchly. Выберите план Starter и щелкните по кнопке Create для создания службы.

Рисунок 2. Создание службы Searchly
Создание службы Searchly
Создание службы Searchly

Open Searchly Dashboard, Open Searchly Dashboard, чтобы посмотреть панель Searchly. В нижней части главной страницы указан адрес Connection URL. Скопируйте его в ключ indexer[url] в файле настроек $APP_ROOT/src/settings.php.

Рисунок 3. Учетные данные для службы Searchly
Учетные данные для службы Searchly
Учетные данные для службы Searchly

В меню панели Searchly выберите Home > Indices, затем New Index и создайте новый индекс cvs.

Рисунок 4. Создание индекса в Searchly
Создание индекса в Searchly
Создание индекса в Searchly

Будет создан индекс со статусом «Оpen».

Рисунок 5. Создание индекса в Searchly
Создание индекса в Searchly
Создание индекса в Searchly

Шаг 4. Загрузка и индексация резюме

После инициализации системы индексации требуется инициализация PDF-парсера и клиента PHP Elasticsearch, которые были загружены через Composer на шаге 1. Добавьте следующий код в файл $APP_ROOT/public/index.php перед функциями обратного вызова:

<?php
// инициализация приложения Slim - пропущено
// конфигурирование зависимостей
$container = $app->getContainer();

// pdf-парсер
$container['pdfparser'] = function ($c) {
  return new Smalot\PdfParser\Parser();
};

// индексатор
$container['indexer'] = function ($c) {
  $config = $c->get('settings');
  $params['hosts'] = array($config['indexer']['url'] . ':80');
  return new Elasticsearch\Client($params);
};

Приведенный выше код использует контейнер внедрения зависимостей Slim для конфигурирования и подготовки к использованию PDF-парсера и клиента индексатора.

Отправленная форма попадает в обработчик форм, который ее принимает и проверяет, а затем извлекает и индексирует содержимое PDF. Ниже приведен код, который нужно включить в файл $APP_ROOT/public/index.php:

<?php
// инициализация Slim-приложения - пропущено
$app->post('/add', function ($request, $response, $args) {
  
  $post = $request->getParsedBody();
  $files = $request->getUploadedFiles();
  
  try {
  
    // проверка допустимости входных данных
    if (empty($post['name'])) {
      throw new Exception('No name provided');
    }

    if (empty($post['email']) || (filter_var($post['email'], 
      FILTER_VALIDATE_EMAIL) == false)) {
      throw new Exception('Invalid email address provided');
    }

    if (!empty($post['url']) && (filter_var($post['url'], 
      FILTER_VALIDATE_URL) == false)) {
      throw new Exception('Invalid URL provided');
    }
        
    // проверка загрузки файла
    if (empty($files['upload']->getClientFilename())) {
      throw new Exception('No file uploaded');
    }
    
    // проверка допустимости типа файла
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $type = $finfo->file($files['upload']->file);
    if ($type != 'application/pdf') {
      throw new Exception('Invalid file format, only PDF supported');    
    }
    
    // извлечение текста из PDF
    $pdf = $this->pdfparser->parseFile($files['upload']->file);
    $text = $pdf->getText();
    
    // добавление текста в индекс
    $document = array(
        'name' => strip_tags($post['name']),
        'email' => strip_tags($post['email']),
        'content' => $text,
        'url' => strip_tags($post['url']),
        'notes' => strip_tags($post['notes']),   
     );
     
    $params = array();
    $params['body']  = $document;
    $params['index'] = 'cvs';
    $params['type']  = 'doc';
    $indexerResponse = $this->indexer->index($params);
    $id = $indexerResponse['_id'];

    return $this->renderer->render($response, 
      'add.phtml', array('router' => $this->router, 'id' => $id));
    
  } catch (ClientException $e) {
    throw new Exception($e->getResponse());
  }
});

//  другие обратные вызовы - пропущено

Этот код определяет обратный вызов для обработки подаваемых форм через POST. Он начинается со сбора различных входных параметров — имя, адрес электронной почты, адрес сайта, примечания. Все это проверяется с использованием различных валидаторов. Загруженный файл также проверяется на то, что это именно PDF.

Если входные данные допустимы, используется PDF-парсер для извлечения содержимого PDF-файла как текстовой строки с использованием методов parseFile() и getText(). Эта строка, вместе с другими входными данными, преобразуется в PHP-массив, представляющий документ Elasticsearch. Для индексирования и сохранения этого документа в Searchly используется клиент Elasticsearch PHP.

В листинге выше обратите внимание, что в клиент Elasticsearch PHP передается массив параметров. Параметр body содержит реальный контент для индексации, параметр index указывает, какой индекс использовать, а параметр type указывает тип документа. После индексации документу присваивается уникальный идентификатор в индексе Searchly.

Чтобы увидеть это в действии, попробуйте добавить резюме через приложение. Если все пройдет успешно, вы увидите сообщение с идентификатором проиндексированного документа.

Рисунок 6. Успешная загрузка и индексация резюме
Успешная загрузка и индексация резюме
Успешная загрузка и индексация резюме

Если вы вернетесь в консоль IBM Cloud, откроете панель Searchly и посмотрите в раздел Contents, то увидите новую запись с идентификатором, содержащую данные о добавленном резюме. Можно щелкнуть по записи, чтобы увидеть детальную информацию об этом резюме в индексе.

Рисунок 7. Документ в индексе Searchly
Документ в индексе Searchly
Документ в индексе Searchly

Заключение

Мы проиндексировали содержимое PDF-файла, однако само резюме еще не хранится в системе. Здесь в игру вступает IBM Cloud Object Storage.

Во второй части этой серии статей я расскажу о службе IBM Cloud Object Storage и объясню, как ее можно использовать для сохранения и загрузки файлов резюме. Я также опишу процесс создания интерфейса поиска для приложения и поиск кандидатов с использованием навыков как ключевых слов. И в заключение я покажу, как все это развернуть в защищенной, надежной и масштабируемой облачной среде IBM Cloud.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Облачные вычисления
ArticleID=1051497
ArticleTitle=Использование IBM Cloud и PHP для создания базы данных резюме с функцией поиска, часть 1: Разработка оптимизированного для мобильных устройств, управляемого данными приложения для поиска резюме
publish-date=10302017