Backend-сервис для хранения и управления вакансиями на базе Yii2.
- PHP 8.0 или выше
- MySQL 5.7+ или PostgreSQL 9.5+
- Composer
- Apache/Nginx с mod_rewrite
composer installОтредактируйте файл config/db.php и укажите параметры подключения к БД:
return [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=vakansii_db',
'username' => 'root',
'password' => 'your_password',
'charset' => 'utf8mb4',
];Создайте базу данных:
mysql -u root -p -e "CREATE DATABASE vakansii_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"Или для PostgreSQL:
psql -U postgres -c "CREATE DATABASE vakansii_db ENCODING 'UTF8';"./yii migrateУбедитесь, что DocumentRoot указывает на директорию web/:
<VirtualHost *:80>
ServerName vakansii-api.local
DocumentRoot /path/to/vakansii-back/web
<Directory /path/to/vakansii-back/web>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>server {
listen 80;
server_name vakansii-api.local;
root /path/to/vakansii-back/web;
index index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9000;
try_files $uri =404;
}
}chmod -R 777 runtime web/assetsGET /vacancy/search
Описание: Быстрый полнотекстовый поиск по названию и описанию вакансий с использованием FULLTEXT индексов (10-100x быстрее обычного поиска).
Параметры запроса:
q(string, обязательный) - поисковый запрос (1-255 символов)page(integer, optional) - номер страницы (1-10000, по умолчанию: 1)sort(string, optional) - тип сортировки:relevance(по умолчанию) - по релевантности результатовasc- по дате создания (старые первые)desc- по дате создания (новые первые)
Пример запроса:
GET /vacancy/search?q=PHP разработчик&page=1&sort=relevanceПример ответа:
{
"data": [
{
"id": 1,
"title": "Senior PHP Developer",
"salary": 200000,
"description": "Требуется опытный PHP разработчик с глубокими знаниями фреймворков"
},
{
"id": 5,
"title": "Backend PHP Developer",
"salary": 150000,
"description": "Ищем PHP разработчика для работы над микросервисами"
}
],
"pagination": {
"total": 12,
"page": 1,
"pageSize": 10,
"pageCount": 2
},
"query": "PHP разработчик"
}Пример ошибки (пустой запрос):
{
"success": false,
"message": "Поисковый запрос не может быть пустым"
}Особенности:
- Поддержка PostgreSQL (tsvector с русской морфологией) и MySQL (FULLTEXT)
- Кеширование результатов на 5 минут для повышения производительности
- Регистронезависимый поиск
- Автоматическое ранжирование по релевантности
GET /vacancy
Параметры запроса:
page(integer, optional) - номер страницы (по умолчанию: 1)sort(string, optional) - поле для сортировки:salaryилиcreated_at(по умолчанию:created_at)order(string, optional) - порядок сортировки:ascилиdesc(по умолчанию:desc)
Пример запроса:
GET /vacancy?page=1&sort=salary&order=descПример ответа:
{
"data": [
{
"id": 1,
"title": "PHP Developer",
"salary": 150000,
"description": "Требуется опытный PHP разработчик"
},
{
"id": 2,
"title": "Frontend Developer",
"salary": 120000,
"description": "Ищем frontend разработчика"
}
],
"pagination": {
"total": 25,
"page": 1,
"pageSize": 10,
"pageCount": 3
}
}GET /vacancy/{id}
Параметры запроса:
fields(string, optional) - список полей через запятую для выборочного отображения:title,description,salary,additional_fields
Пример запроса без фильтра:
GET /vacancy/1Пример ответа:
{
"id": 1,
"title": "PHP Developer",
"salary": 150000,
"description": "Требуется опытный PHP разработчик",
"additional_fields": {
"company": "Tech Corp",
"location": "Москва"
}
}Пример запроса с фильтром полей:
GET /vacancy/1?fields=title,salaryПример ответа:
{
"id": 1,
"title": "PHP Developer",
"salary": 150000
}POST /vacancy
Тело запроса (JSON):
{
"title": "PHP Developer",
"description": "Требуется опытный PHP разработчик с опытом работы от 3 лет",
"salary": 150000,
"additional_fields": {
"company": "Tech Corp",
"location": "Москва",
"employment_type": "full-time"
}
}Пример успешного ответа:
{
"success": true,
"id": 1,
"message": "Вакансия успешно создана"
}Пример ответа с ошибкой:
{
"success": false,
"errors": {
"title": ["Название вакансии не может быть пустым."],
"salary": ["Зарплата должна быть целым числом."]
},
"message": "Ошибка при создании вакансии"
}| Поле | Тип | Описание |
|---|---|---|
| id | INT | Первичный ключ (AUTO_INCREMENT) |
| title | VARCHAR(255) | Название вакансии (обязательное) |
| description | TEXT | Описание вакансии (обязательное) |
| salary | INT | Зарплата (обязательное) |
| additional_fields | JSON | Дополнительные поля (опциональное) |
| search_vector | TSVECTOR | Полнотекстовый индекс (PostgreSQL) |
| created_at | INT | Время создания (UNIX timestamp) |
| updated_at | INT | Время обновления (UNIX timestamp) |
Индексы:
- PRIMARY KEY на
id - GIN INDEX на
search_vector(PostgreSQL) для полнотекстового поиска - FULLTEXT INDEX на
title, description(MySQL) для полнотекстового поиска
Полнотекстовый поиск вакансий:
# Простой поиск
curl -X GET "http://localhost/vacancy/search?q=PHP"
# Поиск по нескольким словам
curl -X GET "http://localhost/vacancy/search?q=PHP%20разработчик"
# Поиск с пагинацией и сортировкой
curl -X GET "http://localhost/vacancy/search?q=developer&page=2&sort=relevance"Получение списка вакансий:
curl -X GET "http://localhost/vacancy?page=1&sort=salary&order=desc"Получение конкретной вакансии:
curl -X GET "http://localhost/vacancy/1"Получение вакансии с выбранными полями:
curl -X GET "http://localhost/vacancy/1?fields=title,salary"Создание вакансии:
curl -X POST "http://localhost/vacancy" \
-H "Content-Type: application/json" \
-d '{
"title": "PHP Developer",
"description": "Требуется опытный PHP разработчик",
"salary": 150000,
"additional_fields": {
"company": "Tech Corp",
"location": "Москва"
}
}'Полнотекстовый поиск вакансий:
// Простой поиск
const searchVacancies = async (query) => {
const response = await fetch(`http://localhost/vacancy/search?q=${encodeURIComponent(query)}`);
const data = await response.json();
return data;
};
// Использование
const results = await searchVacancies('PHP разработчик');
console.log(results.data); // Массив вакансий
console.log(results.pagination.total); // Общее количествоПолучение списка вакансий:
fetch('http://localhost/vacancy?page=1&sort=salary&order=desc')
.then(response => response.json())
.then(data => console.log(data));Создание вакансии:
fetch('http://localhost/vacancy', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'PHP Developer',
description: 'Требуется опытный PHP разработчик',
salary: 150000,
additional_fields: {
company: 'Tech Corp',
location: 'Москва'
}
})
})
.then(response => response.json())
.then(data => console.log(data));- 10-100x быстрее обычного LIKE поиска благодаря FULLTEXT индексам
- PostgreSQL: использует tsvector с русской морфологией и ts_rank для ранжирования
- MySQL: использует FULLTEXT индекс с MATCH...AGAINST
- Кеширование результатов поиска на 5 минут
- Результаты запросов кешируются на 5-10 минут
- Значительное ускорение повторных запросов
- Автоматическая инвалидация кеша при изменении данных
| Количество записей | LIKE поиск | FULLTEXT поиск | Ускорение |
|---|---|---|---|
| 100 вакансий | ~50-100ms | ~5-10ms | 10x |
| 1000 вакансий | ~200-500ms | ~10-20ms | 10-25x |
| 10000 вакансий | ~1000-3000ms | ~20-50ms | 20-150x |
| С кешем | - | ~1-5ms | 200-600x |
API защищен системой ограничения запросов:
- Лимит: 100 запросов в час на IP-адрес
- Ответ при превышении лимита: HTTP 429 (Too Many Requests)
- Заголовки ответа:
X-Rate-Limit-Limit- максимальное количество запросовX-Rate-Limit-Remaining- оставшееся количество запросовX-Rate-Limit-Reset- время сброса лимита (UNIX timestamp)
Пример ответа при превышении лимита:
{
"success": false,
"message": "Rate limit exceeded. Try again later.",
"retry_after": 3600
}CORS настроен и разрешает запросы с любых источников для упрощения интеграции с frontend.
Проект имеет полное покрытие автоматическими тестами:
# Все тесты (unit, functional, API)
vendor/bin/codecept run
# Только API тесты
vendor/bin/codecept run api
# Тесты полнотекстового поиска
vendor/bin/codecept run api SearchCest
# Тесты rate limiting
vendor/bin/codecept run api RateLimitingCest
# С подробным выводом
vendor/bin/codecept run --debugДля ручного тестирования API можно использовать:
- Swagger UI:
http://localhost:8080/swagger-ui.html- интерактивная документация - ReDoc:
http://localhost:8080/redoc.html- альтернативная документация - Postman
- Insomnia
- cURL
- Встроенные инструменты браузера
Проверьте настройки в config/db.php и убедитесь, что база данных создана.
Убедитесь, что:
- mod_rewrite включен (для Apache)
- файл
.htaccessприсутствует в директорииweb/ - Pretty URLs включены в конфигурации
Проверьте логи в директории runtime/logs/
Если полнотекстовый поиск работает медленно:
- Убедитесь, что миграции применены:
php yii migrate - Проверьте наличие FULLTEXT индексов в базе данных
- Для PostgreSQL: убедитесь, что колонка
search_vectorсуществует - Для MySQL: проверьте наличие FULLTEXT индекса на полях
title, description
Для более подробной информации обратитесь к следующим документам:
- README.md - Общая информация о проекте и быстрый старт
- QUICKSTART.md - Пошаговое руководство по запуску за 5 минут
- FULLTEXT_SEARCH.md - Подробное руководство по полнотекстовому поиску
- ARCHITECTURE.md - Описание архитектуры проекта
- PROJECT_INFO.md - Детальная информация о реализации
- HTTPS_SETUP.md - Настройка HTTPS/SSL
- SETUP_GUIDE.md - Полное руководство по настройке (HTTPS, CI/CD, Swagger)
- DOCKER.md - Запуск в Docker
- Swagger UI - Интерактивная API документация
MIT