Skip to main content

Сайт мобильных анекдотов

Многим в рунете знаком bash.org.ru - цитатник рунета, где пользователи собирают прикольные фразы, анекдоты и т.д. Подумаем и сделаем аналог баша на codeigniter php mysql.

Что представляет из себя цитата баша - просто небольшой текст. Для ввода такого текста даже не надо использовать визуальный редактор, достаточно переводы строк переводить в br с помощью php функции nl2br.

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

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

ТЗ мобильных анекдотов

Подумаем о схеме данных - о том какие таблицы в БД нам нужны и как лучше организовать данные и работу с ними.

Нам нужно хранить посты, дату их создания, рейтинг, отмодерированы ли они и забанены ли. Первая, не очень удачная схема данных mysql - решаем задачу в лоб:

Таблица постов: post

id INT первичный ключ, авто увеличение
created datetime Дата создания
body text Текст поста
is_moderated tinyint отмодерировано ли
is_flagged tinyint забанено ли
rating INT рейтинг

В принципе этой таблицы хватит для работы. Итак

* Пользователь добавил новый пост - он добавился в таблицу с флагом is_moderated = 0 и показываться на сайте не будет
* Далее модератор в Адмнике сайта смотрит на пост и если он соответствует правилам сайта отмечает его как is_moderated = 1, и сообщение становится доступным для пользователей на сайте.
* Пользователь решил проголосовать за или против поста - мы добавляем +1 или -1 к рейтингу данного поста.
* Пользователь решил забанить пост - добавляем +1 к полю is_flagged и когда is_flagged будет больше 3 (или какого-то другого числа, должно быть настраиваемо в конфиге), пост отключается от показа. Администратор видит такие посты в Админке и может вернуть их к жизни.

Вроде все работает, но сделано не очень хорошо. Во первых - у БД MySQL есть кеш, и если таблицы БД входящие в запрос не изменились - результат берется из кеша. Это просто здорово, работает замечательно быстро, но... У нас очень часто обновляются таблицы - добавили пост - изменились таблицы, проголосовали, забанили, и т.д. - таблицы содержат не только медленно меняющиеся данные, но и счетчики - поля которые изменяются часто. Это сводит на нет все преимущества кеша, плохо это.

Во вторых мы совершенно не защищены от накруток рейтинга и бана. Любой пользователь может накрутить любой рейтинг и забанить любой пост. Это не правильно и нам стоит подумать как от этого избавиться.

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

Для оптимизации работы кеша MySQL мы все изменяющиеся поля вынесем в отдельные таблицы. Получается

Таблица постов: post

id INT первичный ключ, авто увеличение
created datetime Дата создания
body text Текст поста
is_moderated tinyint отмодерировано ли
is_flagged tinyint забанено ли

Таблица рейтингов: rating

id INT первичный ключ, авто увеличение
post_id int Ид поста
rating int Рейтинг

Таблица голосов: vote

id INT первичный ключ, авто увеличение
post_id int Ид поста
created date Дата создания
vote tinyint голос за пост +1 или -1
ip bigint(13) айпи в числовом виде

Таблица бана: flagged

id INT первичный ключ, авто увеличение
post_id int Ид поста
ip bigint(13) айпи в числовом виде

Стало получше. Теперь мы защищены от накруток рейтинга - два голоса с одного поста не пройдут. Остается проблема с поисковиками и с теми, кто скачивает сайты целиком - их придется обрабатывать отдельно - поисковики отлавливать по User-Agent, плюс смотреть на число голосов. Если кто-то проголосовал за слишком большое число постов, то, похоже, это робот и учитывать его голоса не надо.

Как будет все работать.

* Пользователь добавил пост
* Модератор его отмодерировал

пока без изменений

* Пользователь проголосовал за пост
* Пересчитываем рейтинг поста, обновляем его в таблице рейтингов.

Многовато лишних телодвижений, надо хранить все голоса за все время, а их может быть очень много. Плохо.

Похоже у схемы данных есть недостатки и надо подумать, как ее сделать получше.

Таблица постов: post

id INT первичный ключ, авто увеличение
created datetime Дата создания
body text Текст поста
is_flagged tinyint забанено ли
rating int Рейтинг за предыдущие дни

Мы возвращаем рейтинг в таблицу постов, но будем хранить там только рейтинг за предыдущие дни - то есть раз в сутки будем его пересчитывать на основании другой таблицы - голосов за день. Кроме того в этой таблице мы будем хранить исключительно отмодерированные посты, посты ожидающие модерации будут в другой таблице. Так мы уберем одно поле из таблицы, упростим запросы.

Таблица постов для модерации: post2moder

id INT первичный ключ, авто увеличение
created timestamp Дата создания
body text Текст поста

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

Таблица голосов: vote

id INT первичный ключ, авто увеличение
post_id int Ид поста
vote tinyint голос за пост +1 или -1
ip bigint(13) айпи в числовом виде

Голоса за сегодня - делаем дополнительный запрос по этой таблице и суммируем рейтинг в таблице post с этим. Таблица голосов за сегодня большой не будет, а раз в сутке мы будем ее очищать - сбрасывая изменения в основную таблицу постов.

Таблица голосов бана: flag_post

id INT первичный ключ, авто увеличение
post_id int Ид поста
ip bigint(13) айпи в числовом виде
Просто собираем голоса бана, и когда голоса превышают критическое значение - баним пост.

Теперь схема данных стала попроще и лучше соответствует нашим задачам.