Обработка непрочитанных сообщений в PHP / MySQL

для личного проекта мне нужно построить форум с использованием PHP и MySQL. Для меня невозможно использовать уже построенный пакет форума (например, phpBB).

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

tbl_userReadPosts: user_id, post_id, read_timestamp

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

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

у кого-нибудь есть опыт в этом, и как вы справились с этим?

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

мысли и мнения с благодарностью, как всегда.

6 ответов


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

Что-то между тем, что вы уже предложили: сохраните последнюю активность пользователей и в сочетании с хранением информации о том, что они видели в файле cookie, чтобы определить, какие темы/сообщения они уже прочитали.

Это выгружает хранилище на клиентскую сторону cookie, что намного эффективнее.


таблица, содержащая все user_ids и post_ids, - плохая идея, поскольку она растет экспоненциально. Представьте, что ваше решение форума выросло до миллиона сообщений и 50 000 пользователей. Теперь у вас 50 миллиардов записей. Это будет проблемой.

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

все сообщения, сделанные до последнего входа в систему, считаются читать.

IE, я последний раз вошел в систему на 4/3/2011, а затем я войти сегодня. Все сообщения, сделанные до 4/3/2011, считаются прочитанными (они не новы для меня). Все сообщения между 4/3/2011 и сейчас, не читаются, если они не видны в таблице чтения. Таблица чтения сбрасывается при каждом входе в систему.

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


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

очевидно, пользователю не нужно знать, что есть непрочитанные сообщения от 2 лет назад, поэтому вы показываете только "новый пост" для сообщений, сделанных за последние 24 часа, и не находится в строке, разделенной запятыми.

вы также можете решить эту проблему с помощью переменной сеанса или файла cookie.


этот метод хранит самый последний доступ postID отдельно для каждого forumID.

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

<?php
    session_start();
    //error_reporting(E_ALL);

    // debug: clear session
    if (isset($_GET['reset'])) { unset($_SESSION['activity']); }

    // sample data: db table with your forum ids
    $forums = array(
        //  forumID     forumTitle
            '1'     =>  'Public Chat',
            '2'     =>  'Member Area',
            '3'     =>  'Moderator Mayhem'
    );

    // sample data: db table with your forum posts
    $posts = array(
        //  postID                  forumID     postTitle
            '12345' =>  array(  'fID'=>'1', 'title'=>'Hello World'),
            '12346' =>  array(  'fID'=>'3', 'title'=>'I hate you all'),
            '12347' =>  array(  'fID'=>'1', 'title'=>'Greetings!'),
            '12348' =>  array(  'fID'=>'2', 'title'=>'Car thread'),
            '12349' =>  array(  'fID'=>'1', 'title'=>'I like turtles!'),
            '12350' =>  array(  'fID'=>'2', 'title'=>'Food thread'),
            '12351' =>  array(  'fID'=>'3', 'title'=>'FR33 V1AGR4'),
            '12352' =>  array(  'fID'=>'3', 'title'=>'CAPSLOCK IS AWESOME!!!!!!!!'),
            '12353' =>  array(  'fID'=>'2', 'title'=>'Funny pictures thread'),
    );

    // sample data: db table with the last read post from each forum
    $userhist = array(
        //  forumID     postID
            '1'     =>  '12344',
            '2'     =>  '12350',
            '3'     =>  '12346'
    );

    // reference for shorter code
    $s = &$_SESSION['activity'];

    // store user's history into session
    if (!isset($s)) { $s = $userhist; }

    // mark forum as read
    if (isset($_GET['mark'])) {
        $mid = (int)$_GET['mark'];
        if (array_key_exists($mid, $forums)) {
            // sets the last read post to the last entry in $posts
            $s[$mid] = array_search(end($posts), $posts);
        }
        // mark all forums as read
        elseif ($mid == 0) {
            foreach ($forums as $fid=>$finfo) {
                // sets the last read post to the last entry in $posts
                $s[$fid] = array_search(end($posts), $posts);
            }
        }
    }

    // mark post as read
    if (isset($_GET['post'])) {
        $pid = (int)$_GET['post'];
        if (array_key_exists($pid, $posts)) {
            // update activity if $pid is newer
            $hist = &$s[$posts[$pid]['fID']];
            if ($pid > $hist) {
                $hist = $pid;
            }
        }
    }

    // link to mark all as read
    echo '<p>[<a href="?mark=all">Read All</a>]</p>' . PHP_EOL;

    // display forum/post info
    foreach ($forums as $fid=>$finfo) {
        echo '<p>Forum: ' . $finfo;
        echo ' [<a href="?mark=' . $fid . '">Mark as Read</a>]<br>' . PHP_EOL;
        foreach ($posts as $pid=>$pinfo) {
            if ($pinfo['fID'] == $fid) {
                echo '- Post: <a href="?post=' . $pid . '">' . $pid . '</a>';
                echo ' - ' . ($s[$fid] < $pid ? 'NEW' : 'old');
                echo ' - "' . $pinfo['title'] . '"<br>' . PHP_EOL;
            }
        }
        echo '</p>' . PHP_EOL;
    }

    // debug: display session value and reset link
    echo '<hr><pre>$_SESSION = '; print_r($_SESSION); echo '</pre>' . PHP_EOL;
    echo '<hr>[<a href="?reset">Reset Session</a>]' . PHP_EOL;
?>

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


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


что-то, что не было предложено, использовало большие данные для хранения такой информации, а именно NoSQL. Он эффективно сделан специально для обработки такого рода данных.

Я использую MongoDB, но вы можете найти приложение NoSQL в соответствии с вашими потребностями. http://nosql.findthebest.com/

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

другое предложение заключается в том, что вы можете альтернативно хранить данные в качестве "метаданных", подобно предложению csv, но давая ему более гибкую и запоминаемую структуру, используя сериализацию для сжатия данных для вашего объекта для загрузки и несериализации во время выполнения. Таким образом, работает как сеанс, который не истекает, связанный с user_id, а не session_id, который может быть загружен по требованию и отделен, как вам нравится. Например, когда страница форума загружается для конкретный пользователь.

например:

(сухие закодированные примеры-отрегулируйте, чтобы соответствовать вашей собственной схеме)

<?php
/**
array(
    "form_id1" => array( "post_id1", "post_id2", ),
    "form_id2" => array( "post_id1", "post_id2", )
);
*/

$this->user->metadata = unserialize( file_get_contents( '/metadata/forums/' . $this->user->id ) );

if( !isset($this->user->metadata[$this->forum->id]) ){
    $this->user->metadata[$this->forum-id] = array();
}
if(!in_array($this->post->id, $this->user->metadata[$this->forum->id]) ){
   $this->user->metadata[$this->forum-id][] = $this->post->id;
}
file_put_contents( '/metadata/forums/' . $this->user->id, serialize( $this->metadata); );

вы можете поменять местами file_x_contents с вашей СУБД-например:

<?php
$getMetadata = "SELECT forums FROM user_metadata WHERE user_id = $this->user->id";
$dbrs = mysqli_query( $getMetadata );
$this->user->metadata = unserialize( $dbrs['forums'] );
$dbrs->close();

$metadata = serialize($this->user->metadata);
$saveMetadata = "UPDATE user_metadata SET forums = '$metadata' WHERE user_id = '$this->user->id'";
mysqli_query( $saveMetadata );

вы также можете делать другие вещи, такие как поиск через regexp, отделить его дальше (тема, категория и т. д.) или изменить метод, основанный на пользователях, которые читают сообщения на форуме (Форум->post->viewedby), а не сообщения форума, которые читает пользователь (пользователь->метаданные->форумы). Особенно, если у вас уже есть рабочий "общий вид", но это будет сложнее получить сообщения, которые были/не были прочитаны конкретным пользователем, в то время как обратное верно для другого метода или даже использовать оба метода в сочетании.