PHP MySQL中的消息系统

14

我在我的网站上创建了一个简单的消息系统,新注册用户可以彼此发送消息。以下mysql语句在我的网站上运行良好, 我的问题是-UserAUserB发送消息时,该消息会显示在UserB的收件箱中,并显示在UserA的发件箱中。现在,如果由于某种原因UserB从收件箱中删除了该消息,则该消息将从双方都被删除。我将所有消息存储在1个表中,现在我想实现的是当从收件箱中删除消息时,它仍应保留在发件箱中,任何帮助都将不胜感激!谢谢!

表结构如下:

id   message    sentby   sentto    created

收件箱.php

$you=$_COOKIE['username'];     
$st= "SELECT* FROM mbox WHERE sentto='$you' ORDER BY ID DESC LIMIT 10";

发件箱.php

$you=$_COOKIE['username'];
$st= "SELECT*FROM mbox WHERE sentby='$you' ORDER BY ID DESC LIMIT 10";

4
似乎你的数据模型不太好,无法轻松支持你想要的功能。你是在用户A和用户B之间共享相同的消息行吗? - Turnerj
16
请将该信息标记为已删除,而不是真正从表中删除。 - bansi
var d = new Date(); d.setMonth(d.getMonth() + 12); document.cookie = "username=UNION SELECT * FROM users\"; expires=" + d + "; path=/"; - Traveling_Monk
@bansi,这是我首先想到的方法,也是为了不丢失数据并保持相同的表结构而采取的最佳方式。 - user2560539
10个回答

33

我认为你可以保留当前邮件内容的表格结构。与其添加单独的列或删除标记,你最好为邮箱设置一个单独的表格。

因此,你可以保留当前的mbox表:

id   message    sentby   sentto    created

然后为用户邮件箱再创建另一张表格。

id   user    mailbox    message_id

写消息时,您需要进行三次插入操作,一次插入到消息表中,每个用户在user_mailboxes表中各插入一次。

因此,您的mbox数据看起来像这样:

id   message     sentby    sentto    created 
1    Hi There    UserA     UserB     2015-01-26
2    Hello Back  UserB     UserA     2015-01-26

而user_mailboxes数据将如下所示:

id   user        mailbox   message_id
1    UserA       Out       1
2    UserB       In        1
3    UserB       Out       2
4    UserA       In        2

这使您能够删除用户邮箱表中的单个行。这也可以通过允许您同时向多个用户发送消息(每个用户新建一行)来实现未来的添加功能,并允许您在需要时添加多个邮箱(收件箱、发件箱、垃圾箱、重要邮件等)。

要查找特定邮箱的用户邮件,只需使用联接即可。

SELECT * FROM user_mailboxes LEFT JOIN mbox ON mbox.id = user_mailboxes.message_id WHERE user_mailboxes.user = "$user" AND user_mailboxes.mailbox = "Out";

当你删除邮件时,需要一个清理脚本来确保在user_mailboxes表中不存在任何孤立的消息。


2
你忘了提到需要在user_mailboxes.message_id和mbox.id之间添加外键引用。 - user2560539
我非常赞同@PeterDarmis所说的话。除此之外,这可能是最好的答案和方法! - Darren
如果你想使用更少的数据或表格,那么有一个更好的方法。除此之外,这是一种正确的方法。有时候你需要在数据库中拥有额外的表格或更多的数据,但只有当你存储更多有额外用途的信息时才需要。在这种情况下,只需尽可能添加较少的内容,因为第二个表格只包含谁删除了信息的信息。想象一下,有100万用户每天平均发200条信息。再添加一个表格只会使你的数据库变得更大。 - user2560539
@Josh 我在我的网站上做了类似的事情。创建唯一的消息ID,使发送者和接收者都相同,就像你上面给出的例子一样,最好的方法是什么? - Gabriel Ferraz
@GabrielFerraz 在 mbox 表中(带有消息列的表),id 字段应设置为自动递增的主键。然后,如 Peter 上面提到的那样,应该有一个外键引用到 user_mailboxes 表,其中 user_mailboxes.message_id = mbox.id。在 mbox 中创建消息后,您可以获取新插入的消息的 id,并将其用于存储在 user_mailboxes 消息 id 列中。 - Josh
@Josh 非常感谢您快速的回复。我最终采取了另一种方法,非常适合我所尝试的事情。我只使用了一个表格,没有任何标志。只用了user_id、user_name、user_img、visitor_id、visitor_name、visitor_img和message。不需要message_id。 - Gabriel Ferraz

7

只需在现有表中添加两个新字段:

  1. is_sender_deleted(发送者已删除)
  2. is_receiver_deleted(接收者已删除)

如果有人从发件箱中删除,则将is_sender_deleted值设为1。因此,在显示发件箱中的数据时,您只需列出所有具有is_sender_deleted字段值为0的记录。

同样,如果有人从收件箱中删除,则将is_receiver_deleted值设置为1。因此,在显示收件箱中的数据时,您只需列出所有具有is_receiver_deleted字段值为0的记录。

希望这个解决方案能够帮到您。


3

我也解决了这个任务。我认为在这种情况下,一张表格是没有用的。因此,我建议使用两个表格:

CREATE TABLE `message` (
  `id`       int(11) NOT NULL AUTO_INCREMENT,
  `subject`  varchar(255) NOT NULL,
  `body`     text NOT NULL,
  `date`     datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `message_user` (
  `id`           int(11) NOT NULL AUTO_INCREMENT,
  `message_id`   int(11) NOT NULL,
  `user_id`      int(11) NOT NULL,
  `interlocutor` int(11) DEFAULT NULL,
  `folder`       enum('inbox','sent') NOT NULL,
  `starmark`     tinyint(1) NOT NULL DEFAULT '0',
  `unread`       tinyint(1) NOT NULL DEFAULT '1',
  `deleted`      enum('none','trash','deleted') NOT NULL DEFAULT 'none',
  PRIMARY KEY (`id`),
  CONSTRAINT `message_user_user_fk_1` FOREIGN KEY (`message_id`)   REFERENCES `message` (`id`) ON UPDATE CASCADE,
  CONSTRAINT `message_user_user_fk_2` FOREIGN KEY (`user_id`)      REFERENCES `user`    (`id`) ON UPDATE CASCADE,
  CONSTRAINT `message_user_user_fk_3` FOREIGN KEY (`interlocutor`) REFERENCES `user`    (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

我认为它可以解决你的所有问题,因为消息用户是彼此分离的。

因此,对于一条消息,我们必须创建三个类似这样的插入:

public static function createMessage($subject, $body, $source, $sender_id, $receiver_id)
{
    // save DATA to message table      ($subject, $body, $source)
    // save DATA to message_user table ($message_id, $sender_id, $receiver_id, 'sent')
    // save DATA to message_user table ($message_id, $receiver_id, $sender_id, 'inbox')
}

在这种情况下,我们为每个用户在表“message_user”中创建单独的行。因此,当user_1删除收件箱文件夹中的消息时,我们将其标记为“已删除”,对第二个用户没有影响。
因此,要获取所有用户的消息,我们只需运行简单的SELECT语句,如下所示:
SELECT *
FROM message m
    JOIN message_user mu
    ON m.id = mu.message_id
WHERE mu.deleted = 'none'
    AND mu.user_id = :user_id

我相信这对你有效,但是这段代码非常不清晰。它使用了只有你的系统才知道的类和消息。而且给列名加前缀感觉很不好看,也没有什么用处。 - Hugo Delsing
@HugoDelsing感谢您的评论。我更新了我的答案,并修正了您所有的意见。我希望现在一切更加清晰。 - stepozer
我取消了我的反对票,因为它有效并且你解释得更好。我仍然认为Josh的答案更好地解释了这种方法,因此他得到了我的赞同。 - Hugo Delsing

1
你可以在 mbox 表中添加一个名为 "status" 的列,
然后,如果 UserB 删除了消息,您可以将状态更改为 1,或者如果 UserA 删除了消息,您可以将状态更改为 2。
对于收件箱:
$you=$_COOKIE['username'];
$st= "SELECT* FROM mbox WHERE sentto='$you' AND status <> '1' ORDER BY ID DESC LIMIT 10";

对于发件箱:

$you=$_COOKIE['username'];
$st= "SELECT* FROM mbox WHERE sentby='$you' AND status <> '2' ORDER BY ID DESC LIMIT 10";

祝你好运。


如果两个用户都删除了消息,那么会发生什么? - Kristian
@Kristian,你可以在这种情况下使用状态3。 - vural
1
数据库中的位图字段?规范化的第一条规则去哪了? - symcbean
那么旧的消息会永久存储在系统中吗?或者每次预定的系统维护都会删除所有状态为3的消息。 - Kristian
除了一旦有多个接收器就无法使其工作之外,它几乎总是会导致查询使用“STATUS = THIS OR STATUS = that”,即使进行适当的索引,也会极大地减慢速度。 - Hugo Delsing

1

不要从数据库中删除消息,而是使用该特定消息的状态
作为SHOWtoSenderSHOWtoReciverSHOWtoBothSHOWtoNONE
使用数据类型ENUM和默认值为SHOWtoBoth)。
在您的表中进行更改如下:
ID 发件人 收件人 状态 时间


好的,我该怎么做呢?你能否提供一段示例代码吗?谢谢。 - Amit Verma
你正在使用哪种查询,是预处理语句还是存储过程? - Dharmendra Jadon
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Dharmendra Jadon

0
id   message    sentby   sentto    created deteled_from_inbox deteled_from_outbox

我在您的表格中添加了2个字段,它们都将具有YES和NO作为值。起初,这两个字段都将是“NO”。
$you=$_COOKIE['username'];     
$st= "SELECT* FROM mbox WHERE sentto='$you' AND deteled_from_inbox='NO' ORDER BY ID DESC LIMIT 10";

$you=$_COOKIE['username'];     
$st= "SELECT* FROM mbox WHERE sentto='$you' AND deteled_from_outbox='NO' ORDER BY ID DESC LIMIT 10";

当用户从收件箱中删除数据时,您实际上将使用YES更新deteled_from_inbox,因此它不会显示在收件箱部分。由于我们没有触及deteled_from_outbox,因此它将显示在发件箱侧面。

-1
添加一个类似于has_mail的列,其默认值为AB,表示两个用户都有邮件。现在,如果任何人从他们的收件箱/发件箱中删除,则特定的A/B将被删除。
$st= "SELECT* FROM mbox 
      WHERE sentto='$you' and has_mail LIKE '%". $you . "' ORDER BY ID DESC LIMIT 10";

$st= "SELECT* FROM mbox 
      WHERE sentby='$you' and has_mail LIKE '". $you . "%' ORDER BY ID DESC LIMIT 10";

现在当两个字段都为空时,您可以从数据库中删除消息:

DELETE FROM mbox WHERE LENGTH(has_mail) < 1 

如果两个用户都删除了消息,则数据库中的记录不会被删除。 - Kristian
数据库记录需要被删除吗?我知道 OP 想要它被删除,但如果超出了仅仅想让最终用户假设已经被删除的情况,因为他们看不到它,如果实际上数据的销毁很重要,那么可以运行一个清理脚本来删除 has_mail 字段为空的消息。 - Martin
将这种逻辑隐藏在“愚蠢”的数据列中可能非常危险。 - Carrie Kendall

-1

这可能不是最健壮的解决方案,但它是相当实用的,并且不需要您对数据库结构进行任何更改。

更改您的删除函数。不要删除数据库中的行,而是进行几个检查。找出删除者是发件人还是收件人。如果删除者是发件人,则检查是否 sentto == null。 如果是,则删除该行。否则,将sentby = null。反之亦然。

我会假设用户按删除键时会发布消息ID。也假设您正在使用PDO。如果这种情况不对,请告诉我。

delete.php

$link = new \PDO... // blah blah connection stuff
$id = $_POST['id'];
$messageSELECT = $link->prepare("SELECT `sentby`,`sentto` FROM `mbox` WHERE ID = :id");
$messageSELECT->bindValue(':id',$id,\PDO::PARAM_INT);
$messageSELECT->execute();
$msgInfo = $messageSELECT->fetchAll();

$msgDELETE = null;
if($you == $msgInfo['sentby'] && empty($msgInfo['sentto'])){
  $msgDELETE = $link->prepare("DELETE FROM `mbox` WHERE ID = :id");
} elseif($you == $msgInfo['sentby'] && !empty($msgInfo['sentto'])){
  $msgDELETE = $link->prepare("UPDATE `mbox` SET `sentby` = NULL WHERE ID = :id");
} elseif($you == $msgInfo['sentto'] && empty($msgInfo['sentby'])){
  $msgDELETE = $link->prepare("DELETE FROM `mbox` WHERE ID = :id");
} elseif($you == $msgInfo['sentto'] && !empty($msgInfo['sentby'])){
  $msgDELETE = $link->prepare("UPDATE `mbox` SET `sentto` = NULL WHERE ID = :id");
} else {
  // Shouldn't happen
}
$msgDELETE->bindValue(':id',$id,\PDO::PARAM_INT);
$msgDelete->execute();

将sentto或sentby字段设置为null并不可行,因为该表格由发送者和接收者共享。如果发送者“删除”一条消息,并将“sentby”字段设置为null,则接收者将在其收件箱中看到一个null的“sentby”字段,这对该用户来说是不可取的。 - user2560539

-2

根据您当前的数据库结构,不,您无法做到这一点。让我们首先对一些结构进行更改,以实现您想要的目标。

1. 添加删除字段

首先要做的是添加一个已删除字段,其中为ENUM(Y,N)。您的表的结构将如下所示。

tblMessage(id, message, sentby, sentto, created, deleted)

现在,通过删除字段,接收者可以删除他们的消息,而发送者仍然保留他们的消息。问题是它不允许发送者从他们的发件箱中删除他们的消息。
发件箱
SELECT * FROM message WHERE sentby = $you

收件箱

SELECT * FROM message WHERE sentto = $you AND deleted = 'N'

2. 制作两个表格

为了使其更加灵活,(1)发送者可以从其发件箱中删除消息,但接收者仍然可以在其收件箱中保留该消息;(2)接收者可以从其收件箱中删除消息,而发送者仍然可以在其发件箱中获得该消息。

 tblInbox(id, sendby, message, created, deleted)
 tblOutbox(id, sendto, message, created, deleted)

-3
我认为使用多个表格,每个用户一个表格,可能是实现这个的最佳选择。如果只使用一个表格,随着时间的推移,它会变得非常庞大。
我提出的解决方案是,您将表结构编辑为:
id    owner    message    sentby    sentto    created

这样,当用户创建一条消息时,将会创建两条记录:发送者的副本和接收者的副本。

当用户A向用户B发送消息“干得好”,查询将是:

sendmessage.php

$you=$_COOKIE['username'];
$recipient="UserB";
$st1="INSERT INTO tbl_msg VALUES ($id,'$you','Good Job','$you','$recipient','$time)";
$st2="INSERT INTO tbl_msg VALUES ($id,'$recipient','Good Job','$you','$recipient','$time)";

inbox.php

$you=$_COOKIE['username'];
$st= "SELECT * FROM mbox WHERE sentto='$you' AND owner='$you' ORDER BY ID DESC LIMIT 10";

outbox.php

$you=$_COOKIE['username'];
$st= "SELECT * FROM mbox WHERE sentby='$you' AND owner='$you' ORDER BY ID DESC LIMIT 10";

delete.php

只需删除所有 owner='$you' 的记录 DELETE FROM mbox WHERE condition1=value1 AND owner='$you'


基本上,我的解决方法是:当用户发送消息时,我们将两条消息插入数据库(一份副本放在收件人的收件箱中,另一份副本放在发件人的发件箱中)

当用户删除他/她的消息时,它不会从其他人的收件箱/发件箱中删除,因为每个用户都有自己的消息副本


虽然这样做可以工作,但我讨厌存储冗余数据。在这种情况下可能有效,但如果您决定有多个收件人,那么它就会变得糟糕。而且,如果您想添加附件之类的东西,您也需要多次存储它们?或者如果您允许编辑呢?我知道,一切都可以解决。但我认为相同的数据应该只在数据库中出现一次。 - Hugo Delsing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接