在搭建带有听书能力的php站点时,很多团队会把第三方听书插件当作独立模块来用,结果用户明明在插件里点过收藏,回到网站个人中心却看不到。要让php调用的听书插件和用户收藏功能真正关联起来,核心是把插件的收藏事件捕获到,再落到自己库的 user_favorite 表里,并以用户唯一标识做桥接。

一、数据表与关联字段设计
先确认主站用户表用的是自增 id 还是第三方 openid。多数 cms 系统用户表为 user,字段至少包含 id、username。我们需要一张收藏关系表,把用户和听书插件里的 book_id 绑定。表结构不必复杂,但索引要合理。
下面给出简单的建表语句,注意 book_id 是听书插件返回的内容编号,source 用来区分不同插件渠道,避免以后接多个音频源时混乱。user_id 必须和主站用户表强制一致,这是关联的基础。
CREATE TABLE `user_favorite` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '主站用户id', `book_id` varchar(64) NOT NULL COMMENT '听书插件书籍id', `source` varchar(32) NOT NULL DEFAULT 'tingbook' COMMENT '插件来源', `created_at` int(11) NOT NULL COMMENT '收藏时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_user_book` (`user_id`,`book_id`,`source`), KEY `idx_user` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
二、php接收插件收藏回调
主流听书插件支持在用户点击收藏时,向配置的回调地址发送 POST 请求,携带 book_id、user_token 以及签名 sign。php 侧要先做签名校验,再解析 token 拿到真实 user_id,最后写入表。直接信任客户端传的 user_id 会被刷库,这点必须避免。
以下代码展示了一个轻量的回调处理。假设插件使用 md5(secret + book_id + user_token) 生成 sign,我们在服务端用相同规则比对。验证通过后,用 token 换 user_id 并插入,借助唯一索引防止重复收藏。
<?php
$secret = 'your_plugin_secret';
$bookId = $_POST['book_id'] ?? '';
$token = $_POST['user_token'] ?? '';
$sign = $_POST['sign'] ?? '';
if (empty($bookId) || empty($token) || empty($sign)) {
http_response_code(400);
echo json_encode(['code' => 1, 'msg' => '参数缺失']);
exit;
}
$expect = md5($secret . $bookId . $token);
if ($expect !== $sign) {
http_response_code(403);
echo json_encode(['code' => 2, 'msg' => '签名错误']);
exit;
}
// 用 token 换用户id,这里简化为查表
$userId = getUserIdByToken($token);
if (!$userId) {
http_response_code(401);
echo json_encode(['code' => 3, 'msg' => '用户无效']);
exit;
}
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'pass');
$sql = 'INSERT IGNORE INTO user_favorite (user_id, book_id, source, created_at)
VALUES (?, ?, 'tingbook', ?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([$userId, $bookId, time()]);
echo json_encode(['code' => 0, 'msg' => 'ok']);
三、读取用户收藏并回传插件
关联不是单向的,用户在网站收藏的书籍,也应该能在听书插件里高亮。php 需要提供个接口,按 user_id 查 user_favorite,把 book_id 列表返回给插件端渲染。这里用联表或直接查关系表都可以,数据量小的时候单表就够了。
下面示例读取当前用户收藏,并输出 json。插件拿到后,对比本地书籍列表即可标记已收藏。注意分页,避免用户收藏几千本时一次性拉垮接口。
<?php
$userId = getLoginUserId();
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'pass');
$page = (int)($_GET['page'] ?? 1);
$limit = 20;
$offset = ($page - 1) * $limit;
$sql = 'SELECT book_id, created_at FROM user_favorite
WHERE user_id = ? AND source = 'tingbook'
ORDER BY created_at DESC LIMIT ? OFFSET ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$userId, $limit, $offset]);
$list = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['code' => 0, 'data' => $list]);
四、常见误区与处理
一种典型错误是用了 <input> 隐藏域存 user_id 传给插件,以为这样就能关联,实际上听书插件多运行在 iframe 或独立端,根本读不到主站 dom。正确做法是通过 token 或 oauth 换身份,而不是依赖页面标签。
另一个坑是忽略编码,book_id 若含中文或特殊符号,回调时没 urlencode,php 收到就乱码,导致同一本书存成两条。建议在插件端发送前 rawurlencode,php 收到后直接入库,查询时也用原值比对,不要擅自转义。
只要把签名、用户映射、去重索引三件事做对,php 调用听书插件对接用户收藏功能就能稳定关联,后续扩展其他音频源也只是加 source 值的事。