- Определить основные сущности:
- Пользователь (участник)
- Профиль экстрасенса (фото, биография, статус)
- Эпизод/битва (запись видео, описание)
- Рейтинг (звёзды от зрителей)
- Статус (автоматически рассчитывается по сумме баллов)
- Требования к функционалу:
- Регистрация/авторизация
- Личный кабинет
- Страница битвы с видеоплеером
- Система голосования (звёздный рейтинг)
- Отображение текущего статуса и общего рейтинга
- Защита от накрутки (cookie + IP‑check)
- Технический стек:
- Backend – PHP 8+, MySQL 8+
- Web‑сервер – Apache/Nginx
Структура базы данных
Ниже пример минимального набора таблиц. Схема совместима с любой современной СУБД MySQL.
CREATE DATABASE battle_extrasens;
USE battle_extrasens;
-- 1. Пользователи
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(30) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
-- 2. Профили экстрасенсов
CREATE TABLE profiles (
user_id INT PRIMARY KEY,
avatar VARCHAR(255) DEFAULT 'default.jpg',
bio TEXT,
status ENUM('Новичок','Ученик','Мастер','Легенда') DEFAULT 'Новичок',
total_score INT DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB;
-- 3. Битвы (эпизоды)
CREATE TABLE battles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(150) NOT NULL,
description TEXT,
video_url VARCHAR(255) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
-- 4. Оценки зрителей (звёздный рейтинг)
CREATE TABLE ratings (
id INT AUTO_INCREMENT PRIMARY KEY,
battle_id INT NOT NULL,
user_id INT NOT NULL,
stars TINYINT NOT NULL CHECK (stars BETWEEN 1 AND 5),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uniq_vote (battle_id, user_id), -- один голос от одного пользователя
FOREIGN KEY (battle_id) REFERENCES battles(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB;
После создания таблиц импортируйте их в базу (например, через phpMyAdmin или консоль mysql -u root -p < schema.sql).
Настройка соединения с БД
Создайте файл config.php и поместите в него параметры подключения. Не забудьте изменить права доступа к файлу (chmod 600).
<?php
// config.php
define('DB_HOST', 'localhost');
define('DB_NAME', 'battle_extrasens');
define('DB_USER', 'your_user');
define('DB_PASS', 'your_password');
try {
$pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=utf8",
DB_USER,
DB_PASS,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
} catch (PDOException $e) {
die('Ошибка подключения к базе данных: ' . $e->getMessage);
}
?>
Регистрация и авторизация
Для простоты используем готовый шаблон PHPMailer для подтверждения e‑mail и password_hash для хранения пароля.
register.php
<?php
require 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username']);
$email = trim($_POST['email']);
$pass = $_POST['password'];
$hash = password_hash($pass, PASSWORD_DEFAULT);
$stmt = $pdo->prepare('INSERT INTO users (username,email,password) VALUES (?,?,?)');
$stmt->execute([$username,$email,$hash]);
// сразу создаём профиль
$userId = $pdo->lastInsertId;
$pdo->prepare('INSERT INTO profiles (user_id) VALUES (?)')->execute([$userId]);
header('Location: login.php');
exit;
}
?><html lang="ru">
<head>
<meta charset="UTF-8">
<title>Регистрация</title>
</head>
<body>
<h2>Регистрация</h2>
<form method="post">
<label>Логин: <input type="text" name="username" required></label><br>
<label>E‑mail: <input type="email" name="email" required></label><br>
<label>Пароль: <input type="password" name="password" required></label><br>
<button type="submit">Зарегистрироваться</button>
</form>
</body> login.php
<?php
require 'config.php';
session_start;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$_POST['email']]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($_POST['password'], $user['password'])) {
$_SESSION['user_id'] = $user['id'];
header('Location: dashboard.php');
exit;
} else {
$error = 'Неверный e‑mail или пароль';
}
}
?><html lang="ru">
<head>
<meta charset="UTF-8">
<title>Вход</title>
</head>
<body>
<h2>Вход</h2>
<?php if (!empty($error)): ?>
<p style="color:red;"><?= $error ?></p>
<?php endif; ?>
<form method="post">
<label>E‑mail: <input type="email" name="email" required></label><br>
<label>Пароль: <input type="password" name="password" required></label><br>
<button type="submit">Войти</button>
</form>
</body> Страница битвы (видеоплеер + рейтинг)
battle.php?id=12
<?php
require 'config.php';
$battleId = (int)$_GET['id'] ?? 0;
$stmt = $pdo->prepare('SELECT * FROM battles WHERE id = ?');
$stmt->execute([$battleId]);
$battle = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$battle) { die('Битва не найдена'); }
$stmt = $pdo->prepare('SELECT AVG(stars) AS avg_rating, COUNT(*) AS votes FROM ratings WHERE battle_id = ?');
$stmt->execute([$battleId]);
$ratingInfo = $stmt->fetch(PDO::FETCH_ASSOC);
$avg = round($ratingInfo['avg_rating'], 1);
$votes = $ratingInfo['votes'];
?><html lang="ru">
<head>
<meta charset="UTF-8"> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
.star {font-size:2rem; color:#ccc; cursor:pointer;}
.star.selected, .star.hover {color:#f39c12;}
</style>
</head>
<body> <video width="640" controls> Ваш браузер не поддерживает HTML5‑видео.
</video>
<h3>Оценка зрителей</h3>
<div id="rating">
<!-- 5 звёзд -->
<?php for ($i=1;$i<=5;$i++): ?>
<span class="star" data-value="<?= $i ?>">★</span>
<?php endfor; ?>
<span id="avgRating">(<?= $avg ?> из 5, <?= $votes ?> голосов)</span>
</div>
<script>
$(function{
const battleId = = $battleId ?>;
const $stars = $('.star');
// подсветка при наведении
$stars.on('mouseenter', function{
const val = $(this).data('value');
$stars.each(function(i){
$(this);toggleClass('hover', i < val);
});
}).on('mouseleave', function{
$stars.removeClass('hover');
});
// отправка оценки
$stars.on('click', function{
const stars = $(this).data('value');
$.post('rate.php', {battle_id: battleId, stars: stars}, function(resp){
if (resp.success){
$('#avgRating').text(`(${resp.avg} из 5, ${resp.votes} голосов)`);
$stars.removeClass('selected');
$stars.each(function(i){
if (i < stars) $(this).addClass('selected');
});
} else {
alert(resp.message);
}
}, 'json');
});
});
</script>
</body> rate.php – обработка AJAX‑запроса
<?php
require 'config.php';
session_start;
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success'=>false,'message'=>'Только авторизованные пользователи могут голосовать']);
exit;
}
$battleId = (int)$_POST['battle_id'] ?? 0;
$stars = (int)$_POST['stars'] ?? 0;
$userId = $_SESSION['user_id'];
if ($stars < 1 || $stars > 5) {
echo json_encode(['success'=>false,'message'=>'Недопустимое значение']);
exit;
}
/* Защита от двойного голоса – UNIQUE KEY в таблице ratings */
$stmt = $pdo->prepare('INSERT INTO ratings (battle_id,user_id,stars) VALUES (?,?,?)
ON DUPLICATE KEY UPDATE stars = VALUES(stars)');
$stmt->execute([$battleId,$userId,$stars]);
// пересчитаем средний рейтинг
$stmt = $pdo->prepare('SELECT AVG(stars) AS avg_rating, COUNT(*) AS votes FROM ratings WHERE battle_id = ?');
$stmt->execute([$battleId]);
$res = $stmt->fetch(PDO::FETCH_ASSOC);
echo json_encode([
'success'=>true,
'avg' => round($res['avg_rating'],1),
'votes' => $res['votes']
]);
?>
Cookie и/или reCAPTCHA. Система статусов
Статус экстрасенса будет зависеть от общего количества набранных баллов (сумма всех звёзд). Добавим небольшую функцию, которая будет вызываться после каждой новой оценки.
update_status.php
<?php
require 'config.php';
function recalcUserStatus(int $userId, PDO $pdo): void {
// Считаем суммарные звёзды за все битвы, где пользователь был участником
$stmt = $pdo->prepare('
SELECT SUM(r.stars) AS total
FROM ratings r
JOIN battles b ON r.battle_id = b.id
WHERE b.id IN (SELECT battle_id FROM participants WHERE user_id = ?)
');
$stmt->execute([$userId]);
$total = (int)$stmt->fetchColumn;
// Определяем статус
if ($total >= 200) $status = 'Легенда';
elseif ($total >= 100) $status = 'Мастер';
elseif ($total >= 50) $status = 'Ученик';
else $status = 'Новичок';
// Сохраняем в профиле
$pdo->prepare('UPDATE profiles SET total_score = ?, status = ? WHERE user_id = ?')
->execute([$total,$status,$userId]);
}
?>
После успешного сохранения оценки в rate.php добавьте вызов функции:
// после пересчёта среднего рейтинга
require_once 'update_status.php';
recalcUserStatus($userId, $pdo);
Отображение статуса в профиле
<?php
require 'config.php';
session_start;
$userId = $_SESSION['user_id'] ?? 0;
$stmt = $pdo->prepare('SELECT u.username, p.avatar, p.bio, p.status, p.total_score FROM users u
JOIN profiles p ON u.id = p.user_id WHERE u.id = ?');
$stmt->execute([$userId]);
$profile = $stmt->fetch(PDO::FETCH_ASSOC);
?><html lang="ru">
<head>
<meta charset="UTF-8">
<title>Личный кабинет</title>
</head>
<body> <img src="avatars/<?= $profile['avatar'] ?>" alt="Аватар" width="150">
<p><b>Статус: <span style="color:#e67e22"><?= $profile['status'] ?></span></p>
<p><b>Всего баллов: <?= $profile['total_score'] ?></p></body> Защита от накрутки и безопасность
- SQL‑инъекции: используем подготовленные запросы (PDO).
- CSRF: добавляем токен в формы (пример:
$_SESSION['csrf_token']). - Накрутка голосов:
- UNIQUE KEY (battle_id, user_id) – один голос от одного пользователя.
- Записываем IP и timestamp в отдельную таблицу
vote_logи ограничиваем частоту (например, 1 голос/30 сек). - Включаем
reCAPTCHA v3для анонимных голосов.
- Пароли:
password_hash+password_verify. - HTTPS: обязательный протокол для всех страниц.
Дизайн и адаптивность
Для быстрого прототипа можно подключить готовый CSS‑фреймворк Tailwind CSS или Bootstrap 5. Ниже пример простого блока рейтинга с Tailwind:
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@3.4.0/dist/tailwind.min.css" rel="stylesheet">
<div class="flex items-center space-x-2">
<!-- звёзды -->
<span class="star text-2xl cursor-pointer" data-value="1">★</span>
<span class="star text-2xl cursor-pointer" data-value="2">★</span>
<span class="star text-2xl cursor-pointer" data-value="3">★</span>
<span class="star text-2xl cursor-pointer" data-value="4">★</span>
<span class="star text-2xl cursor-pointer" data-value="5">★</span>
<span id="avgRating" class="ml-2 text-gray-600">(0 из 5, 0 голосов)</span>
</div>
Tailwind позволяет быстро менять цвета, отступы и адаптировать интерфейс под мобильные устройства.
Развёртывание проекта
- Подготовьте сервер с LAMP/LEMP‑стеком.
- Создайте базу данных и импортируйте
schema.sql. - Установите права:chmod -R 750 /var/www/html/battle
- Настройте виртуальный хост (Apache пример):
<VirtualHost *:80> ServerName battle.example;com <Directory /var/www/html/battle> AllowOverride All Require all granted </Directory> </VirtualHost> - Перезапустите веб‑сервер:
systemctl restart apache2(или nginx). - Обязательно включите HTTPS (Let’s Encrypt:
certbot --apache -d battle.example.com).
Что дальше?
- Добавить модуль комментариев к каждой битве.
- Реализовать таблицу лидеров (по сумме баллов).
- Интегрировать социальный вход (Google, VK, Facebook).
- Разработать модерацию контента и систему жалоб.
- Оптимизировать запросы (индексы, кеширование).
Следуя этим шагам, вы получите полностью функционирующий сайт «Битва экстрасенсов», где участники могут демонстрировать свои способности, зрители, голосовать, а система автоматически присваивает статус в зависимости от набранных баллов.
Удачной разработки!