PHPWeb 实现私信功能全解析:从设计到部署

私信功能是社交类网站的核心交互模块之一,它允许用户之间进行私密交流。本文将详细介绍如何使用 PHP 技术栈实现一个完整的私信系统,包括数据库设计、核心功能开发及前后端交互实现。

一、功能需求分析

一个实用的私信系统应包含以下核心功能:

  • 发送文本私信
  • 查看私信列表(显示最近联系人)
  • 查看与特定用户的聊天记录
  • 未读消息提醒与计数
  • 标记消息为已读
  • 删除单条或全部消息

二、技术选型

  • 后端:PHP 7.4+(采用 MVC 架构)
  • 数据库:MySQL 8.0
  • 前端:HTML5 + jQuery + Bootstrap 5
  • 模板引擎:Smarty(可选)
  • 通信方式:AJAX(可扩展为 WebSocket 实现实时通信)

三、数据库设计

我们需要设计两张核心表:用户表(简化版)和私信表。

1. 用户表(users)

sql

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `avatar` varchar(255) DEFAULT '/uploads/avatars/default.png' COMMENT '头像路径',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

2. 私信表(messages)"www.cq.gov.cn.gzjxhL.cn", "gov.zb.gzjxhL.cn", "gov.wap.gzjxhL.cn", "hakiki.cn",

sql

CREATE TABLE `messages` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `sender_id` int(11) NOT NULL COMMENT '发送者ID',
  `receiver_id` int(11) NOT NULL COMMENT '接收者ID',
  `content` text NOT NULL COMMENT '消息内容',
  `is_read` tinyint(1) NOT NULL DEFAULT 0 COMMENT '0-未读 1-已读',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发送时间',
  `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '0-正常 1-已删除',
  PRIMARY KEY (`id`),
  KEY `sender_receiver` (`sender_id`,`receiver_id`),
  KEY `receiver_unread` (`receiver_id`,`is_read`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='私信表';

索引设计说明:

  • sender_receiver索引用于快速查询双方聊天记录
  • receiver_unread索引用于高效统计未读消息

四、核心代码实现

1. 数据库连接类(DB.php)

php

运行"www.hakiki.cn", "m.hakiki.cn", "wap.hakiki.cn", "gov.hakiki.cn", "web.hakiki.cn",

<?php
class DB {
    private static $instance = null;
    private $conn;
    
    private function __construct() {
        $host = 'localhost';
        $dbname = 'your_database';
        $username = 'your_username';
        $password = 'your_password';
        
        try {
            $this->conn = new PDO(
                "mysql:host=$host;dbname=$dbname;charset=utf8mb4",
                $username,
                $password,
                [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
                ]
            );
        } catch(PDOException $e) {
            die("数据库连接失败: " . $e->getMessage());
        }
    }
    
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    public function getConn() {
        return $this->conn;
    }
    
    // 防止克隆
    private function __clone() {}
    // 防止反序列化
    private function __wakeup() {}
}

2. 消息模型类(Message.php)

php

运行"www.stats.gov.cn.hakiki.cn", "www.cq.gov.cn.hakiki.cn", "gov.zb.hakiki.cn",

<?php
class Message {
    private $db;
    
    public function __construct() {
        $this->db = DB::getInstance()->getConn();
    }
    
    /**
     * 发送消息
     */
    public function send($senderId, $receiverId, $content) {
        if (empty($content) || $senderId == $receiverId) {
            return false;
        }
        
        $stmt = $this->db->prepare("
            INSERT INTO messages (sender_id, receiver_id, content)
            VALUES (:sender_id, :receiver_id, :content)
        ");
        
        return $stmt->execute([
            ':sender_id' => $senderId,
            ':receiver_id' => $receiverId,
            ':content' => trim($content)
        ]);
    }
    
    /**
     * 获取与指定用户的聊天记录
     */
    public function getChatHistory($userId, $targetUserId) {
        // 先标记未读消息为已读
        $this->markAsRead($targetUserId, $userId);
        
        $stmt = $this->db->prepare("
            SELECT m.*, 
                   u1.username AS sender_name, 
                   u1.avatar AS sender_avatar,
                   u2.username AS receiver_name
            FROM messages m
            LEFT JOIN users u1 ON m.sender_id = u1.id
            LEFT JOIN users u2 ON m.receiver_id = u2.id
            WHERE (m.sender_id = :user_id AND m.receiver_id = :target_id AND m.deleted = 0)
               OR (m.sender_id = :target_id AND m.receiver_id = :user_id AND m.deleted = 0)
            ORDER BY m.created_at ASC
        ");
        
        $stmt->execute([
            ':user_id' => $userId,
            ':target_id' => $targetUserId
        ]);
        
        return $stmt->fetchAll();
    }
    
    /**
     * 获取用户的私信列表(最近联系人)
     */
    public function getMessageList($userId) {
        $stmt = $this->db->prepare("
            SELECT 
                m.*,
                IF(m.sender_id = :user_id, u2.username, u1.username) AS contact_name,
                IF(m.sender_id = :user_id, u2.avatar, u1.avatar) AS contact_avatar,
                IF(m.sender_id = :user_id, m.receiver_id, m.sender_id) AS contact_id,
                (SELECT COUNT(*) FROM messages 
                 WHERE receiver_id = :user_id 
                   AND sender_id = IF(m.sender_id = :user_id, m.receiver_id, m.sender_id)
                   AND is_read = 0
                   AND deleted = 0) AS unread_count
            FROM messages m
            LEFT JOIN users u1 ON m.sender_id = u1.id
            LEFT JOIN users u2 ON m.receiver_id = u2.id
            WHERE (m.sender_id = :user_id OR m.receiver_id = :user_id)
              AND m.deleted = 0
            GROUP BY contact_id
            ORDER BY m.created_at DESC
        ");
        
        $stmt->execute([':user_id' => $userId]);
        return $stmt->fetchAll();
    }
    
    /**
     * 标记消息为已读
     */
    public function markAsRead($senderId, $receiverId) {
        $stmt = $this->db->prepare("
            UPDATE messages 
            SET is_read = 1 
            WHERE sender_id = :sender_id 
              AND receiver_id = :receiver_id 
              AND is_read = 0
        ");
        
        return $stmt->execute([
            ':sender_id' => $senderId,
            ':receiver_id' => $receiverId
        ]);
    }
    
    /**
     * 获取未读消息总数
     */
    public function getUnreadTotal($userId) {
        $stmt = $this->db->prepare("
            SELECT COUNT(*) AS total 
            FROM messages 
            WHERE receiver_id = :user_id 
              AND is_read = 0
              AND deleted = 0
        ");
        
        $stmt->execute([':user_id' => $userId]);
        $result = $stmt->fetch();
        return $result['total'];
    }
    
    /**
     * 删除消息
     */
    public function delete($messageId, $userId) {
        $stmt = $this->db->prepare("
            UPDATE messages 
            SET deleted = 1 
            WHERE id = :id 
              AND (sender_id = :user_id OR receiver_id = :user_id)
        ");
        
        return $stmt->execute([
            ':id' => $messageId,
            ':user_id' => $userId
        ]);
    }
}

3. 控制器实现(MessageController.php)

php

运行"gov.wap.hakiki.cn", "skmnc.com.cn", "www.skmnc.com.cn", "m.skmnc.com.cn",

<?php
session_start();
require_once 'DB.php';
require_once 'Message.php';

class MessageController {
    private $messageModel;
    private $currentUser;
    
    public function __construct() {
        // 验证用户是否登录
        if (!isset($_SESSION['user_id'])) {
            header('Location: login.php');
            exit;
        }
        
        $this->currentUser = [
            'id' => $_SESSION['user_id'],
            'username' => $_SESSION['username']
        ];
        $this->messageModel = new Message();
    }
    
    /**
     * 显示私信列表页面
     */
    public function showList() {
        $messageList = $this->messageModel->getMessageList($this->currentUser['id']);
        $unreadTotal = $this->messageModel->getUnreadTotal($this->currentUser['id']);
        
        include 'views/message_list.php';
    }
    
    /**
     * 显示与指定用户的聊天页面
     */
    public function showChat($targetUserId) {
        if (empty($targetUserId) || !is_numeric($targetUserId)) {
            header('Location: message.php?action=list');
            exit;
        }
        
        $chatHistory = $this->messageModel->getChatHistory(
            $this->currentUser['id'], 
            $targetUserId
        );
        
        // 获取对方用户信息
        $db = DB::getInstance()->getConn();
        $stmt = $db->prepare("SELECT username, avatar FROM users WHERE id = :id");
        $stmt->execute([':id' => $targetUserId]);
        $targetUser = $stmt->fetch();
        
        if (!$targetUser) {
            header('Location: message.php?action=list');
            exit;
        }
        
        include 'views/chat.php';
    }
    
    /**
     * 处理发送消息请求
     */
    public function sendMessage() {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->jsonResponse(false, '无效请求方式');
        }
        
        $receiverId = isset($_POST['receiver_id']) ? (int)$_POST['receiver_id'] : 0;
        $content = isset($_POST['content']) ? $_POST['content'] : '';
        
        if (empty($receiverId) || empty($content)) {
            $this->jsonResponse(false, '参数不完整');
        }
        
        $result = $this->messageModel->send(
            $this->currentUser['id'],
            $receiverId,
            $content
        );
        
        if ($result) {
            $this->jsonResponse(true, '发送成功');
        } else {
            $this->jsonResponse(false, '发送失败');
        }
    }
    
    /**
     * 处理删除消息请求
     */
    public function deleteMessage() {
        $messageId = isset($_POST['message_id']) ? (int)$_POST['message_id'] : 0;
        
        if (empty($messageId)) {
            $this->jsonResponse(false, '参数不完整');
        }
        
        $result = $this->messageModel->delete($messageId, $this->currentUser['id']);
        
        if ($result) {
            $this->jsonResponse(true, '删除成功');
        } else {
            $this->jsonResponse(false, '删除失败');
        }
    }
    
    /**
     * JSON响应辅助方法
     */
    private function jsonResponse($success, $message, $data = []) {
        header('Content-Type: application/json');
        echo json_encode([
            'success' => $success,
            'message' => $message,
            'data' => $data
        ]);
        exit;
    }
}

// 路由处理
$action = isset($_GET['action']) ? $_GET['action'] : 'list';
$controller = new MessageController();

switch ($action) {
    case 'list':
        $controller->showList();
        break;
    case 'chat':
        $targetUserId = isset($_GET['user_id']) ? (int)$_GET['user_id'] : 0;
        $controller->showChat($targetUserId);
        break;
    case 'send':
        $controller->sendMessage();
        break;
    case 'delete':
        $controller->deleteMessage();
        break;
    default:
        $controller->showList();
        break;
}

4. 前端页面实现

私信列表页面(views/message_list.php)

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>我的私信</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        .contact-item {
            padding: 15px;
            border-bottom: 1px solid #eee;
            transition: background-color 0.3s;
        }
        .contact-item:hover {
            background-color: #f8f9fa;
        }
        .unread-badge {
            background-color: #dc3545;
            padding: 3px 8px;
            border-radius: 50%;
            font-size: 0.8rem;
        }
    </style>
</head>
<body>
    <div class="container mt-4">
        <h3>我的私信 <span class="badge bg-danger"><?= $unreadTotal ?></span></h3>
        
        <div class="list-group mt-3">
            <?php if (empty($messageList)): ?>
                <div class="alert alert-info">暂无私信记录</div>
            <?php else: ?>
                <?php foreach ($messageList as $item): ?>
                    <a href="message.php?action=chat&user_id=<?= $item['contact_id'] ?>" class="text-decoration-none text-dark">
                        <div class="contact-item d-flex justify-content-between align-items-center">
                            <div class="d-flex align-items-center">
                                <img src="<?= $item['contact_avatar'] ?>" alt="头像" width="50" height="50" class="rounded-circle me-3">
                                <div>
                                    <div class="fw-bold"><?= $item['contact_name'] ?></div>
                                    <div class="text-muted small">
                                        <?= $item['sender_id'] == $this->currentUser['id'] ? '我: ' : '' ?><?= mb_substr($item['content'], 0, 20) ?>
                                    </div>
                                </div>
                            </div>
                            <div class="text-end">
                                <small class="text-muted"><?= date('m-d H:i', strtotime($item['created_at'])) ?></small>
                                <?php if ($item['unread_count'] > 0): ?>
                                    <div class="unread-badge text-white mt-1"><?= $item['unread_count'] ?></div>
                                <?php endif; ?>
                            </div>
                        </div>
                    </a>
                <?php endforeach; ?>
            <?php endif; ?>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
</body>
</html>

聊天页面(views/chat.php)

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>与 <?= $targetUser['username'] ?> 聊天</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        .chat-container {
            height: 500px;
            overflow-y: auto;
            padding: 15px;
            border: 1px solid #eee;
            border-radius: 5px;
            margin-bottom: 15px;
        }
        .message {
            margin-bottom: 15px;
            max-width: 70%;
        }
        .sent {
            margin-left: auto;
        }
        .sent .message-content {
            background-color: #0d6efd;
            color: white;
        }
        .received .message-content {
            background-color: #e9ecef;
        }
        .message-content {
            padding: 10px 15px;
            border-radius: 18px;
        }
        .message-time {
            font-size: 0.7rem;
            color: #6c757d;
            margin-top: 5px;
        }
        .sent .message-time {
            text-align: right;
        }
    </style>
</head>
<body>
    <div class="container mt-4">
        <a href="message.php?action=list" class="btn btn-secondary mb-3">返回私信列表</a>
        
        <div class="card">
            <div class="card-header d-flex align-items-center">
                <img src="<?= $targetUser['avatar'] ?>" alt="头像" width="40" height="40" class="rounded-circle me-2">
                <h5 class="mb-0"><?= $targetUser['username'] ?></h5>
            </div>
            
            <div class="card-body">
                <div class="chat-container" id="chatContainer">
                    <?php foreach ($chatHistory as $msg): ?>
                        <div class="message <?= $msg['sender_id'] == $this->currentUser['id'] ? 'sent' : 'received' ?>">
                            <div class="message-content"><?= htmlspecialchars($msg['content']) ?></div>
                            <div class="message-time"><?= date('m-d H:i', strtotime($msg['created_at'])) ?></div>
                        </div>
                    <?php endforeach; ?>
                </div>
                
                <div class="input-group">
                    <textarea id="messageInput" class="form-control" rows="3" placeholder="输入消息内容..."></textarea>
                    <button id="sendBtn" class="btn btn-primary">发送</button>
                </div>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
    <script>
        // 页面加载完成后滚动到底部
        $(document).ready(function() {
            scrollToBottom();
            
            // 发送消息
            $('#sendBtn').click(sendMessage);
            
            // 按Enter发送消息,Shift+Enter换行
            $('#messageInput').keydown(function(e) {
                if (e.key === 'Enter' && !e.shiftKey) {
                    e.preventDefault();
                    sendMessage();
                }
            });
            
            // 定时检查新消息(实际项目可替换为WebSocket)
            setInterval(checkNewMessages, 5000);
        });
        
        // 发送消息函数
        function sendMessage() {
            const content = $('#messageInput').val().trim();
            if (!content) return;
            
            $.post('message.php?action=send', {
                receiver_id: <?= $targetUserId ?>,
                content: content
            }, function(response) {
                if (response.success) {
                    $('#messageInput').val('');
                    // 重新加载聊天记录
                    location.reload();
                } else {
                    alert(response.message);
                }
            }, 'json');
        }
        
        // 滚动到底部
        function scrollToBottom() {
            const container = $('#chatContainer');
            container.scrollTop(container[0].scrollHeight);
        }
        
        // 检查新消息
        function checkNewMessages() {
            // 实际实现中可以记录最后一条消息ID,只请求新消息
            $.get('message.php?action=check_new', {
                target_user_id: <?= $targetUserId ?>
            }, function(response) {
                if (response.success && response.data.has_new) {
                    // 有新消息,刷新页面或局部更新
                    location.reload();
                }
            }, 'json');
        }
    </script>
</body>
</html>

五、功能扩展建议

  1. 实时通信:当前实现使用 AJAX 轮询检查新消息,可替换为 WebSocket(如 Ratchet 库)实现真正的实时推送
  2. 消息类型扩展:支持发送图片、文件添加表情选择器支持消息引用功能
  3. 消息撤回:添加is_withdrawn字段,允许撤回一定时间内的消息
  4. 消息搜索:实现基于内容的消息搜索功能,可结合 MySQL 全文索引或 Elasticsearch
  5. 性能优化:实现消息分页加载添加 Redis 缓存热门聊天记录对消息表进行分表处理(按时间或用户 ID 范围)
  6. 用户体验提升:消息发送状态显示(发送中、已送达、已读)输入状态提示(对方正在输入...)聊天记录导出功能

六、安全性考虑

  1. 输入验证:对所有用户输入进行严格验证,防止 XSS 攻击
  2. 权限检查:确保用户只能操作自己有权限的消息
  3. SQL 注入防护:使用 PDO 预处理语句,避免直接拼接 SQL
  4. 敏感内容过滤:对消息内容进行敏感词过滤
  5. CSRF 防护:添加 CSRF 令牌验证重要操作

七、总结

本文详细介绍了使用 PHP 实现私信功能的完整流程,从数据库设计到前后端代码实现。该方案采用 MVC 架构,代码结构清晰,易于维护和扩展。

实现私信功能不仅能提升网站的用户互动性,也是学习 PHP 数据库操作、AJAX 交互和用户会话管理的绝佳实践。在实际项目中,可根据需求复杂度对功能进行裁剪或扩展,特别是在处理高并发场景时,需要重点考虑性能优化策略。

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务