本文目录导读:

在PHP项目中实现推荐反馈闭环,核心思路是:记录用户行为 -> 模型更新或策略调整 -> 生成新推荐 -> 再次记录行为,形成一个数据驱动的循环系统。
下面我会从架构设计、技术实现、代码示例三个层面,帮你完整落地这个闭环。
整体架构设计
推荐反馈闭环通常包含以下4个步骤:
用户行为(点击/购买/跳过)
→ 日志采集 (PHP中间件/队列)
→ 特征/标签更新 (异步或定时任务)
→ 推荐策略调整 (规则/算法)
→ 生成新推荐列表
→ 展示给用户
→ 再次采集反馈
在PHP项目里,重点在于行为采集层和策略调整层,模型训练部分可以用Python/Spark,但PHP可以做简单的协同过滤或规则引擎。
关键模块实现
行为采集模块(用户反馈入环)
必须采集的核心数据:
| 事件类型 | 字段示例 | 说明 |
|---|---|---|
| 曝光 | item_id, position, timestamp | 用户看到什么 |
| 点击 | item_id, dwell_time | 用户对什么感兴趣 |
| 正向行为 | 收藏/加购/购买 | 强正反馈 |
| 负向行为 | 不感兴趣/长时间未点击 | 负反馈 |
| 隐性反馈 | 鼠标轨迹/滚动深度 | 可选的 |
代码示例(Laravel风格的采集中间件):
// App/Http/Middleware/CollectBehavior.php
public function handle($request, Closure $next)
{
$response = $next($request);
// 从请求中提取反馈数据
$behavior = [
'user_id' => auth()->id(),
'item_id' => $request->input('item_id'),
'action' => $request->input('action'), // view/click/buy/skip
'position' => $request->input('position'),
'duration' => $request->input('duration'),
'context' => [
'page' => $request->path(),
'referer' => $request->header('referer'),
'device' => $request->userAgent(),
],
'timestamp' => now(),
];
// 异步写入(推荐用Redis队列 + 消费者进程)
dispatch(new SaveBehaviorJob($behavior));
return $response;
}
特征/画像更新模块(闭环反馈 -> 模型输入)
消费者进程(artisan command):
// app/Jobs/SaveBehaviorJob.php
public function handle()
{
DB::table('user_behaviors')->insert($this->behavior);
// 实时更新用户短期兴趣标签(用于实时推荐)
$userId = $this->behavior['user_id'];
$itemId = $this->behavior['item_id'];
$itemTags = ItemTag::where('item_id', $itemId)->pluck('tag');
// 衰减+累加用户标签权重
UserTagWeight::updateOrCreate(
['user_id' => $userId, 'tag' => $itemTags],
['weight' => DB::raw('weight + 1')]
);
}
推荐引擎模块(基于反馈生成新推荐)
两种常见策略,根据你的数据量选择:
方案A:标签匹配(适合中小项目)
class TagBasedRecommender
{
public function recommend($userId, $limit = 20)
{
// 1. 获取用户最高权重的标签
$userTags = UserTagWeight::where('user_id', $userId)
->orderBy('weight', 'desc')
->take(5)
->pluck('tag');
// 2. 找出未被该用户交互过的、包含这些标签的物品
$excluded = UserBehavior::where('user_id', $userId)
->whereIn('action', ['click', 'buy'])
->pluck('item_id');
$recommendations = Item::
whereHas('tags', function($q) use ($userTags) {
$q->whereIn('tag', $userTags);
})
->whereNotIn('id', $excluded)
->withCount(['behaviors as score' => function($q) {
// 用物品的总热度作为辅助排序
$q->where('action', 'click');
}])
->orderBy('score', 'desc')
->take($limit)
->get();
return $recommendations;
}
}
方案B:协同过滤(适合有较多用户行为数据)
可以用Redis实现基于物品的协同过滤:
class ItemCFRecommender
{
private $redis;
public function recommend($userId, $limit = 20)
{
// 1. 获取用户的近期正反馈物品
$likedItems = UserBehavior::where('user_id', $userId)
->whereIn('action', ['click', 'buy'])
->orderBy('created_at', 'desc')
->take(10)
->pluck('item_id');
// 2. 从Redis读取物品相似度矩阵,聚合相似物品
$candidates = [];
foreach ($likedItems as $itemId) {
$similarItems = $this->redis->zRevRange("item_sim:{$itemId}", 0, 20, true);
foreach ($similarItems as $simItemId => $score) {
if (!isset($candidates[$simItemId])) {
$candidates[$simItemId] = 0;
}
$candidates[$simItemId] += $score;
}
}
// 3. 过滤掉已交互的,取TopN
$excluded = UserBehavior::where('user_id', $userId)
->pluck('item_id')
->toArray();
$candidates = array_diff_key($candidates, array_flip($excluded));
arsort($candidates);
return array_slice(array_keys($candidates), 0, $limit);
}
}
反馈注入推荐结果(展示时带上反馈点)
渲染推荐列表时,必须包含反馈追踪信息:
<!-- 推荐卡片 -->
<div class="recommend-item"
data-item-id="{{ $item->id }}"
data-position="{{ $index }}">
<a href="/item/{{ $item->id }}"
onclick="trackClick(event, {{ $item->id }}, {{ $index }})">
<img src="{{ $item->image }}" />
<h3>{{ $item->title }}</h3>
</a>
</div>
<script>
function trackClick(event, itemId, position) {
// 发送反馈到 /api/behavior
fetch('/api/behavior', {
method: 'POST',
body: JSON.stringify({
item_id: itemId,
position: position,
action: 'click'
})
});
}
</script>
定时闭环:更新推荐模型
创建artisan command,定期执行:
// app/Console/Commands/UpdateRecommendationModel.php
class UpdateRecommendationModel extends Command
{
protected $signature = 'recommend:update-model';
public function handle()
{
// 1. 重新计算物品相似度矩阵(基于最近N天的行为)
$this->calculateItemSimilarity();
// 2. 更新全局热门榜 / 探索池
$this->updateHotItems();
// 3. 清理过期的负反馈缓存
$this->cleanExpiredNegatives();
}
}
crontab设置:* */1 * * * php artisan recommend:update-model(每小时更新)
闭环效果验证与优化
关键指标监控
在PHP项目中集成一个简单的指标记录:
// app/Services/MetricsService.php
class MetricsService
{
public static function track($userId, $strategy, $result)
{
// 记录每个session的指标
Cache::tags("metrics:{$userId}")->put([
'ctr' => $result['clicks'] / max($result['impressions'], 1),
'conversion' => $result['purchases'] / max($result['clicks'], 1),
'coverage' => count($result['items_shown']),
'diversity' => $result['unique_categories'] / count($result['items_shown']),
'freshness' => $result['new_items'] / count($result['items_shown']),
]);
}
}
常见问题与优化技巧
| 问题 | 解决方案 |
|---|---|
| 新用户冷启动 | 纯热门推荐 + 快速标签采集(首屏交互后立即更新) |
| 反馈延迟 | 使用Redis队列异步处理,别阻塞主流程 |
| 结果同质化 | 在最终推荐列表中加入10%的探索池(基于随机或冷门) |
| 计算量过大 | 离线计算相似度矩阵,缓存到Redis,线上只做查表聚合 |
完整闭环示意图
用户点击物品A
↓
PHP采集中间件 -> 写入Redis队列
↓
Job消费者:
- 写入MySQL行为表
- 更新用户标签权重 (+1)
- 更新物品热度计数
↓
下次请求推荐API时:
- 读取用户最新标签权重
- 查询未被点击的物品
- 按权重*热度排序
- 混入探索池 -> 返回20条
↓
用户看到新推荐列表 -> 再次点击
↓
形成闭环
整个闭环可以在一个PHP项目内完全实现,核心不需要外部系统,适合中小型项目的渐进式增长需求。