ThinkPHP实现推荐系统需离线预计算协同过滤相似度并存入Redis,内容推荐用TF-IDF向量缓存,混合推荐按相似用户→共现标签→向量相似三级fallback,全程需超时控制与异步更新。

ThinkPHP 本身不提供推荐引擎,所有“智能推荐”都得靠你自己搭骨架、选算法、避坑位。直接用 Db::table('user_action')->select() 拿数据然后 foreach 算相似度,用户一过百就卡死;硬套 Python 的 sklearn 思路,在 PHP 里写矩阵分解,内存和耗时都会失控。
协同过滤必须预计算相似度,不能实时算
PHP 是请求级生命周期,没有共享内存或状态缓存。每次 HTTP 请求都从头读数据库、重算用户相似度,array_reduce 套三层循环 + 平方根运算,100 个活跃用户就能干到 300ms+。真实可用的做法是:把相似度矩阵离线生成,存成结构化数据供接口查表。
- 用定时任务(
cron)每小时跑一次php think rec:build-user-sim命令,输出 JSON 或写入 Redis - Redis 中存成
sim:user_a:neighborshash,字段为user_b→0.87,支持HGETALL+ZRANGE快速取 top-K - 绝对不要用
file_get_contents('sim.json')加载几 MB 文件——改用fopen()+fgets()流式解析,或直接让 Redis 承担存储和索引 - 相似度函数必须做均值中心化(mean-centering),否则冷门用户评分偏低会导致推荐严重偏移
内容推荐别碰 LIKE %keyword%,用 TF-IDF + 向量缓存
对 content 字段加 FULLTEXT 索引也扛不住并发查询,更别说 WHERE title LIKE '%PHP%' 这种全表扫描。关键词匹配只能当 fallback,且必须脱离主表。
- 预处理每篇文章:小写化、去标点、按空格/顿号切词、过滤停用词(如“的”“了”),生成词频向量
- 全局统计 IDF,单篇用
tf * idf得到稠密向量,存进runtime/cache/article_vectors.php(PHP 数组格式,include即可读) - 余弦相似度阈值设为
0.15起步,防噪声;避免用pow($x, 2)计算平方,改用$x * $x防负值异常 - 若需实时更新,只增量重算新文章向量,老向量复用;全文向量文件超过 2MB 就拆成按 ID 区间分片
混合推荐要明确 fallback 顺序,不能拼凑逻辑
纯协同过滤对新物品(cold start)完全失效,纯内容推荐对行为稀疏用户不准——二者不是“加起来更好”,而是要有清晰的兜底链路。
立即学习“PHP免费学习笔记(深入)”;
- 第一优先:查 Redis 中该用户的
sim:user_x:neighbors,取 top-15 相似用户看过但当前用户没看过的 item - 第二优先:若无结果或 item 数<3,查
item_tags表中该用户历史高互动 item 的 tag_id 集合,用WHERE tag_id IN (101,205)+GROUP BY item_id HAVING COUNT(*) >= 2找共现标签 - 第三优先:fallback 到 TF-IDF 向量相似文章,仅限当前 item 本身有摘要/标题且已预计算向量
- 所有环节必须带超时控制:
Redis::hgetall()超过 50ms 就跳过,不阻塞主流程
最易被忽略的是向量缓存的更新时机和冷热分离——没人会手动清 article_vectors.php,但新文章发布后若不触发向量化任务,推荐结果就永远滞后。别指望“自动监听数据库变更”,老实用命令行 + 钩子:发布文章成功后,立刻 exec("php think rec:update-vector {$itemId} > /dev/null &") 异步跑。



















