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設計、安全策略、性能優化等多個方面。該系統具有良好的擴展性和可維護性,能夠滿足大多數企業級應用的站內信需求。