PHP项目怎样实现用户私信功能?

wen PHP项目 21

本文目录导读:

PHP项目怎样实现用户私信功能?

  1. 数据库设计
  2. PHP核心功能实现
  3. 前后端交互实现
  4. 安全性与优化建议
  5. 消息实时推送方案

我来详细说明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秒检查一次
}

这个实现方案涵盖了私信功能的核心需求,你可以根据实际项目要求进行调整,记得在生产环境中做好错误处理、日志记录和性能监控。

抱歉,评论功能暂时关闭!