【问题标题】:Private Messaging System With Threading/Replies带有线程/回复的私人消息系统
【发布时间】:2010-11-13 11:25:09
【问题描述】:

我目前正在创建一个私人消息系统(PHP/MySQL),用户可以在其中一次向多个收件人发送消息,然后这些用户可以决定回复。

这是我目前正在使用的:

tbl_pm tbl:
id
date_sent
title
content
status ENUM ('unread', 'read') DEFAULT 'unread'

tblpm_info tbl:
id
message_id
sender_id
receiver_id

但是,我需要一些帮助来确定两件事的逻辑:

1) 创建新消息时,“id”是否应该自动递增?如果两个表中的“id”列都设置为自动递增,我将如何设置“关系表”中的“message_id”列?

例如新建一条消息时,我的MySQL语句如下:

<?php
mysql_query("INSERT INTO `tblpm` (title, content, sender_id, date_sent) VALUES ('$subject', '$message', '$sender', NOW())" );

在同一个语句中,如何将 tblpm 的“自动递增”值输入 tblpm_info "message_id" 字段?

2) 当用户回复消息时,我的 MySQL 语句应该是什么样子?

也许我让这比我需要的更复杂。非常感谢任何帮助!

【问题讨论】:

    标签: php mysql insert-id


    【解决方案1】:

    1) 确实是的,除非您提供不同的唯一主键方式,否则 id 应该是自动递增的。您可以直接从 mysql 使用 mysql_insert_id()LAST_INSERT_ID() 获取插入的 id,因此要发布一些连接的信息,您可以这样做

       mysql_query("INSERT INTO table1 ...")
       $foreign_key=mysql_insert_id(); //this gives you the last auto-increment for YOUR connection
    

    或者,但前提是您绝对确定没有其他人同时写入表或控制事务,插入后执行:

    $foreign_key=mysql_query("SELECT LAST_INSERT_ID()")
    INSERT INTO table2 message_id=$foreign_key
    

    或者,在不将 FK 拉到 php 的情况下,全部在一个事务中(我也建议将 SQL 也包装为事务),例如:

    "INSERT INTO table1...; INSERT INTO table2 (message_id,...) VALUES(LAST_INSERT_ID(),...)"
    

    根据您的语言和 mysql 库,您可能无法发出多查询方法,因此您最好使用第一种方法。

    2)这可以有很多方法,取决于您是否也需要回复所有收件人(例如会议),以类似线程/论坛的方式回复,客户端是否可以存储最后检索到的消息/id(例如在 cookie 中;也会影响您是否真的需要“读取”字段)。

    “私人聊天”方法是最简单的一种,那么您最好将消息存储在一个表中,并将 from-to 关系存储到另一个表中(并在它们上使用 JOIN),或者简单地重新填充一张表中的消息(因为现在存储很便宜)。所以,最简单的模型就是一张表:

    table: message_body,from,to
    $recepients=array(1,2,3..);
    foreach($recepients as $recepient)
     mysql_query("INSERT INTO table (...,message_body,from,to) VALUES(...,$from,$recepient)");
    

    (复制消息等,仅接收者更改)

    message_table: id,when,message_body
    to-from-table: id,msg_id,from,to
    
    $recepients=array(1,2,3,...);
    mysql_insert("INSERT INTO message_table (when,message_body) VALUES(NOW(),$body)");
    $msg_id=mysql_insert_id();
    foreach($recepients as $recepient)
     mysql_query("INSERT INTO to-from-table (msg_id,from,to) VALUES($msg_id,$from,$recepient)");
    

    (消息插入一次,存储所有接收者的关系和FK)

    然后每个客户端存储他/她收到的最后一个 message_id(默认为 0),并假设所有先前的消息都已阅读):

    "SELECT * FROM message WHERE from=$user_id OR to=$user_id WHERE $msg_id>$last_msg_id"
    

    或者我们只记下用户的最后输入时间,并从那时起查询任何新消息:

    "SELECT * FROM message WHERE from=$user_id OR to=$user_id WHERE when>='".date('Y-m-d H:i:s',$last_input_time)."' "
    

    如果您需要更类似于会议或论坛的方法,并且需要跟踪谁阅读了该消息,您可能需要跟踪所有相关用户。

    假设在一次“多用户会议”中不会有 100 多人,我会使用一张表来发送消息,并使用“逗号分隔和包装列表”技巧来存储标签。

    id autoincrement (again, no need for a separate message id)
    your usual: sent_at, title (if you need one), content
    sender (int)
    recepients (I'd go with varchar or shorter versions of TEXT; whereas TEXT or BLOB gives you unlimited number of users but may have impact on performance)
    readers (same as above)
    

    recipients/readers 字段的秘诀是填充它们以逗号分隔的 id 列表并再次用逗号包裹(稍后我会详细说明原因)。

    因此,您必须再次将接收者的 id 收集到一个数组中,例如$recepients=array(2,3,5) 并修改您的插入:

    "INSERT INTO table (sent_at,title,content,sender,recepients) VALUES(NOW(),'$title','$content',$sender_id,',".implode(',', $recepients).",')"
    

    你会得到像
    这样的表格行 ... 发件人 |收件人
    ...        1 | ,2, //单个用户消息
    ...        1 | ,3,5, //多用户消息

    为您使用的 ID 为 $user_id=2 的用户选择所有消息

    SELECT * FROM table WHERE sender=$user_id OR INSTR(recepients, ',$user_id,')
    

    之前我们包装了接收者的内爆列表,例如'5,2,3' 变成 ',5,2,3,' 并且这里的 INSTR 告诉我们 ',2,' 是否作为子字符串包含在某处 - 因为只寻找 '2',',2' 或 '2, ' 可能会给您带来误报,例如'234,56','1**,234','9,452,**89' 相应地 - 这就是我们必须包装列表的原因首先。

    当用户阅读/接收他/她的消息时,您将他们的 id 附加到读者列表中,例如:

    UPDATE table SET readers=CONCAT(',',TRIM(TRAILING ',' FROM readers),',$user_id,') WHERE id=${initial message_id here}
    

    导致:

    ... 发件人 |收件人|读者
    ...        1 | ,2,          | ,2,
    ...        1 | ,3,5,         | ,3,5,2,

    或者我们现在可以修改初始查询,添加“is_read”列来说明用户之前是否阅读过消息:

    SELECT * FROM table WHERE INSTR(recepients, ',$user_id,'),INSTR(readers, ',$user_id,') AS is_read
    

    从结果中收集消息 ID 并一次性更新“收件人”字段

    "UPDATE table SET readers=CONCAT(',',TRIM(TRAILING ',' FROM readers),',$user_id,') WHERE id IN (".implode(',' ,$received_msg_ids).")"
    

    【讨论】:

    • 感谢您的回复,我有点喜欢“包装列表”技巧,但是,我确实有一个问题。使用这种方法,我将如何跟踪实际阅读消息的时间戳。换句话说,除了跟踪“is_read”之外,我还想跟踪每个收件人的“when_read”。谢谢。
    【解决方案2】:

    您不应依赖两个 ID 的自动增量,因为可能有两个用户几乎同时发布两条消息。如果第一个脚本将数据插入到tbl_pm 表中,那么第二个脚本在第一个脚本完成其tblpm_info 插入之前设法执行其tbl_pmtblpm_info 插入,第一个脚本的两个数据库插入将具有不同身份证。

    除此之外,您的数据库结构似乎没有很好地组织手头的任务。假设您的消息可能很长,并且发送给大量用户,那么将消息内容存储一次是理想的,并且对于每个收件人都有未读状态、已读时间等。例如:

    CREATE TABLE `pm_data` (
      `id` smallint(5) unsigned NOT NULL auto_increment,
      `date_sent` timestamp NOT NULL,
      `title` varchar(255)
      `sender_id` smallint(5) unsigned,
      `parent_message_id` smallint(5) unsigned,
      `content` text,
      PRIMARY_KEY (`id`)
    );
    CREATE TABLE `pm_info` (
      `id` smallint(5) unsigned NOT NULL auto_increment,
      `pm_id` smallint(5) unsigned NOT NULL,
      `recipient_id` smallint(5) unsigned,
      `read` tinyint(1) unsigned default 0,
      `read_date` timestamp,
      PRIMARY_KEY (`id`)
    );
    

    创建这两个表,并注意它们都有一个“id”值设置为自动递增,但“信息”表还有一个pm_id 字段,它将保存“数据”行的 ID 号它所引用的,这样您就可以确定“信息”表中的每一行都有一个主键,您可以使用它来从中进行选择。

    如果您想使用 MySQL 进行真正的关系数据库设置,请确保您的引擎设置为 InnoDB,它允许在表之间设置关系,因此(例如)如果您尝试将某些内容插入到“信息”表中如果引用了“数据”表中不存在的pm_id,则 INSERT 将失败。

    一旦您选择了数据库结构,您的 PHP 代码将如下所示:

     <?php
     // Store these in variables such that if they change, you don't need to edit all your queries
     $data_table = 'data_table';
     $info_table = 'info_table';
     mysql_query("INSERT INTO `$data_table` (title, content, sender_id, date_sent) VALUES ('$subject', '$message', '$sender', NOW())" );
     $pmid = mysql_insert_id(); // Get the inserted ID
     foreach ($recipent_list as $recipient) {
          mysql_query("INSERT INTO `$info_table` (pm_id, recipient_id) VALUES ('$pmid', '$recipient')" );
     }
    

    【讨论】:

      【解决方案3】:

      是的。你肯定会在两个 id 上设置 auto_increment。

      要设置 message_id,您可以以编程方式将其插入其中。

      您的查询将如下所示:

      mysql_query("INSERT INTO `tblpm` (title, content, sender_id, date_sent) VALUES ('$subject', '$message', '$sender', NOW())" );
      

      注意它是一样的!如果 id 设置为 auto_increment,它将为您完成所有的魔法。

      【讨论】:

      • 感谢您的回复。您能否详细说明您的意思是“设置 message_id”,您将以编程方式将其插入其中?”谢谢。
      【解决方案4】:

      在普通的 PHP/Mysql 调用中,mysql_insert_id() 返回上一个 INSERT 操作的自动递增值

      因此,您插入消息,收集新生成的 ID,并将该值放入另一个表中。

      【讨论】:

        【解决方案5】:

        就您个人而言(假设示例未简化并且我看不到更多示例)我会将这两个表中的数据存储在一个表中,因为它们似乎直接相关:

        tbl_pm tbl:

        message_id

        发送日期

        标题

        内容

        status ENUM ('unread', 'read') DEFAULT 'unread'

        sender_id

        receiver_id

        所以你最终得到了像上面这样的东西,因为关系总是一对一的,所以真的不需要加入吗?您在 tbl_pm 表中已读/未读,这肯定会因每个收件人而更改,这意味着您无论如何都必须为每个收件人存储邮件的副本。也许状态应该在 tbl_pm 信息表中。

        如果您确实想插入两个表,请尝试在查询中使用 last_insert_id() 或 mysql_insert_id() 如上所述,来自 php。

        【讨论】:

          【解决方案6】:

          我可能会做类似于 gavin 推荐的操作,但如果您想要线程消息,则必须添加另一个键,如下所示:

          private_messages
          - title (text)
          - date (timestamp)
          - content (text)
          - status (enum)
          - sender_id (int)
          - receiver_id (int)
          - parent_message_id (int)
          

          那么您可以在没有单独的表或系统的情况下嵌套消息。

          【讨论】:

            猜你喜欢
            • 2012-09-09
            • 1970-01-01
            • 1970-01-01
            • 2010-10-20
            • 1970-01-01
            • 2011-09-19
            • 2016-05-07
            • 2013-01-10
            • 1970-01-01
            相关资源
            最近更新 更多