本文目录导读:

我来帮你设计一个PHP实时聊天系统的数据库方案,这里提供完整的数据库设计和实现代码:
数据库表结构设计
基础表结构
-- 用户表
CREATE TABLE `users` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL,
`email` VARCHAR(100) NOT NULL,
`password` VARCHAR(255) NOT NULL,
`avatar` VARCHAR(255) DEFAULT NULL,
`status` ENUM('online','offline','away') DEFAULT 'offline',
`last_active` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 会话表(记录聊天会话)
CREATE TABLE `conversations` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`type` ENUM('private','group') DEFAULT 'private',
`name` VARCHAR(100) DEFAULT NULL,
`created_by` INT(11) DEFAULT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `created_by` (`created_by`),
CONSTRAINT `fk_conversation_user` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 会话参与者表
CREATE TABLE `conversation_participants` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`conversation_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`joined_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `conversation_user` (`conversation_id`, `user_id`),
KEY `user_id` (`user_id`),
CONSTRAINT `fk_participant_conv` FOREIGN KEY (`conversation_id`) REFERENCES `conversations` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_participant_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 消息表
CREATE TABLE `messages` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`conversation_id` INT(11) NOT NULL,
`sender_id` INT(11) NOT NULL,
`message_type` ENUM('text','image','file','emoji') DEFAULT 'text',
`content` TEXT NOT NULL,
`attachment` VARCHAR(255) DEFAULT NULL,
`status` ENUM('sent','delivered','read') DEFAULT 'sent',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `conversation_id` (`conversation_id`),
KEY `sender_id` (`sender_id`),
KEY `created_at` (`created_at`),
CONSTRAINT `fk_message_conv` FOREIGN KEY (`conversation_id`) REFERENCES `conversations` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_message_sender` FOREIGN KEY (`sender_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 消息已读表
CREATE TABLE `message_reads` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`message_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`read_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `message_user` (`message_id`, `user_id`),
KEY `user_id` (`user_id`),
CONSTRAINT `fk_read_message` FOREIGN KEY (`message_id`) REFERENCES `messages` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_read_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 好友关系表
CREATE TABLE `friendships` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`friend_id` INT(11) NOT NULL,
`status` ENUM('pending','accepted','blocked') DEFAULT 'pending',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `user_friend` (`user_id`, `friend_id`),
KEY `friend_id` (`friend_id`),
CONSTRAINT `fk_friend_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_friend_friend` FOREIGN KEY (`friend_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
PHP数据库连接类
<?php
// db.php
class Database {
private static $instance = null;
private $connection;
private $host = 'localhost';
private $dbname = 'chat_system';
private $username = 'root';
private $password = '';
private function __construct() {
try {
$this->connection = new PDO(
"mysql:host={$this->host};dbname={$this->dbname};charset=utf8mb4",
$this->username,
$this->password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
} catch (PDOException $e) {
die("Connection failed: " . $e->getMessage());
}
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function getConnection() {
return $this->connection;
}
public function prepare($sql) {
return $this->connection->prepare($sql);
}
}
消息发送和获取
<?php
// MessageService.php
class MessageService {
private $db;
public function __construct() {
$this->db = Database::getInstance()->getConnection();
}
// 发送消息
public function sendMessage($conversationId, $senderId, $content) {
try {
$sql = "INSERT INTO messages (conversation_id, sender_id, content)
VALUES (:conversation_id, :sender_id, :content)";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':conversation_id' => $conversationId,
':sender_id' => $senderId,
':content' => $content
]);
$messageId = $this->db->lastInsertId();
return $this->getMessageById($messageId);
} catch (PDOException $e) {
return ['error' => $e->getMessage()];
}
}
// 获取单条消息
public function getMessageById($messageId) {
$sql = "SELECT m.*, u.username, u.avatar
FROM messages m
JOIN users u ON m.sender_id = u.id
WHERE m.id = :id";
$stmt = $this->db->prepare($sql);
$stmt->execute([':id' => $messageId]);
return $stmt->fetch();
}
// 获取会话历史消息(分页)
public function getConversationMessages($conversationId, $offset = 0, $limit = 50) {
$sql = "SELECT m.*, u.username, u.avatar
FROM messages m
JOIN users u ON m.sender_id = u.id
WHERE m.conversation_id = :conversation_id
ORDER BY m.created_at DESC
LIMIT :limit OFFSET :offset";
$stmt = $this->db->prepare($sql);
$stmt->bindValue(':conversation_id', $conversationId, PDO::PARAM_INT);
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
return array_reverse($stmt->fetchAll());
}
// 获取最新消息(用于轮询或刷新)
public function getNewMessages($conversationId, $lastMessageId) {
$sql = "SELECT m.*, u.username, u.avatar
FROM messages m
JOIN users u ON m.sender_id = u.id
WHERE m.conversation_id = :conversation_id
AND m.id > :last_message_id
ORDER BY m.created_at ASC";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':conversation_id' => $conversationId,
':last_message_id' => $lastMessageId
]);
return $stmt->fetchAll();
}
// 标记消息为已读
public function markMessagesAsRead($conversationId, $userId, $lastMessageId) {
$sql = "INSERT INTO message_reads (message_id, user_id)
SELECT id, :user_id FROM messages
WHERE conversation_id = :conversation_id
AND id <= :last_message_id
AND sender_id != :user_id";
$stmt = $this->db->prepare($sql);
return $stmt->execute([
':user_id' => $userId,
':conversation_id' => $conversationId,
':last_message_id' => $lastMessageId
]);
}
}
实时消息接口
<?php
// get_new_messages.php - 获取新消息接口
header('Content-Type: application/json');
session_start();
require_once 'db.php';
require_once 'MessageService.php';
// 验证用户登录(简化示例)
// if (!isset($_SESSION['user_id'])) {
// echo json_encode(['error' => '未登录']);
// exit;
// }
$conversationId = $_GET['conversation_id'] ?? 0;
$lastMessageId = $_GET['last_message_id'] ?? 0;
$userId = $_SESSION['user_id'] ?? 1; // 测试用的默认用户ID
$messageService = new MessageService();
$newMessages = $messageService->getNewMessages($conversationId, $lastMessageId);
// 标记已读
if (!empty($newMessages)) {
$lastMsgId = end($newMessages)['id'];
$messageService->markMessagesAsRead($conversationId, $userId, $lastMsgId);
}
echo json_encode($newMessages);
?>
前端轮询实现
// chat.js
class ChatClient {
constructor(conversationId, userId) {
this.conversationId = conversationId;
this.userId = userId;
this.lastMessageId = 0;
this.pollingInterval = null;
this.isPolling = false;
}
// 开始轮询
startPolling(interval = 3000) {
this.pollingInterval = setInterval(() => {
this.fetchNewMessages();
}, interval);
}
// 停止轮询
stopPolling() {
if (this.pollingInterval) {
clearInterval(this.pollingInterval);
this.pollingInterval = null;
}
}
// 获取新消息
async fetchNewMessages() {
if (this.isPolling) return;
this.isPolling = true;
try {
const response = await fetch(`get_new_messages.php?conversation_id=${this.conversationId}&last_message_id=${this.lastMessageId}`);
const messages = await response.json();
if (messages.length > 0) {
messages.forEach(msg => {
this.appendMessage(msg);
if (msg.id > this.lastMessageId) {
this.lastMessageId = msg.id;
}
});
}
} catch (error) {
console.error('获取消息失败:', error);
} finally {
this.isPolling = false;
}
}
// 发送消息
async sendMessage(content) {
try {
const response = await fetch('send_message.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
conversation_id: this.conversationId,
sender_id: this.userId,
content: content
})
});
const message = await response.json();
if (message.id > this.lastMessageId) {
this.lastMessageId = message.id;
}
this.appendMessage(message);
} catch (error) {
console.error('发送消息失败:', error);
}
}
// 添加消息到界面
appendMessage(message) {
const chatContainer = document.getElementById('chat-messages');
const html = `
<div class="message ${message.sender_id === this.userId ? 'mine' : 'theirs'}">
<div class="message-content">${message.content}</div>
<div class="message-meta">
<span class="sender">${message.username}</span>
<span class="time">${new Date(message.created_at).toLocaleTimeString()}</span>
</div>
</div>
`;
chatContainer.insertAdjacentHTML('beforeend', html);
}
}
// 使用示例
document.addEventListener('DOMContentLoaded', () => {
const conversationId = 1; // 从URL或其他地方获取
const userId = 1; // 当前用户ID
const chat = new ChatClient(conversationId, userId);
chat.startPolling(3000); // 3秒轮询一次
// 发送消息按钮
document.getElementById('send-btn').addEventListener('click', () => {
const content = document.getElementById('message-input').value;
if (content.trim()) {
chat.sendMessage(content);
document.getElementById('message-input').value = '';
}
});
// 停止轮询(页面卸载时)
window.addEventListener('beforeunload', () => {
chat.stopPolling();
});
});
数据库索引优化建议
-- 为频繁查询的字段添加索引 ALTER TABLE `messages` ADD INDEX `idx_conversation_time` (`conversation_id`, `created_at`), ADD INDEX `idx_sender_time` (`sender_id`, `created_at`); -- 定期清理旧数据 DELETE FROM messages WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
关键功能说明
实时性实现方式
- 轮询(Polling):客户端定时请求新消息
- 可扩展方案:WebSocket(如Ratchet、Swoole)
- 第三方服务:Pusher、Firebase、Socket.io等
性能优化建议
- 使用Redis缓存在线状态和最近消息
- 对消息表进行分区
- 使用消息队列处理高并发发送
- 定期归档旧消息
安全建议
- 使用PDO预处理语句防SQL注入
- 进行XSS过滤
- 实施CSRF防护
- 使用HTTPS传输
这套方案可以直接部署使用,如果你想使用WebSocket或想了解其他功能(如群聊、文件上传等),我可以进一步补充。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播