PHP项目如何实现@提及功能?

wen java案例 2

本文目录导读:

PHP项目如何实现@提及功能?

  1. 核心流程梳理
  2. 前端实现(关键交互)
  3. 后端 API 接口 (搜索用户)
  4. 数据存储设计 (存储提及)
  5. 展示渲染 (显示提及)
  6. 完整代码示例 (极简版)
  7. 进阶优化

在 PHP 项目中实现“@提及”功能,通常涉及几个关键环节:触发检测用户列表查询与展示数据存储与解析

下面详细拆解实现步骤,从简单的前端模拟到完整的后端存储。

核心流程梳理

  1. 输入阶段:用户输入 字符。
  2. 搜索阶段:前端弹出下拉框,显示匹配的用户列表,后端接收搜索请求。
  3. 选择阶段:用户点击某个用户,前端将该用户信息(如 ID)嵌入到文本中。
  4. 提交与存储:将包含特殊标记的文本存入数据库。
  5. 展示阶段:从数据库读取文本时,将 @用户ID 替换为可点击的链接或高亮文字。

前端实现(关键交互)

前端负责监听输入、弹出列表、高亮选择,可以使用原生 JS 或现成的库(如 At.jsjquery-textcompleteTribute.js 等)来节省时间。

简单原生实现思路 (无库):

// 在 textarea 或 contenteditable div 上监听
const input = document.getElementById('comment-input');
const mentionList = document.getElementById('mention-list');
input.addEventListener('input', function(e) {
  const cursorPos = this.selectionStart;
  const text = this.value;
  // 定位当前输入位置前的最后一个 @ 符号
  const lastAtIndex = text.lastIndexOf('@', cursorPos - 1);
  // 检查 @ 符号前是否为空或空格 (避免匹配到 email 如 aa@bb.com)
  if (lastAtIndex !== -1 && (lastAtIndex === 0 || text[lastAtIndex - 1] === ' ')) {
    const query = text.substring(lastAtIndex + 1, cursorPos);
    // 防止查询空白
    if (query !== '' && !query.includes(' ')) {
      // 异步查询后端 API
      fetchMentions(query).then(users => {
        // 渲染下拉列表
        renderMentionList(users, lastAtIndex, cursorPos);
      });
    }
  } else {
    mentionList.style.display = 'none'; // 隐藏下拉框
  }
});
function fetchMentions(query) {
  return fetch(`/api/users/search?q=${encodeURIComponent(query)}`)
    .then(res => res.json());
}
function selectUser(user) {
  // 1. 替换输入框中的 @query 为 @user:displayName (或自定义格式)
  const beforeAt = input.value.substring(0, lastAtIndex);
  const afterAt = input.value.substring(cursorPos);
  input.value = beforeAt + `@[${user.id}:${user.name}] ` + afterAt;
  // 2. 隐藏下拉列表
  mentionList.style.display = 'none';
}

重要:在 contenteditable 中实现更复杂,建议使用 Tribute.js 这类成熟库。


后端 API 接口 (搜索用户)

提供一个搜索接口,实时返回匹配用户。

// /api/users/search.php
header('Content-Type: application/json');
$query = $_GET['q'] ?? '';
$query = trim($query);
if (mb_strlen($query) < 1) {
    echo json_encode([]);
    exit;
}
// 假设从数据库查询
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $db->prepare("SELECT id, username, avatar_url FROM users WHERE username LIKE :q LIMIT 10");
$stmt->execute([':q' => $query . '%']);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($users);

优化建议

  • 权限过滤:只返回当前用户可提及的用户(如好友、群成员)。
  • 缓存:使用 Redis 缓存热门用户搜索,减少数据库压力。
  • 防 SQL 注入:使用参数绑定,如上面的 q

数据存储设计 (存储提及)

存储时不能直接存 @张三,因为用户名可能会变,最好存储用户 ID

推荐格式

  1. 带标记的文本这篇文档需要 @[user:123] 来处理。
  2. JSON 结构化
    {
      "text": "这篇文档需要 @user 来处理。",
      "mentions": [{"user_id": 123, "offset": 8, "length": 5}]
    }

    但开发复杂度较高,大部分项目使用格式1。

数据库存储示例

-- 存储文本内容
INSERT INTO comments (post_id, content, mentions_count) VALUES 
(:post_id, :content, :mentions_count);
-- 同时存储提及关系(用于通知查询)
INSERT INTO mention_notifications (comment_id, mentioned_user_id, post_id) 
VALUES (
  :comment_id,
  :mentioned_user_id,
  :post_id
);

PHP 解析与提取提及的用户 ID

// 假设前端提交的文本格式为: "Hello @[user:123:John] how are you?"
$content = $_POST['content'];
$pattern = '/@\[user:(\d+):([^\]]+)\]/';
preg_match_all($pattern, $content, $matches);
// $matches[1] 是所有被提及的用户 ID 数组
$mentionedUserIds = array_unique($matches[1]);
// 这里可以触发发送通知的逻辑
foreach ($mentionedUserIds as $userId) {
    // sendNotification($userId, $commentId);
}

展示渲染 (显示提及)

从数据库读取文本后,将 @[user:ID:Name] 替换为带链接的 HTML。

function renderContent($content) {
    // 替换 @[user:123:张三] 为 <a href="/user/123" class="mention">@张三</a>
    $pattern = '/@\[user:(\d+):([^\]]+)\]/';
    $replacement = '<a href="/user/$1" class="mention" data-user-id="$1">@$2</a>';
    return preg_replace($pattern, $replacement, htmlspecialchars($content));
}
// 使用
echo renderContent($row['content']);

安全提醒htmlspecialchars 必不可少,防止 XSS 攻击。


完整代码示例 (极简版)

前端 (HTML + JS):监听 输入,展示下拉框,替换文本。 后端搜索 (search.php):模糊查询并返回 JSON。 提交处理 (submit.php)

$rawContent = $_POST['content']; // "你好 @[user:5:Lisi] 辛苦了"
// 1. 提取被 @ 的用户 ID
preg_match_all('/@\[user:(\d+):[^\]]+\]/', $rawContent, $matches);
$mentionedIds = array_unique($matches[1]);
// 2. 保存内容到数据库
$stmt = $db->prepare("INSERT INTO comments (content, user_id, created_at) VALUES (?, ?, NOW())");
$stmt->execute([$rawContent, $currentUserId]);
$commentId = $db->lastInsertId();
// 3. 生成通知记录
foreach ($mentionedIds as $toUserId) {
    $stmt = $db->prepare("INSERT INTO notifications (type, from_user_id, to_user_id, comment_id) VALUES ('mention', ?, ?, ?)");
    $stmt->execute([$currentUserId, $toUserId, $commentId]);
}

展示 (view.php)

$comment = $db->query("SELECT content FROM comments WHERE id = 1")->fetch();
$displayContent = preg_replace(
    '/@\[user:(\d+):([^\]]+)\]/',
    '<a href="/profile/$1" class="mention">@$2</a>',
    htmlspecialchars($comment['content'])
);
echo $displayContent;

进阶优化

  1. 实时性:使用 WebSocket 推送 通知。
  2. 富文本编辑器:在 contenteditable 中实现,建议使用 Froala EditorQuill 等支持 Mention 插件的编辑器。
  3. 权限控制:用户只能提及当前模块(如该群聊)中的成员。
  4. 历史记录:存储 mentions 表,方便统计和搜索被 @ 过的内容。

  • 前端:监听输入 -> 请求搜索 -> 替换文本。
  • 后端:搜索接口 (LIKE) -> 存储格式 (@[user:ID:Name]) -> 解析存入通知表 -> 展示时渲染链接。
  • 关键: 使用 用户 ID 而非用户名来标记“@提及”,防止改名后失效。

如果需要进一步了解某个具体环节(例如如何实现 contenteditable 中插入不可编辑的 mention 标签),请随时提出。

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