本文目录导读:

这里为您提供一个完整的Java站内信(站内消息)系统实现案例,包含核心功能、代码示例和数据库设计。
系统设计
1 功能模块
- 发送站内信
- 接收站内信
- 已读/未读状态管理
- 删除消息
- 消息列表查询
- 未读数量统计
2 数据库表设计
-- 站内信主表 CREATE TABLE `sys_message` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, varchar(200) DEFAULT NULL COMMENT '标题', `content` text COMMENT '内容', `sender_id` bigint(20) DEFAULT NULL COMMENT '发送者用户ID', `sender_name` varchar(100) DEFAULT NULL COMMENT '发送者名称', `msg_type` tinyint(4) DEFAULT '0' COMMENT '消息类型:0系统消息,1用户消息,2通知', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '发送时间', `status` tinyint(4) DEFAULT '0' COMMENT '状态:0正常,1删除', PRIMARY KEY (`id`), KEY `idx_sender_id` (`sender_id`), KEY `idx_create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 消息接收表 CREATE TABLE `sys_message_receiver` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `message_id` bigint(20) NOT NULL COMMENT '消息ID', `receiver_id` bigint(20) NOT NULL COMMENT '接收者用户ID', `is_read` tinyint(1) DEFAULT '0' COMMENT '是否已读:0未读,1已读', `read_time` datetime DEFAULT NULL COMMENT '阅读时间', `receiver_delete` tinyint(1) DEFAULT '0' COMMENT '接收者是否删除', PRIMARY KEY (`id`), UNIQUE KEY `uk_message_receiver` (`message_id`,`receiver_id`), KEY `idx_receiver_id` (`receiver_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
实体类实现
// 消息实体类
package com.example.message.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class SysMessage {
private Long id;
private String title;
private String content;
private Long senderId;
private String senderName;
private Integer msgType; // 0系统 1用户 2通知
private LocalDateTime createTime;
private Integer status;
// 发送给指定用户
private Long receiverId;
}
// 消息接收实体类
package com.example.message.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class MessageReceiver {
private Long id;
private Long messageId;
private Long receiverId;
private Integer isRead;
private LocalDateTime readTime;
private Integer receiverDelete;
}
// 消息传输对象
package com.example.message.dto;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class MessageDTO {
private Long messageId;
private String title;
private String content;
private Long senderId;
private String senderName;
private Integer msgType;
private LocalDateTime createTime;
private Integer isRead;
private Long receiverId;
// 请求参数
private Long userId; // 当前用户ID
private List<Long> targets; // 接收者ID列表
}
// 分页查询参数
@Data
public class MessageQueryDTO {
private Long userId;
private Integer isRead; // null查全部,0未读,1已读
private Integer msgType;
private Integer pageNum = 1;
private Integer pageSize = 10;
}
Mapper层实现
package com.example.message.mapper;
import com.example.message.dto.MessageDTO;
import com.example.message.dto.MessageQueryDTO;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface MessageMapper {
// 插入消息主表
@Insert("INSERT INTO sys_message(title, content, sender_id, sender_name, msg_type, create_time) " +
"VALUES(#{title}, #{content}, #{senderId}, #{senderName}, #{msgType}, NOW())")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertMessage(MessageDTO message);
// 批量插入接收人
@Insert("<script>" +
"INSERT INTO sys_message_receiver(message_id, receiver_id, is_read) VALUES " +
"<foreach collection='targets' item='target' separator=','>" +
"(#{messageId}, #{target}, 0)" +
"</foreach>" +
"</script>")
int insertReceivers(@Param("messageId") Long messageId, @Param("targets") List<Long> targets);
// 查询用户的消息列表
@Select("<script>" +
"SELECT m.id as messageId, m.title, m.content, m.sender_id as senderId, " +
"m.sender_name as senderName, m.msg_type as msgType, m.create_time as createTime, " +
"r.is_read as isRead, r.receiver_id as receiverId " +
"FROM sys_message m " +
"INNER JOIN sys_message_receiver r ON m.id = r.message_id " +
"WHERE r.receiver_id = #{userId} AND r.receiver_delete = 0 AND m.status = 0 " +
"<if test='isRead != null'> AND r.is_read = #{isRead}</if>" +
"<if test='msgType != null'> AND m.msg_type = #{msgType}</if>" +
"ORDER BY m.create_time DESC " +
"LIMIT #{offset}, #{pageSize}" +
"</script>")
List<MessageDTO> selectMessageList(MessageQueryDTO queryDTO);
// 查询未读数量
@Select("SELECT COUNT(*) FROM sys_message_receiver r " +
"INNER JOIN sys_message m ON r.message_id = m.id " +
"WHERE r.receiver_id = #{userId} AND r.is_read = 0 " +
"AND r.receiver_delete = 0 AND m.status = 0")
int selectUnreadCount(Long userId);
// 标记已读
@Update("UPDATE sys_message_receiver SET is_read = 1, read_time = NOW() " +
"WHERE message_id = #{messageId} AND receiver_id = #{userId} AND is_read = 0")
int markAsRead(@Param("messageId") Long messageId, @Param("userId") Long userId);
// 批量标记已读
@Update("UPDATE sys_message_receiver SET is_read = 1, read_time = NOW() " +
"WHERE receiver_id = #{userId} AND is_read = 0")
int markAllAsRead(Long userId);
// 删除消息(接收者)
@Update("UPDATE sys_message_receiver SET receiver_delete = 1 " +
"WHERE message_id = #{messageId} AND receiver_id = #{userId}")
int deleteReceiverMessage(@Param("messageId") Long messageId, @Param("userId") Long userId);
// 删除消息(发送者)
@Update("UPDATE sys_message SET status = 1 WHERE id = #{messageId} AND sender_id = #{userId}")
int deleteSenderMessage(@Param("messageId") Long messageId, @Param("userId") Long userId);
}
Service层实现
package com.example.message.service;
import com.example.message.dto.MessageDTO;
import com.example.message.dto.MessageQueryDTO;
import com.example.message.mapper.MessageMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
@Service
public class MessageService {
@Autowired
private MessageMapper messageMapper;
/**
* 发送站内信
*/
@Transactional(rollbackFor = Exception.class)
public boolean sendMessage(MessageDTO messageDTO) {
// 1. 验证参数
if (messageDTO.getTargets() == null || messageDTO.getTargets().isEmpty()) {
throw new IllegalArgumentException("接收人不能为空");
}
// 2. 插入消息主表
messageDTO.setCreateTime(null);
int result = messageMapper.insertMessage(messageDTO);
if (result > 0) {
// 3. 批量插入接收人
result = messageMapper.insertReceivers(messageDTO.getId(), messageDTO.getTargets());
return result == messageDTO.getTargets().size();
}
return false;
}
/**
* 批量发送消息给指定用户
*/
public boolean batchSend(Integer msgType, String title, String content,
Long senderId, String senderName, List<Long> targets) {
MessageDTO messageDTO = new MessageDTO();
messageDTO.setMsgType(msgType);
messageDTO.setTitle(title);
messageDTO.setContent(content);
messageDTO.setSenderId(senderId);
messageDTO.setSenderName(senderName);
messageDTO.setTargets(targets);
return sendMessage(messageDTO);
}
/**
* 发送系统消息
*/
public boolean sendSystemMessage(String title, String content, List<Long> targets) {
return batchSend(0, title, content, 0L, "系统", targets);
}
/**
* 获取消息列表
*/
public List<MessageDTO> getMessageList(MessageQueryDTO queryDTO) {
queryDTO.setOffset((queryDTO.getPageNum() - 1) * queryDTO.getPageSize());
return messageMapper.selectMessageList(queryDTO);
}
/**
* 获取未读消息数量
*/
public int getUnreadCount(Long userId) {
return messageMapper.selectUnreadCount(userId);
}
/**
* 标记消息为已读
*/
public boolean markAsRead(Long messageId, Long userId) {
return messageMapper.markAsRead(messageId, userId) > 0;
}
/**
* 批量标记已读
*/
public boolean markAllAsRead(Long userId) {
return messageMapper.markAllAsRead(userId) > 0;
}
/**
* 删除接收者消息
*/
public boolean deleteMessage(Long messageId, Long userId) {
return messageMapper.deleteReceiverMessage(messageId, userId) > 0;
}
/**
* 获取消息详情(带已读标记)
*/
public MessageDTO getMessageDetail(Long messageId, Long userId) {
// 标记为已读
markAsRead(messageId, userId);
// 查询消息详情
MessageQueryDTO queryDTO = new MessageQueryDTO();
queryDTO.setUserId(userId);
queryDTO.setPageNum(1);
queryDTO.setPageSize(1);
List<MessageDTO> messages = messageMapper.selectMessageList(queryDTO);
return messages.isEmpty() ? null : messages.get(0);
}
}
Controller层实现
package com.example.message.controller;
import com.example.message.dto.MessageDTO;
import com.example.message.dto.MessageQueryDTO;
import com.example.message.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/message")
public class MessageController {
@Autowired
private MessageService messageService;
/**
* 发送消息
*/
@PostMapping("/send")
public Result sendMessage(@RequestBody MessageDTO messageDTO) {
boolean success = messageService.sendMessage(messageDTO);
return success ? Result.success() : Result.error("发送失败");
}
/**
* 获取消息列表
*/
@GetMapping("/list")
public Result getMessageList(@RequestParam Long userId,
@RequestParam(required = false) Integer isRead,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
MessageQueryDTO queryDTO = new MessageQueryDTO();
queryDTO.setUserId(userId);
queryDTO.setIsRead(isRead);
queryDTO.setPageNum(pageNum);
queryDTO.setPageSize(pageSize);
List<MessageDTO> list = messageService.getMessageList(queryDTO);
return Result.success(list);
}
/**
* 获取未读消息数量
*/
@GetMapping("/unread-count")
public Result getUnreadCount(@RequestParam Long userId) {
int count = messageService.getUnreadCount(userId);
return Result.success(count);
}
/**
* 标记为已读
*/
@PostMapping("/read")
public Result markAsRead(@RequestParam Long messageId,
@RequestParam Long userId) {
boolean success = messageService.markAsRead(messageId, userId);
return success ? Result.success() : Result.error("标记失败");
}
/**
* 批量标记已读
*/
@PostMapping("/read-all")
public Result markAllAsRead(@RequestParam Long userId) {
boolean success = messageService.markAllAsRead(userId);
return success ? Result.success() : Result.error("标记失败");
}
/**
* 删除消息
*/
@DeleteMapping("/delete")
public Result deleteMessage(@RequestParam Long messageId,
@RequestParam Long userId) {
boolean success = messageService.deleteMessage(messageId, userId);
return success ? Result.success() : Result.error("删除失败");
}
}
使用示例
@RestController
public class TestController {
@Autowired
private MessageService messageService;
@PostMapping("/test/send")
public String testSend() {
// 发送系统通知
List<Long> targets = Arrays.asList(1L, 2L, 3L);
messageService.sendSystemMessage("系统升级通知",
"系统将于今晚进行升级,请提前保存数据。", targets);
// 发送用户消息
MessageDTO dto = new MessageDTO();
dto.setMsgType(1);
dto.setTitle("你好");
dto.setContent("用户消息测试");
dto.setSenderId(100L);
dto.setSenderName("用户A");
dto.setTargets(Collections.singletonList(1L));
messageService.sendMessage(dto);
// 查询未读数量
int unreadCount = messageService.getUnreadCount(1L);
System.out.println("未读消息数: " + unreadCount);
// 获取消息列表
MessageQueryDTO query = new MessageQueryDTO();
query.setUserId(1L);
query.setPageNum(1);
query.setPageSize(10);
List<MessageDTO> list = messageService.getMessageList(query);
return "发送成功,未读数量: " + unreadCount;
}
}
优化建议
-
索引优化:
sys_message表建议添加索引index_idx_title(title)等,sys_message_receiver表建议添加联合索引(receiver_id, is_read) -
缓存优化:使用Redis缓存用户未读数量
public int getUnreadCountCached(Long userId) {
String key = "message:unread:" + userId;
Integer count = redisTemplate.opsForValue().get(key);
if (count == null) {
count = messageMapper.selectUnreadCount(userId);
redisTemplate.opsForValue().set(key, count, 5, TimeUnit.MINUTES);
}
return count;
}
-
异步发送:对于批量发送消息,使用消息队列(如RabbitMQ)
-
定时清理:定期清理已删除超过一定时间的消息
-
支持通知推送:可集成WebSocket或第三方推送实现实时通知
这个实现涵盖了站内信系统的核心功能,可以根据需求进行扩展和完善。