群发站内信的实现

在很多网站系统(如CMS系统,SNS系统等),都有“站内信”的功能。

  “站内信”不同于电子邮件,电子邮件通过专门的邮件发送、保存。而“站内信”是系统内的消息,说白了,“站内信”的实现,就是通过数据库插入记录来实现的。

  “站内信”有两个基本功能。一:点到点的消息传送。用户给用户发送站内信;管理员给用户发送站内信。二:点到面的消息传送。管理员给用户(指定满足某一条件的用户群)群发消息。点到点的消息传送很容易实现,本文不再详述。下面将根据不同的情况,来说说“站内信”的群发是如何实现的。

  第一种情况,站内的用户是少量级别的。(几十到上百)

  这种情况,由于用户的数量非常少,因此,没有必要过多的考虑数据库的优化,采用简单的表格,对系统的设计也来的简单,后期也比较容易维护,是典型的用空间换时间的做法。

  数据库的设计如下:表名:Message

  ID:编号;SendID:发送者编号;RecID:接受者编号(如为0,则接受者为所有人);Message:站内信内容;Statue:站内信的查看状态;PDate:站内信发送时间;

  如果,某一个管理员要给所有人发站内信,则先遍历用户表,再按照用户表中的所有用户依次将站内信插入到Message表中。这样,如果有56个用户,则群发一条站内信要执行56个插入操作。这个理解上比较简单,比较耗损空间。

  某一个用户登陆后,查看站内信的语句则为:

  Select * FROM Message Where RecID=‘ID’ OR RecID=0

  第二种情况,站内的用户中量级别的(上千到上万)。

  如果还是按照第一种情况的思路。那发一条站内信的后果基本上就是后台崩溃了。因为,发一条站内信,得重复上千个插入记录,这还不是最主要的,关键是上千乃至上万条记录,Message字段的内容是一样的,而Message有大量的占用存储空间。比方说,Message字段有100个汉字,占用 200个字节,那么5万条,就占用200×50000=10000000个字节=10M。简单的一份站内信,就占用10M,这还让不让人活了。

  因此,将原先的表格拆分为两个表,将Message的主体放在一个表内,节省空间的占用

  数据库的设计如下:

  表名:Message

  ID:编号;SendID:发送者编号;RecID:接受者编号(如为0,则接受者为所有人);MessageID:站内信编号;Statue:站内信的查看状态;

  表名:MessageText 

  ID:编号;Message:站内信的内容;PDate:站内信发送时间;

  在管理员发一封站内信的时候,执行两步操作。先在MessageText表中,插入站内信的内容。然后在Message表中给所有的用户插入一条记录,标识有一封站内信。

  这样的设计,将重复的站内信的主体信息(站内信的内容,发送时间)放在一个表内,大量的节省存储空间。不过,在查询的时候,要比第一种情况来的复杂。

  第三种情况,站内的用户是大量级的(上百万),并且活跃的用户只占其中的一部分。

  大家都有这样的经历,某日看一个网站比较好,一时心情澎湃,就注册了一个用户。过了一段时间,由于种种原因,就忘记了注册时的用户名和密码,也就不再登陆了。那么这个用户就称为不活跃的。从实际来看,不活跃的用户占着不小的比例。

  我们以注册用户2百万,其中活跃用户只占其中的10%。

  就算是按照第二种的情况,发一封“站内信”,那得执行2百万个插入操作。但是其中的有效操作只有10%,因为另外的90%的用户可能永远都不会再登陆了。

  在这种情况下,我们还得把思路换换。

  数据库的设计和第二种情况一样:

  表名:Message

  ID:编号;SendID:发送者编号;RecID:接受者编号(如为0,则接受者为所有人);MessageID:站内信编号;Statue:站内信的查看状态;

  表名:MessageText 

  ID:编号;Message:站内信的内容;PDate:站内信发送时间;

  管理员发站内信的时候,只在MessageText插入站内信的主体内容。Message里不插入记录。

  那么,用户在登录以后,首先查询MessageText中的那些没有在Message中有记录的记录,表示是未读的站内信。在查阅站内信的内容时,再将相关的记录插入到Message中。

  这个方法和第二种的比较起来。如果,活跃用户是100%。两者效率是一样的。而活跃用户的比例越低,越能体现第三种的优越来。只插入有效的记录,那些不活跃的,就不再占用空间了。

  以上,是我对群发“站内信”的实现的想法。也欢迎各位提出自己的建议,大家互相借鉴,共同进步。

<think>我们正在设计一个Java站内信系统。根据引用内容,站内信主要分为点到点(私信)和点到面(群发/公告)两种形式。以下是一个实现方案,包括数据库设计、核心功能和代码示例。###1.数据库设计根据引用[2][4],我们需要设计两张核心表:-**消息内容表(t_message_content)**:存储消息本身的内容-**消息记录表(t_message_record)**:存储用户与消息的关联关系(阅读状态等)####消息内容表(t_message_content)|字段名|类型|说明||----------|--------------|-----------------------------||c_id|BIGINT(主键)|消息内容ID||title|VARCHAR(100)|消息标题(可为空)||content|TEXT|消息内容||type|TINYINT|消息类型:0私信,1公告||sender_id|VARCHAR(32)|发送者ID(管理员发送可为系统ID)||send_time|DATETIME|发送时间|####消息记录表(t_message_record)|字段名|类型|说明||----------|--------------|---------------------------------------||r_id|BIGINT(主键)|记录ID||c_id|BIGINT|消息内容ID(外键)||rec_id|VARCHAR(32)|接收者ID||status|TINYINT|状态:0未读,1已读,2删除||read_time|DATETIME|阅读时间(可选)|###2.核心功能实现####(1)发送私信(点到点)```javapublicvoidsendPrivateMessage(StringsenderId,StringreceiverId,Stringtitle,Stringcontent){//1.保存到消息内容表StringsqlContent="INSERTINTOt_message_content(title,content,type,sender_id,send_time)VALUES(?,?,0,?,NOW())";//执行SQL,获取生成的主键c_id//2.创建消息记录StringsqlRecord="INSERTINTOt_message_record(c_id,rec_id,status)VALUES(?,?,0)";//执行SQL}```####(2)发送公告(点到面)```javapublicvoidsendAnnouncement(StringsenderId,List<String>receiverIds,Stringtitle,Stringcontent){//1.保存到消息内容表(类型为公告)StringsqlContent="INSERTINTOt_message_content(title,content,type,sender_id,send_time)VALUES(?,?,1,?,NOW())";//获取生成的c_id//2.为每个接收者创建消息记录StringsqlRecord="INSERTINTOt_message_record(c_id,rec_id,status)VALUES(?,?,0)";for(StringreceiverId:receiverIds){//执行SQL}}```####(3)用户获取站内信列表(参考引用[3])```javapublicList<MessageVO>getMessagesByUserId(StringuserId){Stringsql="SELECTc.*,r.status,r.r_id"+"FROMt_message_contentc"+"LEFTJOINt_message_recordrONc.c_id=r.c_id"+"WHEREr.rec_id=?ANDr.status!=2"+//排除已删除"ORDERBYc.send_timeDESC";//执行查询,返回MessageVO列表(包含消息内容和阅读状态)returnmessageList;}```####(4)阅读消息(更新状态)```javapublicvoidreadMessage(LongrecordId){Stringsql="UPDATEt_message_recordSETstatus=1,read_time=NOW()WHEREr_id=?";//执行更新}```####(5)删除消息(用户侧)```javapublicvoiddeleteMessage(LongrecordId){Stringsql="UPDATEt_message_recordSETstatus=2WHEREr_id=?";//执行更新(注意:仅标记删除,不实际删除记录)}```###3.优化考虑1.**群发性能**:当接收者数量极大时,批量插入消息记录(使用JDBC批处理)。2.**未读消息数**:在用户表或缓存中维护未读计数。3.**WebSocket推送**(引用[1]):新消息实时推送。```java//在WebSocket处理类中@OnMessagepublicvoidonMessage(Stringmessage,Sessionsession){//处理消息}publicvoidsendMessage(Sessionsession,Stringmessage)throwsIOException{session.getBasicRemote().sendText(message);//同步发送//或session.getAsyncRemote().sendText(message);//异步发送}```###4.关键点说明-**状态管理**(引用[4]):`status`字段在记录表中,允许用户独立删除自己的消息而不影响他人。-**类型区分**:私信(`type=0`)和公告(`type=1`)在内容表中标识,但处理逻辑基本一致。§§相关问题§§1.如何实现站内信的实时推送?2.站内信系统如何设计分页查询?3.如何优化海量用户下的群发公告性能?4.站内信系统如何保证消息的可靠性?</think>### Java 站内信实现方案 #### 一、核心设计思路 1. **消息分类**(引用[2]): - **点到点消息**(私信):用户↔用户 - **点到面消息**(公告/群发):管理员→用户群 2. **双表结构**(引用[3][4]): ```sql -- 消息内容表 (t_message_content) CREATE TABLE t_message_content ( c_id BIGINT PRIMARY KEY, -- 消息ID title VARCHAR(100), -- 标题 content TEXT, -- 正文 type TINYINT, -- 0=私信, 1=公告 sender_id VARCHAR(32), -- 发送者ID send_time DATETIME -- 发送时间 ); -- 消息记录表 (t_message_record) CREATE TABLE t_message_record ( r_id BIGINT PRIMARY KEY, c_id BIGINT, -- 关联消息ID rec_id VARCHAR(32), -- 接收者ID status TINYINT DEFAULT 0, -- 0=未读, 1=已读, 2=删除 read_time DATETIME ); ``` #### 二、关键功能实现 1. **发送私信**(点到点): ```java public void sendPrivateMessage(String senderId, String receiverId, String content) { // 1. 插入消息内容表 String sql = "INSERT INTO t_message_content (title, content, type, sender_id) VALUES (?, ?, 0, ?)"; try (PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { pstmt.setString(1, "新私信"); pstmt.setString(2, content); pstmt.setString(3, senderId); pstmt.executeUpdate(); // 2. 获取自增ID ResultSet rs = pstmt.getGeneratedKeys(); if (rs.next()) { long cId = rs.getLong(1); // 3. 插入接收记录 String recSql = "INSERT INTO t_message_record (c_id, rec_id) VALUES (?, ?)"; try (PreparedStatement recStmt = conn.prepareStatement(recSql)) { recStmt.setLong(1, cId); recStmt.setString(2, receiverId); recStmt.executeUpdate(); } } } } ``` 2. **群发公告**(点到面): ```java public void broadcastAnnouncement(String adminId, List<String> userIds, String content) { // 1. 插入公告内容 String sql = "INSERT INTO t_message_content (content, type, sender_id) VALUES (?, 1, ?)"; try (PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { pstmt.setString(1, content); pstmt.setString(2, adminId); pstmt.executeUpdate(); ResultSet rs = pstmt.getGeneratedKeys(); if (rs.next()) { long cId = rs.getLong(1); // 2. 批量插入接收记录 String recSql = "INSERT INTO t_message_record (c_id, rec_id) VALUES (?, ?)"; try (PreparedStatement recStmt = conn.prepareStatement(recSql)) { for (String userId : userIds) { recStmt.setLong(1, cId); recStmt.setString(2, userId); recStmt.addBatch(); } recStmt.executeBatch(); // 批量提交 } } } } ``` 3. **消息状态管理**(引用[4]): ```java // 标记已读 public void markAsRead(long recordId) { String sql = "UPDATE t_message_record SET status=1, read_time=NOW() WHERE r_id=?"; // ... 执行更新 } // 删除消息(用户侧) public void deleteMessage(long recordId) { String sql = "UPDATE t_message_record SET status=2 WHERE r_id=?"; // ... 执行更新(软删除) } ``` 4. **消息实时推送**(引用[1]): ```java // WebSocket 消息推送 @OnMessage public void onMessage(String message, Session session) { // 消息处理逻辑 } public void pushMessage(Session session, String jsonMsg) { session.getAsyncRemote().sendText(jsonMsg); // 异步推送 } ``` #### 三、消息查询优化 ```java // 获取用户消息(引用[3]优化版) public List<MessageVO> getUserMessages(String userId) { String sql = "SELECT c.*, r.status, r.r_id " + "FROM t_message_content c " + "JOIN t_message_record r ON c.c_id = r.c_id " + "WHERE r.rec_id = ? AND r.status != 2 " + // 排除已删除 "ORDER BY c.send_time DESC"; try (PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setString(1, userId); ResultSet rs = pstmt.executeQuery(); List<MessageVO> messages = new ArrayList<>(); while (rs.next()) { MessageVO msg = new MessageVO(); msg.setContent(rs.getString("content")); msg.setStatus(rs.getInt("status")); // 0未读/1已读 messages.add(msg); } return messages; } } ``` #### 四、设计要点说明 1. **读写分离**: - 频繁读取操作:消息列表展示 - 低频写入操作:消息发送 2. **状态管理**: - `status=2` 仅标记用户删除,不影响其他接收者(引用[4]) - 公告内容删除需管理员权限 3. **性能优化**: - 批量插入用于群发消息 - 添加索引:`t_message_record(rec_id, status)` 4. **扩展性**: - 可添加 `attachment` 字段支持附件 - 增加 `priority` 字段实现消息优先级
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值