WP DEVELOP

EP107. “登录校验、防重复点赞与目标类型校验”

首页 WordPress 开发课程 LIKE / HEART 功能 · EP107
约 13 分钟· #EP107#LIKE / HEART 功能
🔒 登录后可标记已读

createLike() 端点加上三层自定义限制:① 必须登录才能点赞;② 同一用户对同一教授只能点一次;③ 传来的 ID 必须真的是一个 Professor 类型的文章,不能是随便编的数字或别的文章类型的 ID。过程中也顺手修复了一个真实 bug:未登录访客查看教授页面时,心形图标却显示成「已点赞」的实心状态——原因是 get_current_user_id() 在未登录时返回 0,而 WP_Query'author' => 0 会被当成「不限定作者」处理,导致查询条件形同虚设。


涉及文件

  • wp-content/themes/fictional-university-theme/inc/like-route.php (修改)
  • wp-content/themes/fictional-university-theme/single-professor.php (修改)
  • wp-content/themes/fictional-university-theme/src/modules/Like.js (修改)

代码实现

inc/like-route.phpcreateLike() 加上登录校验、防重复、目标类型校验三层限制

// wp-content/themes/fictional-university-theme/inc/like-route.php

function createLike($data) {
  if (is_user_logged_in()) {
    $professor = sanitize_text_field($data['professorId']);

    $existQuery = new WP_Query(array(
      'author' => get_current_user_id(),
      'post_type' => 'like',
      'meta_query' => array(
        array(
          'key' => 'liked_professor_id',
          'compare' => '=',
          'value' => $professor
        )
      )
    ));

    if ($existQuery->found_posts == 0 AND get_post_type($professor) == 'professor') {
      return wp_insert_post(array(
        'post_type' => 'like',
        'post_status' => 'publish',
        'post_title' => '2nd PHP Test',
        'meta_input' => array(
          'liked_professor_id' => $professor
        )
      ));
    } else {
      die("Invalid professor id");
    }
  } else {
    die("Only logged in users can create a like.");
  }
}

src/modules/Like.jscreateLike() 请求补上 nonce 身份验证(直接照搬 My Notes 模块用过的写法):

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

createLike(currentLikeBox) {
  $.ajax({
    beforeSend: xhr => {
      xhr.setRequestHeader("X-WP-Nonce", universityData.nonce)
    },
    url: universityData.root_url + "/wp-json/university/v1/manageLike",
    type: "POST",
    data: { "professorId": currentLikeBox.data("professor") },
    success: response => {
      console.log(response)
    },
    error: response => {
      console.log(response)
    }
  })
}

single-professor.php:修复未登录用户被误判为「已点赞」的 bug(把 $existQuery 整段包进 is_user_logged_in() 判断里):

$existStatus = 'no';

if (is_user_logged_in()) {
  $existQuery = new WP_Query(array(
    'author' => get_current_user_id(),
    'post_type' => 'like',
    'meta_query' => array(
      array(
        'key' => 'liked_professor_id',
        'compare' => '=',
        'value' => get_the_ID()
      )
    )
  ));

  if ($existQuery->found_posts) {
    $existStatus = 'yes';
  }
}

关键改动点:

  • is_user_logged_in() 在 REST API 自定义端点的回调函数里,必须配合前端传来的 nonce(X-WP-Nonce 请求头)才能正确判断为 true——即使浏览器里其实已经登录,只要请求没带 nonce,WordPress 出于安全考虑一律当作「未登录」处理,这也是这一讲一开始为什么明明登录了却依然被拒绝的原因
  • 「查重」和「新建」用的是同一套 meta_query 逻辑(跟教授详情页判断「当前用户是否已点赞」的查询几乎一模一样),只是这里的 value 用前端传来的 $professor 变量而不是 get_the_ID()——因为端点函数运行时不在任何「当前文章循环」的上下文里,没有 get_the_ID() 可用
  • if ($existQuery->found_posts == 0 AND get_post_type($professor) == 'professor'):两个条件都满足才真正创建——get_post_type($id) 可以传入任意 ID 查询它的文章类型,如果这个 ID 不存在或者对应的不是 professor 类型,条件就不成立,用来防止有人传入博客文章、Campus 甚至完全瞎编的 ID
  • Bug 修复的核心原因:get_current_user_id() 在没有登录时返回整数 0;但 WP_Query 参数里 'author' => 0 在 WordPress 的解析逻辑里等同于「没有传这个参数」,即「不限定作者,查全部」——所以未登录时这个查询依然会查到「别人」对这位教授的点赞记录,进而误判成「当前(其实并不存在的)用户已经点过赞」。解法是干脆不要在未登录时运行这个查询,直接把整段逻辑包进 is_user_logged_in() 判断里,未登录时 $existStatus 保持默认值 'no'

[截图:未登录状态下访问教授详情页,心形图标正确显示为空心(未点赞),不再误判成已点赞]


Hook / Function 速查

名称类型用途
is_user_logged_in()WP 内建 function判断当前请求是否来自已登录用户;在 REST 端点里必须配合正确的 nonce 才会返回 true
get_post_type($post_id)WP 内建 function查询指定 ID 的文章类型,可用来校验一个 ID 是否属于预期的类型

常见坑

  • WP_Query'author' => get_current_user_id() 时没考虑到未登录用户这个值是 0——author => 0 会被当成不限定作者,导致查询条件失效,产生「未登录却被判定为已点赞」这类反直觉的 bug
  • 只校验「是否已点过赞」却不校验「目标 ID 是不是真的是教授」——攻击者可以随便编一个不存在或指向别的文章类型的 ID 来绕过点赞记录的限制,两个校验条件缺一不可
  • 忘记在前端请求里补上 nonce——即使服务端逻辑写得再对,没有 nonce 的话 is_user_logged_in() 永远判定为 false,功能表现得就像完全没登录一样

延伸 / 后续讲座会用到

下一讲(part 3,本课的最后一部分)会让点赞成功后前端心形和数字实时更新(不用手动刷新),并且实现 deleteLike() 的真正业务逻辑。


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 20, EP107