PHP项目怎样实现用户私信已读?

wen PHP项目 58

本文目录导读:

PHP项目怎样实现用户私信已读?

  1. 数据库设计
  2. 后端实现
  3. 前端实现
  4. API 端点实现
  5. 前端显示优化
  6. 高级功能优化
  7. 最佳实践建议

在PHP项目中实现用户私信已读功能,通常有几种常见方案,以下是完整的实现思路和代码示例:

数据库设计

首先需要设计消息表,添加状态字段:

CREATE TABLE `private_messages` (
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `from_user_id` INT NOT NULL,
    `to_user_id` INT NOT NULL,
    `content` TEXT NOT NULL,
    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
    `read_at` DATETIME NULL,  -- 已读时间,NULL表示未读
    `is_read` TINYINT(1) DEFAULT 0,  -- 0未读,1已读
    INDEX `idx_to_user` (`to_user_id`, `is_read`),
    INDEX `idx_from_user` (`from_user_id`)
);

后端实现

查询未读消息数

// 获取用户未读消息数量
function getUnreadCount($userId) {
    $sql = "SELECT COUNT(*) as count FROM private_messages 
            WHERE to_user_id = ? AND is_read = 0";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$userId]);
    return $stmt->fetch()['count'];
}

标记单条消息已读

// 标记单条消息为已读
function markAsRead($messageId, $userId) {
    $sql = "UPDATE private_messages 
            SET is_read = 1, read_at = NOW() 
            WHERE id = ? AND to_user_id = ? AND is_read = 0";
    $stmt = $pdo->prepare($sql);
    return $stmt->execute([$messageId, $userId]);
}

批量标记已读

// 标记与某用户的所有对话为已读
function markConversationRead($fromUserId, $toUserId) {
    $sql = "UPDATE private_messages 
            SET is_read = 1, read_at = NOW() 
            WHERE from_user_id = ? AND to_user_id = ? AND is_read = 0";
    $stmt = $pdo->prepare($sql);
    return $stmt->execute([$fromUserId, $toUserId]);
}

获取消息列表

// 获取对话历史,标记已读
function getConversationMessages($currentUserId, $otherUserId) {
    // 先批量标记来自对方的消息为已读
    $updateSql = "UPDATE private_messages 
                  SET is_read = 1, read_at = NOW() 
                  WHERE from_user_id = ? AND to_user_id = ? AND is_read = 0";
    $pdo->prepare($updateSql)->execute([$otherUserId, $currentUserId]);
    // 获取所有消息
    $sql = "SELECT * FROM private_messages 
            WHERE (from_user_id = ? AND to_user_id = ?) 
               OR (from_user_id = ? AND to_user_id = ?) 
            ORDER BY created_at ASC";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$currentUserId, $otherUserId, $otherUserId, $currentUserId]);
    return $stmt->fetchAll();
}

前端实现

JavaScript 标记已读

// 标记消息已读
function markMessageRead(messageId) {
    fetch('/api/mark-read.php', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ message_id: messageId })
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            // 更新UI
            document.querySelector(`.message-${messageId}`).classList.add('read');
            updateUnreadCount();
        }
    });
}
// 打开对话时标记所有消息已读
function openConversation(userId) {
    fetch('/api/mark-conversation-read.php', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ from_user_id: userId })
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            document.querySelectorAll('.unread-message').forEach(el => {
                el.classList.remove('unread-message');
            });
            updateUnreadCount();
        }
    });
}
// 更新未读消息数
function updateUnreadCount() {
    fetch('/api/unread-count.php')
    .then(response => response.json())
    .then(data => {
        document.getElementById('unread-badge').textContent = data.count;
    });
}

API 端点实现

mark-read.php

<?php
session_start();
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) {
    echo json_encode(['success' => false, 'error' => '未登录']);
    exit;
}
$input = json_decode(file_get_contents('php://input'), true);
$messageId = $input['message_id'] ?? 0;
$userId = $_SESSION['user_id'];
if ($messageId) {
    $success = markAsRead($messageId, $userId);
    echo json_encode(['success' => $success]);
} else {
    echo json_encode(['success' => false, 'error' => '参数错误']);
}
?>

前端显示优化

CSS 样式

.message-item {
    padding: 10px;
    margin: 5px 0;
    border-radius: 8px;
    transition: background-color 0.3s;
}
.message-item.unread {
    background-color: #e8f4fd;
    font-weight: bold;
    border-left: 3px solid #007bff;
}
.message-item.read {
    background-color: #fff;
    font-weight: normal;
    border-left: 3px solid transparent;
}
/* 未读标记 */
.read-status {
    font-size: 12px;
    color: #999;
}
.read-status .read-icon {
    color: #28a745;
}
.read-status .unread-icon {
    color: #007bff;
}

HTML 结构

<div class="message-item message-<?= $message['id'] ?> <?= $message['is_read'] ? 'read' : 'unread' ?>"
     onclick="markMessageRead(<?= $message['id'] ?>)">
    <div class="message-content"><?= htmlspecialchars($message['content']) ?></div>
    <div class="message-time"><?= $message['created_at'] ?></div>
    <div class="read-status">
        <?php if ($message['is_read']): ?>
            <span class="read-icon">✓ 已读</span>
        <?php else: ?>
            <span class="unread-icon">● 新消息</span>
        <?php endif; ?>
    </div>
</div>

高级功能优化

WebSocket 实时通知

// 使用 Ratchet 或 PHP WebSocket 实现实时推送
// 当消息被读取时,通知发送方
function notifyMessageRead($messageId, $senderId) {
    // 通过 WebSocket 或 SSE 推送通知
    $message = [
        'type' => 'message_read',
        'message_id' => $messageId,
        'read_at' => time()
    ];
    // 推送给消息发送者
    pushToUser($senderId, $message);
}

Redis 缓存未读计数

// 使用 Redis 缓存未读消息计数
function getUnreadCountCached($userId) {
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $cacheKey = "unread_count:{$userId}";
    $count = $redis->get($cacheKey);
    if ($count === false) {
        $count = getUnreadCount($userId);
        $redis->setex($cacheKey, 300, $count); // 缓存5分钟
    }
    return $count;
}

最佳实践建议

  1. 数据库索引:确保 to_user_idis_read 字段有联合索引
  2. 批量操作:打开对话时批量标记已读,而不是逐条标记
  3. 防刷机制:限制频繁的已读请求
  4. 用户体验:使用 CSS 动画平滑过渡已读状态
  5. 性能优化:使用缓存减少数据库查询
  6. 安全考虑:验证用户身份,防止越权标记已读

这种实现方式简单有效,适合大多数 PHP 项目的私信系统需求。

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