PHP项目聊天记录如何分页查询加载

wen PHP项目 24

本文目录导读:

PHP项目聊天记录如何分页查询加载

  1. 基于传统LIMIT分页(不推荐用于聊天)
  2. 基于游标的分页(推荐用于聊天)
  3. AJAX前端实现
  4. PHP API接口
  5. 优化建议

在PHP项目中实现聊天记录的分页查询加载,有几种常见方案,我来详细说明最优的实现方式:

基于传统LIMIT分页(不推荐用于聊天)

<?php
// 传统分页查询
function getChatHistory($userId, $page = 1, $pageSize = 20) {
    $offset = ($page - 1) * $pageSize;
    $sql = "SELECT * FROM chat_messages 
            WHERE user_id = ? 
            ORDER BY created_at DESC 
            LIMIT ? OFFSET ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$userId, $pageSize, $offset]);
    return $stmt->fetchAll();
}

基于游标的分页(推荐用于聊天)

<?php
class ChatHistoryPagination {
    private $pdo;
    public function __construct($pdo) {
        $this->pdo = $pdo;
    }
    /**
     * 加载更多历史消息(上拉加载)
     * @param int $chatId 聊天会话ID
     * @param int $beforeMessageId 当前页面最上面的消息ID
     * @param int $limit 加载数量
     */
    public function loadMoreHistory($chatId, $beforeMessageId = null, $limit = 20) {
        if ($beforeMessageId) {
            // 加载某条消息之前的消息
            $sql = "SELECT * FROM chat_messages 
                    WHERE chat_id = ? AND id < ? 
                    ORDER BY created_at DESC 
                    LIMIT ?";
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute([$chatId, $beforeMessageId, $limit]);
        } else {
            // 首次加载,获取最新消息
            $sql = "SELECT * FROM chat_messages 
                    WHERE chat_id = ? 
                    ORDER BY created_at DESC 
                    LIMIT ?";
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute([$chatId, $limit]);
        }
        $messages = $stmt->fetchAll();
        // 反转数组,使消息按时间正序排列
        $messages = array_reverse($messages);
        return [
            'messages' => $messages,
            'hasMore' => $this->hasMoreMessages($chatId, $messages[0]['id'] ?? null),
            'cursor' => $messages[0]['id'] ?? null
        ];
    }
    /**
     * 检查是否还有更早的消息
     */
    private function hasMoreMessages($chatId, $oldestMessageId) {
        if (!$oldestMessageId) return false;
        $sql = "SELECT COUNT(*) FROM chat_messages 
                WHERE chat_id = ? AND id < ?";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$chatId, $oldestMessageId]);
        return $stmt->fetchColumn() > 0;
    }
}

AJAX前端实现

// JavaScript 前端代码
class ChatHistoryLoader {
    constructor(chatContainer, chatId) {
        this.container = chatContainer;
        this.chatId = chatId;
        this.beforeMessageId = null;
        this.isLoading = false;
        this.hasMore = true;
        this.init();
    }
    init() {
        // 首次加载最新消息
        this.loadMessages();
        // 监听滚动条到顶部的加载事件
        this.container.addEventListener('scroll', () => {
            if (this.container.scrollTop === 0 && !this.isLoading && this.hasMore) {
                this.loadMessages();
            }
        });
    }
    async loadMessages() {
        if (this.isLoading || !this.hasMore) return;
        this.isLoading = true;
        this.showLoadingIndicator();
        try {
            const response = await fetch('/api/chat-history.php', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    chat_id: this.chatId,
                    before_message_id: this.beforeMessageId,
                    limit: 20
                })
            });
            const data = await response.json();
            if (data.success && data.data.messages.length > 0) {
                // 保存滚动位置
                const scrollHeight = this.container.scrollHeight;
                // 插入消息到容器顶部
                this.prependMessages(data.data.messages);
                // 更新游标
                this.beforeMessageId = data.data.messages[0].id;
                this.hasMore = data.data.hasMore;
                // 保持滚动位置
                this.container.scrollTop = this.container.scrollHeight - scrollHeight;
            } else {
                this.hasMore = false;
            }
        } catch (error) {
            console.error('加载聊天记录失败:', error);
        } finally {
            this.isLoading = false;
            this.hideLoadingIndicator();
        }
    }
    prependMessages(messages) {
        messages.forEach(message => {
            const messageElement = this.createMessageElement(message);
            this.container.insertBefore(messageElement, this.container.firstChild);
        });
    }
    createMessageElement(message) {
        const div = document.createElement('div');
        div.className = `message ${message.sender === 'user' ? 'user-message' : 'other-message'}`;
        div.innerHTML = `
            <div class="message-content">${this.escapeHtml(message.content)}</div>
            <div class="message-time">${this.formatTime(message.created_at)}</div>
        `;
        return div;
    }
    escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }
    formatTime(timestamp) {
        const date = new Date(timestamp);
        return date.toLocaleString('zh-CN', {
            hour: '2-digit',
            minute: '2-digit',
            month: 'numeric',
            day: 'numeric'
        });
    }
    showLoadingIndicator() {
        const loading = document.createElement('div');
        loading.className = 'loading-indicator';
        loading.textContent = '加载中...';
        this.container.prepend(loading);
    }
    hideLoadingIndicator() {
        const loading = this.container.querySelector('.loading-indicator');
        if (loading) {
            loading.remove();
        }
    }
}
// 使用示例
const chatContainer = document.getElementById('chat-messages');
const chatLoader = new ChatHistoryLoader(chatContainer, 123);

PHP API接口

<?php
// /api/chat-history.php
header('Content-Type: application/json');
try {
    $input = json_decode(file_get_contents('php://input'), true);
    $chatId = $input['chat_id'] ?? null;
    $beforeMessageId = $input['before_message_id'] ?? null;
    $limit = min($input['limit'] ?? 20, 50); // 限制最大加载数量
    if (!$chatId) {
        throw new Exception('缺少聊天会话ID');
    }
    $pagination = new ChatHistoryPagination($pdo);
    $result = $pagination->loadMoreHistory($chatId, $beforeMessageId, $limit);
    echo json_encode([
        'success' => true,
        'data' => $result
    ]);
} catch (Exception $e) {
    http_response_code(400);
    echo json_encode([
        'success' => false,
        'error' => $e->getMessage()
    ]);
}

优化建议

数据库优化

-- 创建联合索引优化查询
CREATE INDEX idx_chat_messages_lookup ON chat_messages(chat_id, id DESC);

缓存策略

<?php
class ChatHistoryCache {
    private $redis;
    private $cachePrefix = 'chat_history:';
    private $cacheTime = 300; // 5分钟
    public function getCachedHistory($chatId, $page) {
        $key = $this->cachePrefix . $chatId . ':' . $page;
        $data = $this->redis->get($key);
        return $data ? json_decode($data, true) : null;
    }
    public function setCachedHistory($chatId, $page, $data) {
        $key = $this->cachePrefix . $chatId . ':' . $page;
        $this->redis->setex($key, $this->cacheTime, json_encode($data));
    }
}

这种基于游标的分页方式最适合聊天记录,因为它:

  • 性能更好(基于索引查询)
  • 不受数据变动影响(新消息不影响旧数据的分页)
  • 用户体验更好(适合上拉加载更多)

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