EP100. “每用户笔记数量上限与超限提示”
🔒 登录后可标记已读防止恶意或无意的用户疯狂创建笔记拖垮数据库/网站性能,给每个用户设置笔记数量上限(这一讲示范设为 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.php:makeNotePrivate() 加上数量限制判断,并把过滤器改成接收两个参数:
// 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.js:createNote() 的 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」(意味着这是一篇全新的文章,不是在改已存在的文章)时才真正拦截 $postarr是wp_insert_post_data过滤器额外提供的第二个参数,跟$data(即将写入数据库的字段)不同,它包含ID这类不会被写入$data但同样重要的请求上下文信息;要接收这第二个参数,add_filter()那一行必须显式声明10, 2(10是执行优先级,用默认值即可;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