本文目录导读:

我来详细说明PHP项目实现用户私信功能的完整方案:
数据库设计
私信相关表结构
-- 私信会话表
CREATE TABLE `conversations` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`subject` VARCHAR(200) DEFAULT NULL COMMENT '私信主题',
`initiator_id` INT NOT NULL COMMENT '发起人ID',
`last_message_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '最后消息时间',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_user` (`initiator_id`),
INDEX `idx_last_message` (`last_message_at`)
);
-- 会话参与者表
CREATE TABLE `conversation_participants` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`conversation_id` INT NOT NULL,
`user_id` INT NOT NULL,
`is_read` TINYINT(1) DEFAULT 0 COMMENT '是否已读',
`last_read_at` TIMESTAMP NULL COMMENT '最后阅读时间',
FOREIGN KEY (`conversation_id`) REFERENCES `conversations`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`),
UNIQUE KEY `unique_conversation_user` (`conversation_id`, `user_id`)
);
-- 消息表
CREATE TABLE `messages` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`conversation_id` INT NOT NULL,
`sender_id` INT NOT NULL,
`message_type` ENUM('text', 'image', 'file', 'system') DEFAULT 'text',
`content` TEXT NOT NULL,
`is_deleted` TINYINT(1) DEFAULT 0 COMMENT '是否删除',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`conversation_id`) REFERENCES `conversations`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`sender_id`) REFERENCES `users`(`id`),
INDEX `idx_conversation` (`conversation_id`),
INDEX `idx_created_at` (`created_at`)
);
PHP核心功能实现
私信核心类
<?php
class MessageSystem {
private $db;
public function __construct($db) {
$this->db = $db;
}
// 创建新会话
public function createConversation($initiatorId, $recipientId, $subject = null) {
try {
// 检查是否已有会话
$existingConvo = $this->findExistingConversation($initiatorId, $recipientId);
if ($existingConvo) {
return $existingConvo;
}
$this->db->beginTransaction();
// 创建会话
$stmt = $this->db->prepare("
INSERT INTO conversations (initiator_id, subject)
VALUES (?, ?)
");
$stmt->execute([$initiatorId, $subject]);
$conversationId = $this->db->lastInsertId();
// 添加参与者
$participants = [$initiatorId, $recipientId];
$stmt = $this->db->prepare("
INSERT INTO conversation_participants
(conversation_id, user_id) VALUES (?, ?)
");
foreach ($participants as $userId) {
$stmt->execute([$conversationId, $userId]);
}
$this->db->commit();
return $conversationId;
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
// 发送消息
public function sendMessage($conversationId, $senderId, $content, $type = 'text') {
// 验证发送者是参与者
if (!$this->isParticipant($conversationId, $senderId)) {
throw new Exception("您不是该会话的参与者");
}
try {
$this->db->beginTransaction();
// 插入消息
$stmt = $this->db->prepare("
INSERT INTO messages
(conversation_id, sender_id, message_type, content)
VALUES (?, ?, ?, ?)
");
$stmt->execute([$conversationId, $senderId, $type, $content]);
// 更新会话最后消息时间
$stmt = $this->db->prepare("
UPDATE conversations
SET last_message_at = NOW()
WHERE id = ?
");
$stmt->execute([$conversationId]);
// 更新其他参与者的未读状态
$stmt = $this->db->prepare("
UPDATE conversation_participants
SET is_read = 0
WHERE conversation_id = ? AND user_id != ?
");
$stmt->execute([$conversationId, $senderId]);
$this->db->commit();
// 触发通知(可选)
$this->notifyParticipants($conversationId, $senderId, $content);
return true;
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
// 获取用户会话列表
public function getUserConversations($userId) {
$stmt = $this->db->prepare("
SELECT
c.*,
cp.is_read,
cp.last_read_at,
u.username as other_username,
u.avatar as other_avatar,
m.content as last_message_content,
m.created_at as last_message_time
FROM conversations c
INNER JOIN conversation_participants cp ON c.id = cp.conversation_id
INNER JOIN conversation_participants cp2 ON c.id = cp2.conversation_id
AND cp2.user_id != ?
INNER JOIN users u ON cp2.user_id = u.id
LEFT JOIN messages m ON c.id = m.id
WHERE cp.user_id = ?
ORDER BY c.last_message_at DESC
");
$stmt->execute([$userId, $userId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 获取会话消息
public function getConversationMessages($conversationId, $userId, $offset = 0, $limit = 20) {
// 验证用户是参与者
if (!$this->isParticipant($conversationId, $userId)) {
return [];
}
// 标记为已读
$this->markAsRead($conversationId, $userId);
$stmt = $this->db->prepare("
SELECT
m.*,
u.username as sender_name,
u.avatar as sender_avatar
FROM messages m
INNER JOIN users u ON m.sender_id = u.id
WHERE m.conversation_id = ? AND m.is_deleted = 0
ORDER BY m.created_at ASC
LIMIT ? OFFSET ?
");
$stmt->execute([$conversationId, $limit, $offset]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 获取未读消息数量
public function getUnreadCount($userId) {
$stmt = $this->db->prepare("
SELECT COUNT(*) as count
FROM conversation_participants cp
WHERE cp.user_id = ? AND cp.is_read = 0
");
$stmt->execute([$userId]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result['count'];
}
// 检查用户是否是参与者
private function isParticipant($conversationId, $userId) {
$stmt = $this->db->prepare("
SELECT id FROM conversation_participants
WHERE conversation_id = ? AND user_id = ?
");
$stmt->execute([$conversationId, $userId]);
return $stmt->fetch(PDO::FETCH_ASSOC) !== false;
}
// 标记为已读
private function markAsRead($conversationId, $userId) {
$stmt = $this->db->prepare("
UPDATE conversation_participants
SET is_read = 1, last_read_at = NOW()
WHERE conversation_id = ? AND user_id = ?
");
$stmt->execute([$conversationId, $userId]);
}
// 查找已有会话
private function findExistingConversation($userId1, $userId2) {
$stmt = $this->db->prepare("
SELECT c.id
FROM conversations c
INNER JOIN conversation_participants cp1 ON c.id = cp1.conversation_id
INNER JOIN conversation_participants cp2 ON c.id = cp2.conversation_id
WHERE cp1.user_id = ? AND cp2.user_id = ?
AND (SELECT COUNT(*) FROM conversation_participants WHERE conversation_id = c.id) = 2
LIMIT 1
");
$stmt->execute([$userId1, $userId2]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ? $result['id'] : false;
}
// 通知参与者(可扩展)
private function notifyParticipants($conversationId, $senderId, $content) {
// 实现通知逻辑,如推送、邮件等
// 获取其他参与者并发送通知
}
}
前后端交互实现
API 接口示例
<?php
// send_message.php
header('Content-Type: application/json');
require_once 'config.php';
require_once 'MessageSystem.php';
$msgSystem = new MessageSystem($db);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$conversationId = $_POST['conversation_id'];
$content = $_POST['content'];
$senderId = $_SESSION['user_id'];
try {
$result = $msgSystem->sendMessage($conversationId, $senderId, $content);
echo json_encode(['success' => true, 'message' => '消息发送成功']);
} catch (Exception $e) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
}
前端JavaScript示例
// message.js
class MessageManager {
constructor() {
this.conversationId = null;
this.pollInterval = null;
}
// 发送消息
async sendMessage(conversationId, content) {
try {
const response = await fetch('send_message.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `conversation_id=${conversationId}&content=${encodeURIComponent(content)}`
});
const data = await response.json();
if (data.success) {
this.appendMessage(content, 'self');
}
return data;
} catch (error) {
console.error('发送消息失败:', error);
}
}
// 加载消息
async loadMessages(conversationId) {
const response = await fetch(`get_messages.php?conversation_id=${conversationId}`);
const messages = await response.json();
this.renderMessages(messages);
}
// 轮询新消息
startPolling(conversationId) {
this.conversationId = conversationId;
this.pollInterval = setInterval(() => {
this.checkNewMessages();
}, 3000); // 3秒轮询一次
}
// WebSocket连接(推荐替代轮询)
connectWebSocket(conversationId) {
const ws = new WebSocket('wss://your-server.com/chat');
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'join',
conversation_id: conversationId,
user_id: currentUserId
}));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleNewMessage(message);
};
return ws;
}
// 渲染消息
renderMessages(messages) {
const container = document.getElementById('message-container');
container.innerHTML = '';
messages.forEach(msg => {
const messageEl = this.createMessageElement(msg);
container.appendChild(messageEl);
});
container.scrollTop = container.scrollHeight;
}
}
安全性与优化建议
安全措施
// 消息过滤
function sanitizeMessage($content) {
// XSS防护
$content = htmlspecialchars($content, ENT_QUOTES, 'UTF-8');
// 敏感词过滤
$forbiddenWords = ['敏感词1', '敏感词2'];
$content = str_replace($forbiddenWords, '***', $content);
// SQL注入防护(使用预处理语句)
return $content;
}
// 权限验证中间件
function checkMessagePermission($conversationId, $userId) {
// 验证用户权限
if (!isParticipant($conversationId, $userId)) {
http_response_code(403);
die(json_encode(['error' => '无权访问该会话']));
}
// 检查黑名单
if (isBlocked($userId, $otherUserId)) {
http_response_code(403);
die(json_encode(['error' => '对方已将你拉黑']));
}
}
性能优化
// 1. 分页加载
$limit = 20;
$offset = isset($_GET['page']) ? ($_GET['page'] - 1) * $limit : 0;
// 2. 缓存未读计数
function getCachedUnreadCount($userId) {
$cacheKey = "unread_count_{$userId}";
$count = apcu_fetch($cacheKey);
if ($count === false) {
$count = $msgSystem->getUnreadCount($userId);
apcu_store($cacheKey, $count, 300); // 缓存5分钟
}
return $count;
}
// 3. 数据库索引优化
// 确保已创建适当的索引
$indexSql = "
CREATE INDEX idx_conversation_user ON conversation_participants(conversation_id, user_id);
CREATE INDEX idx_conversation_created ON messages(conversation_id, created_at);
";
消息实时推送方案
Pusher (推荐)
// 使用Pusher实现实时消息
require_once 'vendor/autoload.php';
$pusher = new Pusher\Pusher(
'your-app-key',
'your-app-secret',
'your-app-id',
['cluster' => 'your-cluster']
);
// 发送消息时触发事件
$pusher->trigger("conversation-{$conversationId}", 'new-message', [
'message' => $message,
'sender_id' => $senderId
]);
服务器推送(SSE)
// server_sent_events.php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
$lastMessageId = $_GET['last_id'] ?? 0;
while (true) {
$newMessages = getNewMessages($lastMessageId);
if ($newMessages) {
echo "data: " . json_encode($newMessages) . "\n\n";
$lastMessageId = end($newMessages)['id'];
}
ob_flush();
flush();
sleep(3); // 3秒检查一次
}
这个实现方案涵盖了私信功能的核心需求,你可以根据实际项目要求进行调整,记得在生产环境中做好错误处理、日志记录和性能监控。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播