Внутренняя документация

Система оценки услуги

Короткая карта текущей реализации: от клика по звезде до файлового хранилища, видимого счётчика и JSON-LD. Здесь же — честное ревью без изменения рабочего кода.

133 страницывес 1 / 0,01повтор через 7 днейKillBot proxy-awareHMAC + HttpOnly cookie
01 · Overview

Как проходит голос

Главный принцип: браузер только показывает интерфейс. Проверка, расчёт и запись выполняются PHP на сервере.

01СтраницаPHP получает базовую оценку и накопленные голоса.
02ИнтерфейсПользователь выбирает от 1 до 5 звёзд.
03ПодтверждениеТалон либо вариант «нет талона».
04ПроверкаPOST, origin, токен, реальный IP, лимиты.
05ЗаписьГолос сохраняется под файловой блокировкой.
06СинхронизацияКарточка, футер и JSON-LD получают один итог.
02 · Ownership

Файлы и ответственность

Не переносить логику между файлами без причины: сейчас у каждого участка есть понятный владелец.

ФайлЧто делаетМожно редактировать вручную?
includes/rating.phpКонфигурация страниц, базовые значения и сборка Product/LocalBusiness JSON-LD.Да, осторожно. Путь страницы должен точно совпадать с SCRIPT_NAME.
includes/rating-widget.phpЕдиная HTML-разметка модалки и её доступные состояния.Да, но одновременно проверять CSS и JS-селекторы.
includes/rating-service.phpХранилище, расчёт, HMAC, cookie, origin и защита от повторов.Только как backend-код; не дублировать в JavaScript.
includes/client-ip.phpЕдиный доверенный resolver IP для рейтинга и почтовой формы; знает актуальный адрес KillBot.При смене прокси обновлять только список PROJECT_TRUSTED_PROXY_IPS.
rating-vote.phpПубличный POST-only JSON endpoint голосования.Менять только вместе с интеграционным тестом ответов и кодов ошибок.
includes/rating-data.jsonПодтверждённые/неподтверждённые голоса, HMAC талонов и попытки.Нет. Не хранить здесь открытые номера; перед правкой делать резервную копию.
js/jquery.validationEngine-ru.jsИсходный клиентский owner интерфейса рейтинга; при сборке входит в общий runtime.Да, затем обязательно пересобрать runtime-common.min.js.
css/main.min.cssOwner визуального блока SHARED RATING MODAL REDESIGN.Да, с проверкой desktop/mobile и состояний модалки.
03 · Logic

Расчёт и защита

Подтверждённый голос влияет полностью; голос без талона учитывается с минимальным весом.

1,0

Подтверждённый голос

Номер талона нормализуется, HMAC-хеш сверяется с хранилищем, код допускается только один раз. Открытый номер в JSON не записывается.

0,01

Голос без талона

Сохраняется отдельно и добавляет всего одну сотую обычного веса к средней оценке. При этом видимое количество оценок увеличивается на один.

Повтор

На той же услуге голос блокируется на 7 дней при совпадении HMAC-хеша реального IP или подписанного HttpOnly cookie. За KillBot адрес берётся из X-Forwarded-For только при доверенном REMOTE_ADDR.

Перебор талонов

Не более восьми попыток с одного IP-хеша за час. Просроченные попытки удаляются при следующей записи.

Конкурентная запись

flock не позволяет двум PHP-процессам одновременно перезаписывать JSON.

средняя = (базовая оценка × базовые голоса + Σ оценка × вес) / (базовые голоса + Σ вес)
счётчик = базовые голоса + количество всех принятых голосов
Базу не удалять. start_rating и start_votes — неизменяемый начальный aggregate, который участвует в каждом расчёте. Новые голоса записываются отдельно в JSON. Удалять базу можно только после отдельной миграции её математического веса в другое хранилище.
04 · Search

Связь со Schema.org

Серверный aggregate — единственный источник значений при загрузке. Ручная синхронизация каждой страницы не нужна.

A

Один расчёт

rating_get_aggregate() возвращает среднюю и число голосов один раз на запрос страницы.

B

Три потребителя

Эти значения одновременно попадают в модалку, badge «Оценки» в футере и AggregateRating.

C

После голосования

Ответ endpoint обновляет все видимые значения и JSON-LD в текущем DOM; после перезагрузки источник снова сервер.

SEO-оговорка. Технически видимая оценка и JSON-LD синхронизированы, однако наличие звёзд в выдаче никогда не гарантируется кодом. Google требует, чтобы рейтинг относился к конкретному объекту, был виден пользователю и формировался из реальных пользовательских оценок. Использование Product для ремонтной услуги остаётся зоной, которую следует регулярно проверять в Rich Results Test и Search Console.
05 · Review

Короткое код-ревью

На текущем объёме система работоспособна. Критических блокеров по выполненному smoke-тесту нет.

Хорошо

Единый серверный источник

Интерфейс, футер и schema используют один aggregate. Это снижает риск расхождений и избавляет от ручного редактирования счётчиков.

Хорошо

Базовая защита и приватность

POST-only endpoint, проверка источника, суточный HMAC-токен, ограничение размера запроса, подписанная cookie и отсутствие открытых IP/талонов в хранилище.

Хорошо

Отказоустойчивое чтение

При недоступном JSON страница сохраняет базовую оценку, а некорректное хранилище не перезаписывается пустыми данными.

Разделить

Rating UI находится в файле локализации валидатора

Клиентская логика работает и корректно попадает в общий runtime, но jquery.validationEngine-ru.js имеет две несвязанные обязанности. При следующем отдельном рефакторинге рейтинг лучше вынести в собственный исходник и сохранить прежний порядок сборки.

Усилить

Секрет HMAC находится в PHP-файле

Через обычный HTTP исходник не выдаётся, а каталог includes закрыт. Но при утечке исходников или резервной копии один секрет раскрывает токены и хеши. В будущем лучше брать его из закрытого серверного конфига вне репозитория.

Усилить

Запись JSON не атомарная

flock защищает от параллельных запросов, но авария между ftruncate и fwrite может повредить файл. Следующий уровень защиты — временный файл, проверка и атомарная замена плюс резервная копия.

Следить

JSON будет расти

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

Исправлено

Реальный IP за KillBot

Рейтинг и антиспам формы используют общий project_client_ip(). Для доверенного прокси 93.77.162.43 берётся первый валидный адрес из X-Forwarded-For; от любого другого отправителя этот заголовок игнорируется.

Компромисс

Это защита от бытового злоупотребления, не CAPTCHA

IP, cookie и токен хорошо останавливают обычные повторы, но не гарантируют защиту от автоматизации с ротацией адресов и cookie. Это соответствует выбранной лёгкой архитектуре без БД.

Компромисс

Счётчик и средняя используют разную меру

Неподтверждённая оценка добавляет единицу к ratingCount, но только 0,01 к весу средней. Это принято намеренно, однако является нестандартной интерпретацией агрегата и должно оставаться задокументированным.

06 · Maintenance

Короткая инструкция

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

01

Добавить страницу

  1. Добавить точный URL-ключ в $pages_config.
  2. Указать базовую оценку, число голосов, название, цену и изображение.
  3. Проверить модалку, footer badge и JSON-LD.
02

Изменить интерфейс

  1. Править общий rating-widget.php.
  2. Не менять id/data-атрибуты без синхронного изменения JS.
  3. Проверить клавиатуру, mobile и сообщения ошибок.
03

Изменить JavaScript

  1. Править исходник, а не minified runtime.
  2. Пересобрать runtime-common.min.js.
  3. Проверить выбор звёзд, оба пути отправки и обновление schema.
04

Добавить талон

  1. Никогда не сохранять открытый номер.
  2. Нормализовать его так же, как endpoint.
  3. Записать HMAC и scope через безопасную служебную операцию с блокировкой.
05

Перенос на сервер

  1. Сохранить запрет HTTP-доступа к includes/.
  2. Указать фактические адреса прокси в PROJECT_TRUSTED_PROXY_IPS.
  3. Дать PHP право записи только в JSON и сохранить UTF-8 без BOM.
06

Если что-то сломалось

  1. Не обнулять JSON.
  2. Сравнить права, JSON-синтаксис и журнал PHP.
  3. Восстановить резервную копию только после сохранения повреждённого файла для анализа.
07 · Release

Перед публикацией

Короткий обязательный чек-лист после любого изменения рейтинга.

  • PHP lint и JavaScript syntax check проходят без ошибок.
  • Страница открывается на desktop и mobile без ошибок консоли.
  • Модалка показывает правильные среднюю и количество.
  • Footer badge совпадает с модалкой.
  • JSON-LD совпадает с видимыми значениями.
  • POST без талона и с валидным талоном проверены на копии хранилища.
  • Повторный голос получает блокировку на семь дней.
  • KillBot передаёт реальный IP в X-Forwarded-For, а недоверенный отправитель не может его подменить.
  • Исходный JSON сохранён и доступен PHP на запись.
  • Каталог includes недоступен по HTTP.
  • Rich Results Test и Search Console не показывают новых ошибок.