WP DEVELOP

EP100. “每用户笔记数量上限与超限提示”

首页 WordPress 开发课程 MY NOTES 前端功能 · EP100
约 14 分钟· #EP100#MY NOTES 前端功能
🔒 登录后可标记已读

防止恶意或无意的用户疯狂创建笔记拖垮数据库/网站性能,给每个用户设置笔记数量上限(这一讲示范设为 5 篇)。核心还是用已经很熟悉的 wp_insert_post_data 过滤器,达到上限时直接用 PHP 的 die() 中断整个保存流程。中途踩了一个坑:一开始的判断逻辑连「删除笔记」这个动作也一起拦住了(因为删除的底层操作也是「更新文章状态」,同样会触发这个过滤器),修复方法是要能区分「这是全新的文章」还是「这是在改一篇已存在的文章」。最后给前端加了超限提示文字,并且用 REST API 自定义字段把「用户当前笔记数」也一起回传,方便前端判断要不要隐藏提示。


涉及文件

  • wp-content/themes/fictional-university-theme/functions.php (修改)
  • wp-content/themes/fictional-university-theme/page-my-notes.php (修改)
  • wp-content/themes/fictional-university-theme/src/modules/MyNotes.js (修改)

代码实现

functions.phpmakeNotePrivate() 加上数量限制判断,并把过滤器改成接收两个参数

// wp-content/themes/fictional-university-theme/functions.php

add_filter('wp_insert_post_data', 'makeNotePrivate', 10, 2); // 修改:追加 priority=10, accepted_args=2

function makeNotePrivate($data, $postarr) { // 修改:新增第二个参数 $postarr
  if ($data['post_type'] == 'note') {
    // 新增:超过 4 篇(即已有 5 篇)且是新建(没有 ID)才拦截
    if(count_user_posts(get_current_user_id(), 'note') > 4 AND !$postarr['ID']) {
      die("You have reached your note limit.");
    }

    $data['post_content'] = sanitize_textarea_field($data['post_content']);
    $data['post_title'] = sanitize_text_field($data['post_title']);
  }

  if($data['post_type'] == 'note' AND $data['post_status'] != 'trash') {
    $data['post_status'] = "private";
  }

  return $data;
}

functions.php:REST API 响应里附加当前用户笔记数(在已有的 university_custom_rest() 函数里追加一个 register_rest_field):

// wp-content/themes/fictional-university-theme/functions.php
register_rest_field('note', 'userNoteCount', array(
  'get_callback' => function() {return count_user_posts(get_current_user_id(), 'note');}
));

page-my-notes.php:新增默认隐藏的超限提示文字

<span class="note-limit-message">Note limit reached: delete an existing note to make room for a new one.</span>

src/modules/MyNotes.jscreateNote()error 回调里判断是否因超限失败,deleteNote()success 回调里判断是否已降回限额以下

// wp-content/themes/fictional-university-theme/src/modules/MyNotes.js

// createNote() 的 error 回调新增:
error: response => {
  if (response.responseText == "You have reached your note limit.") {
    $(".note-limit-message").addClass("active")
  }
  console.log("Sorry")
  console.log(response)
}

// deleteNote() 的 success 回调新增:
success: response => {
  thisNote.slideUp()
  console.log("Congrats")
  console.log(response)
  if (response.userNoteCount < 5) {
    $(".note-limit-message").removeClass("active")
  }
}

关键改动点:

  • count_user_posts($user_id, $post_type) 返回指定用户在指定文章类型下已有的文章数量,配合 > 4 判断「已经有 5 篇或更多」;具体数字(4、5)都是可以自定义调整的上限值
  • die($message) 会立刻终止整个 PHP 请求的执行,makeNotePrivate() 后面的代码(包括 return $data)都不会执行,请求也就不会真正写入数据库;传入的字符串会作为响应内容返回给前端,前端可以用这段文字判断失败原因
  • 关键 bug 修复:一开始只判断「笔记数超过 4」就 die(),结果发现删除笔记也失败了——因为 WordPress 把一篇文章挪进回收站,本质上是对这篇文章执行一次「更新」(把状态改成 trash),同样会触发 wp_insert_post_data。当用户已经在限额上时,任何更新操作(包括删除、编辑已有笔记)都会被误伤。解法是加多一个条件 AND !$postarr['ID']——只有当这个请求「没有 ID」(意味着这是一篇全新的文章,不是在改已存在的文章)时才真正拦截
  • $postarrwp_insert_post_data 过滤器额外提供的第二个参数,跟 $data(即将写入数据库的字段)不同,它包含 ID 这类不会被写入 $data 但同样重要的请求上下文信息;要接收这第二个参数,add_filter() 那一行必须显式声明 10, 210 是执行优先级,用默认值即可;2 是告诉 WordPress 这个回调函数需要接收几个参数,默认只传 1 个)
  • REST API 默认响应不会包含「用户还能建几篇笔记」这类自定义信息,用 register_rest_field() 手动加一个 userNoteCount 字段(这个技巧课程前面加 authorName 字段时就用过一次),这样每次请求成功后,前端不用额外发请求就能顺手拿到最新数量
  • 前端两处联动:createNote() 失败时检查 response.responseText 是否精确等于服务端 die() 传的那句英文,是的话就显示提示文字;deleteNote() 成功后检查回传的 userNoteCount 是否已经降到 5 以下,是的话就隐藏提示文字——这样提示会跟着用户的实际操作动态显示/消失,不需要手动刷新页面

[截图:前台笔记数量达到上限时,页面出现的 "Note limit reached" 超限提示文字]


Hook / Function 速查

名称类型用途
count_user_posts($user_id, $post_type)WP 内建 function统计指定用户在指定文章类型下的文章数量
die($message)PHP 内建 function立刻终止脚本执行,可选传入一段要输出的内容
add_filter($hook, $callback, $priority, $accepted_args)WP 内建 function第三个参数是执行优先级(数字越小越早执行),第四个参数是回调函数要接收几个参数,默认只接收 1 个
register_rest_field($post_type, $field_name, $args)WP 内建 function给 REST API 响应追加自定义字段

常见坑

  • 数量限制的判断条件只看「是否超过上限」,没有排除「这是在更新已有文章(含移入回收站)」的情况——会导致用户一旦达到上限,连编辑、删除已有笔记的正常操作都被一并拦住
  • add_filter() 忘记把最后的 accepted_args 改成 2——即使回调函数定义时写了两个参数,WordPress 默认只会传第一个参数进来,第二个参数会是 null,导致 $postarr['ID'] 判断出错
  • 前端判断错误信息时用 response.responseText 却拼错了跟服务端 die() 里完全不一致的字符串——两边文字必须逐字符匹配,大小写、标点都不能有出入,否则前端永远判断不出「这是超限导致的失败」

延伸 / 后续讲座会用到

My Notes 这个完整的 CRUD + 权限 + 安全 + 限额功能到此告一段落。下一讲开始做全新的功能:Subscriber 账号可以给 Professor 点赞/收藏(Like/Heart),并在教授页面公开显示获赞数。


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 19, EP100