思路来源
https://help.aliyun.com/document_detail/26367.html?spm=a2c4g.11174283.6.624.3e95dce0D4jdTV
实现步骤
- 下单时触发监听事件,把订单中一起购买的商品存入redis中
- 为每个商品创建一个zset有序集合,成员为相关的商品,score为同时被购买的次数
- 洗数据
- 把之前的订单中的商品以脚本的形式跑一遍,把出现过一起购买的商品相关性记入redis中
- 接口推荐
- 根据购物车中的商品, 在redis中取出相关的商品ids 并排序
- 排序规则
- 多次出现的商品进行顶置 ,出现最多次数的商品放在'你可能喜欢的'列表的第一位
- 只出现一次的商品,按每组商品相关性score进行排序 , 每组的第一位拿出来放在最上面,以此类推
代码实现
- 相关性逻辑类(以Magento中实现的代码为例),只做参考,提供思路,代码可以不看
<?php
class Fun_Catalog_Helper_ProductRelation extends Mage_Core_Helper_Abstract
{
const PRODUCT_RALATION_KEY_PREFIX = 'product:relation:';
/**
* 保存一个quote里的商品相关性
* @param Fun_sales_quote $quote
* @return bool
*/
public function saveProductRelation($quote)
{
try{
$data = $quote -> getAllItems();
foreach($data as $v){
$productId = $v->getProductId();
//如果是简单商品取它的配置商品 Id
$product = Mage::getModel('catalog/product')->load($productId);
if($product->getTypeId() == 'simple'){
$parentIds = Mage::getResourceSingleton('catalog/product_type_configurable')->getParentIdsByChildFast($productId);
}
$productId = $parentIds[0] ? $parentIds[0] : $productId;
$productIds[] = $productId;
}
//给每个商品创建redis 有序集合
$redis = Mage::helper('redis')->getRedis(9);
//每次有两个或者两个以上的商品时, 给每个商品的分数增加 1
// Mage::log($productIds,null,'productRelation.log');
if(count($productIds) > 1){
foreach($productIds as $key=>$productId)
{
foreach($productIds as $k => $memberId){
if($productId == $memberId){
continue;
}
//成员为每个商品同时添加购物车的商品, 分数为同时添加到购物车的次数
if($redis->ZRANK(SELF::PRODUCT_RALATION_KEY_PREFIX.$productId,$memberId) !== FALSE){
//已存在, 相关性分数 +1
$redis->ZINCRBY(SELF::PRODUCT_RALATION_KEY_PREFIX.$productId,1,$memberId);
}else{
//不存在, 创建相关性 member
$redis->ZADD(SELF::PRODUCT_RALATION_KEY_PREFIX.$productId,1,$memberId);
}
}
}
}
return TRUE;
}catch(Exception $e){
Mage::Log($e->getMessage(),null,'system.log');
return FALSE;
}
}
/**
* 从redis中取出与此商品列表相关的商品列表
* @param array $productIds
* @param int $page_index
* @param int $page_size
* @param bool $paging 是否分页
* @return array
*/
public function getRelationProductList($productIds,$page_index = 1,$page_size = 20,$paging = false)
{
$redis = Mage::helper('redis')->getRedis(9);
try{
if(count($productIds)>0)
{
$DList = [];
$DDList = [];
foreach($productIds as $productId)
{
//一维数组product_id列表,用来取多次出现的相关商品
$one = $redis->ZREVRANGE(SELF::PRODUCT_RALATION_KEY_PREFIX.$productId,0,100);
$DList = array_merge($DList,$one);
//二维数组product_id列表, 用来取以score为排序的相关商品
$oneWithScores = $redis->ZREVRANGE(SELF::PRODUCT_RALATION_KEY_PREFIX.$productId,0,100,WITHSCORES);
$DDList[] = $oneWithScores;
}
//出现次数排序
//先排序多次出现的相关商品 这些要顶置
$productCountList = array_count_values($DList);
foreach($productCountList as $k=>$v){
if($v<2){
unset($productCountList[$k]);
}
}
arsort($productCountList);
// Mage::log($productCountList,null,'productRelation.log');
//得分排序
//再按各个商品的的相关性商品的得分排序
//每个商品的相关商品列表里得分最高的放在前面 ,以此类推
foreach($DDList as $key=>$value)
{
$i = 0;
foreach($value as $k=>$v)
{
$temp[$i][] = $k;
$i++;
}
}
$scoreList = [];
foreach($temp as $v){
$scoreList = array_merge($scoreList,$v);
}
$scoreList = array_flip($scoreList);
// Mage::log($scoreList,null,'productRelation.log');
//合并排序, 出现次数列表顶置, 得分列表随后 , 前面已有的 product_id会覆盖后面出现的 product_id 保证 product_id 不重复
$finalList = $productCountList + $scoreList;
$finalProductIds = array_keys($finalList);
// Mage::log($finalProductIds,null,'productRelation.log');
//根据数组特性分页
if($paging){
$finalProductIds = array_slice($finalProductIds,$page_size*($page_index-1),$page_size);
}
//过滤当前购物车里存在的商品
foreach($finalProductIds as $k=>$id){
if(in_array($id,$productIds)){
unset($finalProductIds[$K]);
}
}
return $finalProductIds;
}
}catch(Exception $e){
throw new Exception($e->getMessage());
}
}
}
- 前端接口实现
<?php
/**
* 支付或COD验证成功页面和购物车页面加入推荐商品流 (按照 redis里的商品相关性有针对性的真·推荐流)
* @param int $page_index
* @param int $page_size
* @return type josn
*/
public function recommendByOthersBoughtAction()
{
$page_size = $this->getRequest()->getParam('page_size', 20);
$page_index = $this->getRequest()->getParam('page_index', 1);
$product_ids = $this->getRequest()->getParam('product_ids');
$resource = $this->getRequest()->getParam('resource');
$productIds = json_decode($product_ids,true);
if (empty($productIds)){
return parent::recommendByOthersBoughtAction();
}
$cacheTags = array('product_recommend_by_others_bought');
$cacheKey = $this->getCacheKey() . '_recommend_by_others_bought' . '_' . $page_index . '_' . $page_size . '_' . $resource . '_' .$product_ids;
$productsData = unserialize($this->loadCache($cacheKey));
if (!$productsData) {
//从redis中取当前商品相关的商品
$relationProductIds = Mage::helper('fun_catalog/productRelation')->getRelationProductList($productIds,$page_index,$page_size,true);
$relationProducts = [];
foreach($relationProductIds as $relationProductId){
$product = Mage::helper('elasticsearch/product')->getByProductId($relationProductId);
$temp = Mage::helper('elasticsearch')->getListProductDetailLite($product['data'][0]);
$relationProducts[] = $temp;
}
$this->saveCache(serialize($relationProductList), $cacheKey, $cacheTags);
}
return $this->toSuccessJsonWithData($relationProducts);
}