1. 系统概述
1.1 项目背景
本系统旨在为企业或社区平台提供一套完整的站内信解决方案,支持用户之间的消息发送、接收、管理等功能,提升用户间的沟通效率。
1.2 设计目标
- 实现用户间消息发送和接收
- 支持一对一和一对多消息发送
- 提供消息状态跟踪(已读/未读)
- 实现消息分类和管理
- 保证系统的高可用性和可扩展性
2. 技术架构
2.1 整体架构
表现层 (Web Layer) -> Spring MVC + JSP/Thymeleaf
业务层 (Service Layer) -> Spring Service
持久层 (DAO Layer) -> Spring Data JPA + Hibernate
数据库 (Database) -> MySQL 8.0
缓存层 (Cache) -> Redis
消息队列 (MQ) -> RabbitMQ (可选)
2.2 技术选型
- 后端框架: Spring Boot 2.7.x
- ORM框架: Spring Data JPA
- 数据库: MySQL 8.0
- 缓存: Redis
- 前端: HTML5 + CSS3 + JavaScript + Bootstrap 5
- 构建工具: Maven
- 服务器: Tomcat 9+
3. 数据库设计
3.1 数据库ER图
用户(User) --(1:n)--> 站内信(Message)
用户(User) --(1:n)--> 收件箱(MessageInbox)
3.2 数据表结构
用户表 (sys_user)
CREATE TABLE `sys_user` (`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',`username` varchar(50) NOT NULL COMMENT '用户名',`email` varchar(100) DEFAULT NULL COMMENT '邮箱',`status` tinyint(1) DEFAULT '1' COMMENT '状态(0:禁用,1:启用)',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`user_id`),UNIQUE KEY `uniq_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
站内信表 (sys_message)
CREATE TABLE `sys_message` (`message_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '消息ID',`sender_id` bigint(20) NOT NULL COMMENT '发送者ID',`title` varchar(200) NOT NULL COMMENT '消息标题',`content` text NOT NULL COMMENT '消息内容',`message_type` tinyint(1) DEFAULT '1' COMMENT '消息类型(1:普通消息,2:系统通知,3:公告)',`priority` tinyint(1) DEFAULT '1' COMMENT '优先级(1:普通,2:重要,3:紧急)',`send_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '发送时间',`is_draft` tinyint(1) DEFAULT '0' COMMENT '是否为草稿(0:否,1:是)',PRIMARY KEY (`message_id`),KEY `idx_sender` (`sender_id`),KEY `idx_send_time` (`send_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='站内信表';
收件箱表 (sys_message_inbox)
CREATE TABLE `sys_message_inbox` (`inbox_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '收件箱ID',`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 '阅读时间',`is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除(0:否,1:是)',`delete_time` datetime DEFAULT NULL COMMENT '删除时间',PRIMARY KEY (`inbox_id`),UNIQUE KEY `uniq_message_receiver` (`message_id`, `receiver_id`),KEY `idx_receiver` (`receiver_id`),KEY `idx_is_read` (`is_read`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收件箱表';
消息附件表 (sys_message_attachment)
CREATE TABLE `sys_message_attachment` (`attachment_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '附件ID',`message_id` bigint(20) NOT NULL COMMENT '消息ID',`file_name` varchar(255) NOT NULL COMMENT '文件名',`file_path` varchar(500) NOT NULL COMMENT '文件路径',`file_size` bigint(20) DEFAULT '0' COMMENT '文件大小(字节)',`upload_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',PRIMARY KEY (`attachment_id`),KEY `idx_message` (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息附件表';
4. 核心功能模块设计
4.1 消息发送模块
@Service
public class MessageServiceImpl implements MessageService {@Autowiredprivate MessageRepository messageRepository;@Autowiredprivate MessageInboxRepository messageInboxRepository;@Autowiredprivate UserRepository userRepository;@Autowiredprivate RabbitTemplate rabbitTemplate;@Override@Transactionalpublic ApiResponse sendMessage(MessageDTO messageDTO) {// 1. 验证发送者身份User sender = userRepository.findById(messageDTO.getSenderId()).orElseThrow(() -> new BusinessException("发送者不存在"));// 2. 保存消息主体Message message = convertToEntity(messageDTO);messageRepository.save(message);// 3. 处理接收者if (messageDTO.getReceiverIds() != null && !messageDTO.getReceiverIds().isEmpty()) {List<MessageInbox> inboxList = new ArrayList<>();for (Long receiverId : messageDTO.getReceiverIds()) {User receiver = userRepository.findById(receiverId).orElseThrow(() -> new BusinessException("接收者ID[" + receiverId + "]不存在"));MessageInbox inbox = new MessageInbox();inbox.setMessageId(message.getMessageId());inbox.setReceiverId(receiverId);inbox.setIsRead(0);inboxList.add(inbox);}messageInboxRepository.saveAll(inboxList);// 4. 发送消息通知 (异步处理)sendMessageNotification(message, inboxList);}return ApiResponse.success("消息发送成功", message.getMessageId());}private void sendMessageNotification(Message message, List<MessageInbox> inboxList) {// 使用消息队列异步发送通知Map<String, Object> notification = new HashMap<>();notification.put("messageId", message.getMessageId());notification.put("title", message.getTitle());notification.put("receiverIds", inboxList.stream().map(MessageInbox::getReceiverId).collect(Collectors.toList()));rabbitTemplate.convertAndSend("message.exchange", "message.notification", notification);}
}
4.2 消息接收与查询模块
@Service
public class MessageInboxServiceImpl implements MessageInboxService {@Autowiredprivate MessageInboxRepository messageInboxRepository;@Autowiredprivate MessageRepository messageRepository;@Overridepublic Page<MessageVO> getMessagesByUser(Long userId, MessageQueryDTO queryDTO, Pageable pageable) {// 构建查询条件Specification<MessageInbox> spec = (root, query, cb) -> {List<Predicate> predicates = new ArrayList<>();predicates.add(cb.equal(root.get("receiverId"), userId));predicates.add(cb.equal(root.get("isDeleted"), 0));if (queryDTO.getIsRead() != null) {predicates.add(cb.equal(root.get("isRead"), queryDTO.getIsRead()));}if (StringUtils.isNotBlank(queryDTO.getKeyword())) {Join<MessageInbox, Message> messageJoin = root.join("message", JoinType.INNER);predicates.add(cb.or(cb.like(messageJoin.get("title"), "%" + queryDTO.getKeyword() + "%"),cb.like(messageJoin.get("content"), "%" + queryDTO.getKeyword() + "%")));}return cb.and(predicates.toArray(new Predicate[0]));};// 执行查询Page<MessageInbox> inboxPage = messageInboxRepository.findAll(spec, pageable);// 转换为VOreturn inboxPage.map(this::convertToVO);}@Override@Transactionalpublic void markAsRead(Long inboxId, Long userId) {MessageInbox inbox = messageInboxRepository.findById(inboxId).orElseThrow(() -> new BusinessException("消息不存在"));if (!inbox.getReceiverId().equals(userId)) {throw new BusinessException("无权操作此消息");}if (inbox.getIsRead() == 0) {inbox.setIsRead(1);inbox.setReadTime(new Date());messageInboxRepository.save(inbox);}}
}
4.3 消息推送模块(WebSocket)
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {config.enableSimpleBroker("/topic");config.setApplicationDestinationPrefixes("/app");}@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/ws-message").setAllowedOriginPatterns("*").withSockJS();}
}@Component
public class MessageWebSocketHandler {@Autowiredprivate SimpMessagingTemplate messagingTemplate;/*** 向指定用户发送实时消息*/public void sendMessageToUser(Long userId, MessageVO message) {messagingTemplate.convertAndSendToUser(userId.toString(), "/topic/messages", message);}/*** 广播系统消息*/public void broadcastSystemMessage(MessageVO message) {messagingTemplate.convertAndSend("/topic/system-messages", message);}
}
5. API接口设计
5.1 RESTful API设计
发送消息
POST /api/messages
Content-Type: application/json{"senderId": 1,"receiverIds": [2, 3, 4],"title": "会议通知","content": "本周五下午2点召开项目会议","messageType": 1,"priority": 2
}
获取用户消息列表
GET /api/messages/inbox?page=0&size=20&isRead=0&keyword=会议
Authorization: Bearer <token>
标记消息为已读
PUT /api/messages/inbox/{inboxId}/read
Authorization: Bearer <token>
删除消息
DELETE /api/messages/inbox/{inboxId}
Authorization: Bearer <token>
获取未读消息数量
GET /api/messages/inbox/unread-count
Authorization: Bearer <token>
6. 安全设计
6.1 权限控制
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/api/messages/send").hasAnyRole("USER", "ADMIN").antMatchers("/api/messages/**").authenticated().antMatchers("/api/admin/messages/**").hasRole("ADMIN").anyRequest().permitAll().and().csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);}
}
6.2 消息访问权限验证
@Component
public class MessagePermissionValidator {public boolean canAccessMessage(Long userId, Long messageId) {// 验证用户是否有权访问此消息// 1. 用户是发送者// 2. 用户是接收者return messageInboxRepository.existsByReceiverIdAndMessageId(userId, messageId) ||messageRepository.existsBySenderIdAndMessageId(userId, messageId);}
}
7. 性能优化设计
7.1 数据库优化
- 添加合适的索引
- 使用读写分离
- 大数据量表进行分表分库
7.2 缓存策略
@Service
public class MessageCacheService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;private static final String UNREAD_COUNT_KEY = "message:unread:count:";private static final long CACHE_EXPIRE_HOURS = 24;/*** 获取用户未读消息数量(带缓存)*/public long getUnreadCountWithCache(Long userId) {String key = UNREAD_COUNT_KEY + userId;Object count = redisTemplate.opsForValue().get(key);if (count != null) {return Long.parseLong(count.toString());}long actualCount = messageInboxRepository.countByReceiverIdAndIsRead(userId, 0);redisTemplate.opsForValue().set(key, actualCount, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);return actualCount;}/*** 清除未读数量缓存*/public void clearUnreadCountCache(Long userId) {String key = UNREAD_COUNT_KEY + userId;redisTemplate.delete(key);}
}
7.3 异步处理
@Component
public class MessageAsyncProcessor {@Async("messageTaskExecutor")public void processMassMessage(Message message, List<Long> receiverIds) {// 分批处理大量接收者List<List<Long>> batches = Lists.partition(receiverIds, 1000);for (List<Long> batch : batches) {saveMessageInboxBatch(message.getMessageId(), batch);sendNotificationsBatch(message, batch);}}private void saveMessageInboxBatch(Long messageId, List<Long> receiverIds) {// 批量保存收件箱记录List<MessageInbox> inboxList = receiverIds.stream().map(receiverId -> {MessageInbox inbox = new MessageInbox();inbox.setMessageId(messageId);inbox.setReceiverId(receiverId);inbox.setIsRead(0);return inbox;}).collect(Collectors.toList());messageInboxRepository.saveAll(inboxList);}
}
8. 部署架构
8.1 系统部署图
负载均衡器 (Nginx)||-- 应用服务器集群 (Tomcat × 3)| || |-- 站内信服务| |-- WebSocket服务||-- 数据库主从集群 (MySQL Master-Slave)||-- 缓存集群 (Redis Sentinel)||-- 消息队列 (RabbitMQ集群)
8.2 监控与日志
- 使用Spring Boot Actuator进行健康检查
- 集成Prometheus + Grafana监控
- 使用ELK Stack收集和分析日志
9. 扩展性考虑
9.1 未来功能扩展
- 支持消息模板
- 消息撤回功能
- 消息过期自动删除
- 消息分类和标签
- 消息搜索高级功能
9.2 技术扩展
- 微服务化改造
- 分布式事务支持
- 多租户支持
- 国际化支持
10. 总结
本设计文档详细阐述了一个基于JavaWeb的通用站内信系统的技术设计方案,涵盖了系统架构、数据库设计、核心功能实现、API设计、安全策略、性能优化等多个方面。该系统具有良好的扩展性和可维护性,能够满足大多数企业级应用的站内信需求。