在 WordPress 中颁发 Open Badges(无需笨重的插件)

只需在任何 WordPress 课程/LMS 插件(LearnDash、LifterLMS、Tutor)完成时调用 Badges Ninja API,即可颁发 Open Badges——无需安装单独的徽章插件。

Nacho Coll 作者 更新于 20 分钟阅读
只需在任何 WordPress 课程/LMS 插件(LearnDash、LifterLMS、Tutor)完成时调用 Badges Ninja API,即可颁发 Open Badges——无需安装单独的徽章插件。

如果你在 WordPress 上运营课程,你一定很熟悉这套流程:有人在 LearnDash、LifterLMS 或 Tutor LMS 中完成了一个模块,LMS 把它标记为”已完成”,然后……没有任何可验证的事情发生。也许你会发一封带 PDF 的邮件。也许你会安装一个专门的 WordPress 徽章插件,它会新增自己的数据库表、自己的图片库,还有它自己对”徽章”是什么的理解——一种你网站之外没人能验证的理解。

其实有一条更简单的路。WordPress 课程插件在用户完成课程的那一刻就已经会触发一个 action hook。你可以捕获这个 hook,然后对 Badges Ninja API 发起一次 HTTP 调用。不需要额外的插件,不需要额外的托管,不需要私有的徽章格式——只有一份符合标准的 Open Badge v2.0 凭证,收件人可以在任何地方验证它、添加到 LinkedIn、并下载为 PDF。

这篇文章先讲一遍这个通用模式,然后展示 LearnDash、LifterLMS 和 Tutor LMS 各自具体的 hook。

为什么不直接用 WordPress 徽章插件?

专门的 WordPress 徽章插件解决的问题,比它们看起来要解决的问题要窄得多。它们擅长在你网站内的”我的成就”页面上渲染一张图片。但在真正决定一份凭证价值的事情上,它们通常要弱得多:

  • 可携带性。 一个只以短代码形式存在于你 WordPress 网站上的徽章不是凭证——它只是装饰。一旦收件人换了工作、换了邮箱,或者你的网站下线了,这份”证明”也就随之消失了。
  • 可验证性。 任何人都应该能够在不登录你 WordPress 管理后台的情况下核实一份凭证是否真实。Open Badge v2.0 从设计上就为你提供了一个公开的验证 URL 和机器可读的 JSON-LD。
  • 维护面。 每一个 WordPress 插件都意味着多一样需要打补丁的东西、多一张需要备份的数据库表、多一个在核心更新时可能出问题的环节。

在课程完成时调用外部 API 就绕开了这三个问题。WordPress 继续负责它擅长的事——内容分发和进度追踪——而凭证本身则活在一个专门为颁发、验证和分享它而构建的系统里。

这套模式:一个 hook,一次 HTTP 调用

每一个主流的 WordPress LMS 插件都会在用户完成课程时触发一个动作。集成方式始终是同样的三步:

  1. 挂载到你所用 LMS 对应的完成动作上。
  2. 查找(或创建)收件人的身份——通常就是他们 WordPress 账号的邮箱和显示名称。
  3. 用 wp_remote_post() 调用 Badges Ninja API 的 POST /awards,传入徽章 ID 和收件人信息。

你首先需要一个 API 密钥。在你的控制台的 API Keys 下生成一个——完整流程参见 API 密钥指南。密钥只在创建时显示一次,所以要把它作为常量存放在 wp-config.php 中,而不是存进数据库:

define( 'BADGES_NINJA_API_KEY', 'bws_your_32_hex_key_here' );
define( 'BADGES_NINJA_BADGE_ID', 'your-badge-id' );

Newly created key revealed once

把这个密钥留在服务器端。 本指南中的所有内容都运行在你 WordPress 服务器上的 PHP 中——wp_remote_post() 是一次服务器端调用,而 wp-config.php 从不会被发送到浏览器。这正是你想要的:密钥通过 HTTPS 在服务器与服务器之间传输,任何访问者都无法读取到它。切勿把这次调用搬到客户端 JavaScript 中——从浏览器发起的、把密钥内联进去的 fetch() 调用,会让任何人通过浏览器的开发者工具看到它。有两个值得保持的习惯:确保你的主机把 wp-config.php 作为已执行的 PHP 来提供服务(绝不能作为纯文本,也不要在网站根目录留下 wp-config.php.bak 的副本),并为这次集成创建一个专用的 API 密钥,这样一旦它被泄露,你可以单独撤销它——而不影响你的其他工具。

一个共享的辅助函数能让每个 LMS 的 hook 都保持简单:

function badges_ninja_issue_award( $recipient_email, $recipient_name, $badge_id ) {
	$response = wp_remote_post( 'https://api.badges.ninja/awards', array(
		'headers' => array(
			'X-Api-Key'    => BADGES_NINJA_API_KEY,
			'Content-Type' => 'application/json',
		),
		'body' => wp_json_encode( array(
			'badgeId'   => $badge_id,
			'recipient' => array(
				'name'  => $recipient_name,  // must be at least 5 characters
				'email' => $recipient_email,
			),
			'issuedOn'  => round( microtime( true ) * 1000 ), // epoch milliseconds
		) ),
		'timeout' => 15,
	) );
	if ( is_wp_error( $response ) ) {
		error_log( 'Badges Ninja award failed: ' . $response->get_error_message() );
		return false;
	}
	$code = wp_remote_retrieve_response_code( $response );
	if ( $code >= 400 ) {
		error_log( 'Badges Ninja award failed with HTTP ' . $code . ': ' . wp_remote_retrieve_body( $response ) );
		return false;
	}
	return json_decode( wp_remote_retrieve_body( $response ), true );
}

收件人的邮箱一到达 Badges Ninja,就会在服务器端进行一次 SHA-256 哈希处理,返回的数据里包含这次颁发的验证 URL——如果你想在调用成功后,直接在 WordPress 里展示一个”查看你的凭证”链接,这会很方便。

LearnDash

LearnDash 会触发 learndash_course_completed,附带一个包含 user 和 course 的数组参数:

add_action( 'learndash_course_completed', function( $data ) {
	$user   = $data['user'];
	$course = $data['course'];
	// Map LearnDash course ID → your Badges Ninja badge ID.
	// A simple approach: store the badge ID in a course custom field.
	$badge_id = get_post_meta( $course->ID, 'badges_ninja_badge_id', true );
	if ( ! $badge_id ) {
		return; // no badge configured for this course
	}
	badges_ninja_issue_award( $user->user_email, $user->display_name, $badge_id );
} );

一旦你有不止一门课程,把徽章 ID 存为课程 meta(而不是一个单一的全局常量 BADGES_NINJA_BADGE_ID)就是正确的做法——每门课程都对应自己的徽章设计。

LifterLMS

LifterLMS 会触发 lifterlms_course_completed,附带 $user_id 和 $course_id:

add_action( 'lifterlms_course_completed', function( $user_id, $course_id ) {
	$user     = get_userdata( $user_id );
	$badge_id = get_post_meta( $course_id, 'badges_ninja_badge_id', true );
	if ( ! $user || ! $badge_id ) {
		return;
	}
	badges_ninja_issue_award( $user->user_email, $user->display_name, $badge_id );
}, 10, 2 );

Tutor LMS

Tutor LMS 会触发 tutor_course_complete_after,附带课程 ID,你需要从会话中取出当前用户:

add_action( 'tutor_course_complete_after', function( $course_id ) {
	$user_id  = get_current_user_id();
	$user     = get_userdata( $user_id );
	$badge_id = get_post_meta( $course_id, 'badges_ninja_badge_id', true );
	if ( ! $user || ! $badge_id ) {
		return;
	}
	badges_ninja_issue_award( $user->user_email, $user->display_name, $badge_id );
} );

如果你的 Tutor LMS 配置是从后台进程(例如通过测验自动评分)触发完成的,请把 get_current_user_id() 换成你所用具体 hook 提供的用户 ID——在配置期间用一次 error_log( print_r( func_get_args(), true ) ) 检查一下负载,以确认参数。

在接入 hook 之前先设计好徽章

在任何这些动作触发之前,你需要一个可以颁发的徽章。可视化设计器 让你无需专职设计师就能搞定这件事——80 多种形状模板、自定义颜色、图标库和 Logo 上传,全部自动吸附网格。如果你是第一次使用这个设计器,Designing Your First Verifiable Digital Certificate 会带你完成选择模板、打造收件人真正愿意分享的成品的整个过程。

徽章创建好之后,从控制台(或通过 GET /badges)取得它的 ID,填入你课程的自定义字段。

收件人实际能得到什么

这正是一个自制的 WordPress 徽章插件无法复制的部分。当 POST /awards 成功后,收件人会得到:

  • 一个任何人都可以打开的独一无二的验证 URL——无需 WordPress 登录——展示这份凭证是真实的、由谁颁发、以及何时颁发。
  • 一个自动生成的二维码和 A4 PDF 证书。
  • 一个添加到 LinkedIn 个人资料的按钮(如果你的颁发者设置了 LinkedIn 组织 ID)——一次性设置参见 Add a LinkedIn “Add to Profile” Button。
  • 通过魔法链接登录,访问他们自己在 badges.ninja/me 的收件人门户——不用记密码,也不用在你的 WordPress 网站上再管理一个独立账号。
  • 符合标准的 Open Badge v2.0 JSON-LD,因此任何支持 OB 的验证方(不仅是 Badges Ninja)都能对其进行校验。

这些都不是 WordPress 该负责构建的事,也都不该只存在于你的 WordPress 数据库里。

优雅地处理失败

课程完成的 hook 是在一次实时页面请求中触发的,一次缓慢或失败的 API 调用不应该阻塞学习者的”恭喜”页面。一旦你从概念验证阶段往前推进,有两个实用的调整:

不要让一次失败的颁发阻塞界面。 上面的辅助函数已经是记录日志并返回 false,而不是抛出异常——无论 badges_ninja_issue_award() 返回什么,LearnDash、LifterLMS 和 Tutor 都会继续它们正常的完成流程。

规模化时用队列而不是内联调用。 如果你要一次性让大批学员通过同一门课程(比如某个学员群组的开班日,或是网络研讨会回放的解锁),就从 hook 里触发一个 WP-Cron 定时事件,而不是同步调用 API:

add_action( 'learndash_course_completed', function( $data ) {
	wp_schedule_single_event( time() + 30, 'badges_ninja_deferred_award', array(
		$data['user']->user_email,
		$data['user']->display_name,
		get_post_meta( $data['course']->ID, 'badges_ninja_badge_id', true ),
	) );
} );
add_action( 'badges_ninja_deferred_award', 'badges_ninja_issue_award', 10, 3 );

这样可以分散负载,避免单个慢请求变成学习者的问题。对于真正的大批量场景(整个历史学员群组一次性迁移),完全跳过 WordPress hook 这条路,改用通过 CSV 批量颁发——只需上传一次,平台会在服务器端对颁发过程分页处理,并内置暂停/恢复功能。

关于幂等性的说明

如果你的 LMS 有时会针对同一个用户不止一次触发完成 hook(在测验重考时,LearnDash 常见这类边界情况,或者 Tutor LMS 的重新同步),你肯定不希望同一枚徽章被颁发两次。最简单的防护方式,是在一次成功调用之后立即设置一个 WordPress 用户 meta 标记:

if ( get_user_meta( $user_id, 'badges_ninja_awarded_' . $course_id, true ) ) {
	return; // already issued
}
$result = badges_ninja_issue_award( $user->user_email, $user->display_name, $badge_id );
if ( $result ) {
	update_user_meta( $user_id, 'badges_ninja_awarded_' . $course_id, true );
}

如果你在真正大的量级上颁发,并希望集中处理而不是按站点各自处理,可以看看由 webhook 驱动的事件处理,或者完整的 API 快速入门,里面有所有端点的请求/响应格式。

当这套模式超出单一课程范围时

上面的例子都假定一门课程对应一枚徽章。一旦你要处理可叠加的凭证——比如一枚在多枚课程徽章之后解锁的路径徽章,或者依赖多个前置条件的毕业设计徽章——就该把”哪枚徽章对应哪次完成”的映射关系,从 post meta 移到一张小型查找表里,或者由一个基于电子表格(Airtable 或 Google Sheets)的自动化流程来驱动,由它触发同样的 POST /awards 调用。API 调用本身不会变;变的只是由什么来决定何时发起它。

排查集成问题

以下这些问题几乎占了人们第一次接入时遇到的大部分麻烦:

Hook 一直没有触发。 确认你挂载的是与插件实际版本相符的正确动作——LearnDash 尤其在多个主要版本之间改过完成 hook 的名称(较旧的安装有时仍依赖 learndash_course_completed 从与新版本不同的代码路径触发)。在你的回调函数顶部临时加一个 error_log(),然后在以一个可丢弃的测试账号完成一门测试课程时,观察 wp-content/debug.log。

wp_remote_post() 超时。 共享主机环境有时会阻止对陌生主机的出站 HTTPS,或者把请求超时限制在上面设置的 15 秒以下。如果调用持续超时,请向你的主机确认是否允许对 api.badges.ninja 的出站连接,并确认你的 PHP curl 扩展拥有最新的 CA 证书——过期的证书包是较老的共享主机技术栈上一种常见的静默故障。

400 Bad Request。 负载缺少必填字段,或者结构不对。POST /awards 期望 badgeId、一个包含 name 和 email 的嵌套 recipient 对象,以及一个 issuedOn 时间戳——上面的辅助函数发送的正是这些。最常见的坑在收件人的姓名上:它必须至少 5 个字符,所以像”Jo”这样的 WordPress 显示名称会被拒绝。当显示名称太短时,回退使用账号的全名(或邮箱本地部分)。

401 Unauthorized。 API 密钥常量没有被加载,或者常量设置之后密钥被重新生成过。密钥只在创建时显示一次,之后无法再次获取——如果你已经搞不清哪个密钥仍然有效,就删掉旧的、颁发一个新的,而不要去猜测。

徽章已颁发,但设计不对。 这几乎总是因为测试时留下的 badges_ninja_badge_id post-meta 值已经过时。因为这个映射关系存在课程 post 上,复制课程用于新的学员群组之后,很容易忘记你曾经改过它——每次克隆课程时值得快速检查一下。

同一个人收到重复的颁发。 上面已经用 user-meta 防护处理过,但值得再强调一次:LMS 的完成 hook 并不保证只触发一次。把”已经颁发过”当作需要检查的正常情况,而不是可以忽略的边界情况。

常见问题

这能和 WooCommerce 课程套餐一起用吗? 可以——如果一次套餐购买应该独立于课程完成、直接触发一枚徽章(例如,任何购买了某个特定套餐的人都能获得一枚”认证会员”徽章),就挂载 woocommerce_order_status_completed,而不是某个 LMS 的完成动作,并从产品 meta 而不是课程中获取徽章 ID。

收件人可以在没有 WordPress 账号的情况下获得徽章吗? 可以,而这正是重点所在。这次颁发是绑定到他们在 Badges Ninja 里的邮箱地址,而不是绑定到某条 WordPress 用户记录。如果你的网站允许访客结账或匿名完成测验,只需确保你捕获了一个真实的邮箱地址,并把它传入 badges_ninja_issue_award()。

如果我想在颁发之前先审核一遍怎么办? 不要直接从完成 hook 触发调用。而是把待处理的颁发信息(用户、课程、时间戳)写入一张自定义表或自定义文章类型,再从一个定时任务,或者一个按需调用同一个辅助函数的管理端”批准并颁发”按钮里发起颁发。

每门课程都需要单独的徽章,还是一枚徽章可以覆盖多门课程? 两种方式都可行。一些颁发者会为每门课程各创建一枚徽章以获得更细的粒度;另一些则只创建一枚”项目完成”徽章,并且只在所有必修课程都完成后才颁发它,在 hook 内部先检查前置条件是否满足,再调用 API。


准备好颁发你的第一份可验证凭证了吗?前往 badges.ninja 免费开始——可视化设计器、公开验证页面、PDF 证书、Open Badge v2.0 输出。无需信用卡。

Nacho Coll

关于作者

Founder & Engineer at Badges Ninja

Nacho founded Badges Ninja to make issuing verifiable digital credentials as simple as a single API call — Open Badge v2.0 badges and certificates, minted, hosted, and verifiable without standing up your own issuer infrastructure. Writes about the Open Badges spec, credential verification, and running a credentialing platform serverless on AWS, from the operator side of the wire.

返回博客

相关文章