具体需求:
每一个用户浏览的产品做记录,
例如 A 用户看了产品 a,b,c 三个,B用户看了a,m,c 三个
那么a产品的看了又看列表显示为:
c,b,m 即 看a 产品的更多的会看 c产品,其次 b产品 和 m产品
实现思路(很直白的实现思路,希望抛砖引玉):
(实现的是实实在在的浏览关联显示,暂不考虑广告竞价,虚假排名的情况)
首先浏览记录不需要做成实时,且实时会有很多并发问题。那么就只需要记录每个用户的浏览记录,最终找个时间点统计即可。
但是又存在浏览权重的问题,例如 不同用户浏览a产品都会去看c 那么c的权重高。
那么产品的关联性还要有权重的标识。
那么数据库设计就存在多种方式,我采用的设计方式是:
表一:product_view_history
id为自增id
u_id是每个用户的唯一标识
value是每个用户浏览产品记录
create_at是记录添加时间
calcu_at是统计时间(打算使用计划任务,到点统计此时需要统计的数据)
表二:product_view_relate
id为自增id
p_id是产品id
value是产品关联产品以及权重(格式: 产品id_权重)
当用户浏览一个商品的时候 在表product_view_history找到此用户,有则更新数据,无则添加数据
u_id是用户的唯一标识,可以是 session_id 也可以为每个用户唯一生成,app可以采用token等标识。
注意这里判断此用户是否已经记录过了,不仅仅是根据 u_id 的存在还有 calcu_at 是同一个统计时间。
记录浏览记录 php 伪代码:
function calcuAt($now = ''){
if($now == ''){
$now = date();
}
$now = strtotime($now);
// $this->time_period 统计的时间间隔(单位是小时)
$this->time_period = $this->time_period == 0 ? 1:$this->time_period;
$ymd = date('Y-m-d',$now);
$h = date('H',$now);
$calcu_h = ((int)($h / $this->time_period)) * $this->time_period + $this->time_period;
$calcu_h = $calcu_h > 24 ? 24 : $calcu_h;
$calcu_date_to_time = strtotime($ymd) + $calcu_h * 3600;
$calcu_date = date('Y-m-d H:i:s',$calcu_date_to_time);
return $calcu_date;
}
function recordProductViewRelate($product){
$uid = $user->getUid();
$now = date('Y-m-d H:i:s');
$calcu_at = $this->calcuAt($now);
$pvhistory = getModel('product_view_history');
$pvhistory_data = $pvhistory->getCollection()
->addFieldToFilter('u_id',$uid)
->addFieldToFilter('calcu_at',$calcu_at)
->getFirstItem()
;
if(@$pvhistory_data->getId()){
$view_ids = explode(',',$pvhistory_data->getValue());
if(!in_array($product->getId(),$view_ids)){
// 最多记录 10个相关联浏览记录
if(count($view_ids) >= 10){
array_shift($view_ids);
}
$view_ids[] = $product->getId();
$view_ids_str = implode(',',$view_ids);
$pvhistory_data->setValue($view_ids_str)->save();
}
}else{
$sd = array(
'u_id' => $uid,
'value' => $product->getId(),
'create_at' => $now,
'calcu_at' => $calcu_at,
);
$pvhistory->setData($sd)->save();
}
return 1;
}
然后再写一个计划任务,每次去统计这些数据即可。
例如 统计时间间隔是 4小时,那么 0,4,8,12,16,20 点分别会统计一次
计划任务伪代码:
<?php
// 排序函数
function cmp($a,$b){
list($a1,$a2) = explode('_',$a);
list($b1,$b2) = explode('_',$b);
if ($a2 == $b2) {
return 0;
}
return ($a2 < $b2) ? 1 : -1;
}
$time_period = $this->time_period;
$now_time = time();
$ymd = date('Y-m-d',$now_time);
$h = date('H',$now_time);
if($h % $time_period != 0){
// 没到启动时间
exit;
}
$h_start = ((int)($h / $time_period)) * $time_period;
$h_end = ((int)($h / $time_period)) * $time_period + $time_period;
$h_end = $h_end > 24 ? 24 : $h_end;
$period_start = strtotime($ymd) + $h_start * 3600;
$period_start = date('Y-m-d H:i:s',$period_start);
$period_end = strtotime($ymd) + $h_end * 3600;
$period_end = date('Y-m-d H:i:s',$period_end);
$pvhistory = getModel('product_view_history');
$history_array = $pvhistory->getCollection()
->addFieldToFilter("calcu_at > $period_start")
->addFieldToFilter("calcu_at <= $period_end")
;
if(count($history_array) <= 0){
// 无数据
exit;
}
//todo 整理此时间段内所有用户的浏览记录到
$data = array();
foreach($history_array as $history){
$history_line_array = explode(',',$history['value']);
$history_line_tmp_array = explode(',',$history['value']);
foreach($history_line_array as $history_item){
reset($history_line_tmp_array);
foreach($history_line_tmp_array as $history_item_tmp){
if($history_item == $history_item_tmp){
continue;
}
if(is_array(@$data[$history_item]) && array_key_exists($history_item_tmp,@$data[$history_item])){
list($p_id,$p_weight) = explode('_',$data[$history_item][$history_item_tmp]);
$data[$history_item][$history_item_tmp] = $p_id.'_'.(string)((int)$p_weight + 1);
}else{
// 最多记录 10个相关联浏览记录
if(count(@$data[$history_item]) >= 10){
continue;
}else{
$data[$history_item][$history_item_tmp] = $history_item_tmp.'_1';
}
}
}
}
}
// todo 保存 data 到表 product_view_relate
$pvrelate = getModel('product_view_relate');
foreach($data as $pid => $v_arr){
$v_arr = array_slice($v_arr,0,10,true); // 截取数组前10个 保留键值
$pvrelate_data = $pvrelate->getCollection()->addFieldToFilter('p_id',$pid)->getFirstItem();
if(@$pvrelate_data->getId()){
$v_handle_arr = array();
foreach($v_arr as $v1 => $v2){
list($v2k,$v2v) = explode('_',$v2);
$v_handle_arr[$v1] = $v2v;
}
if($pvrelate_data->getValue() == ''){
$original_relate_arr = array();
}else{
$original_relate_data_arr = explode(',',$pvrelate_data->getValue());
$original_relate_arr = array();
foreach($original_relate_data_arr as $original_relate_data){
list($op_id,$op_weight) = explode('_',$original_relate_data);
$original_relate_arr[$op_id] = $op_weight;
//$original_relate_arr[$op_id] = $original_relate_data;
}
foreach($original_relate_arr as $relate_pid => $relate_weight){
if(array_key_exists($relate_pid,$v_handle_arr)){
$v_handle_arr[$relate_pid] = $v_handle_arr[$relate_pid] + $relate_weight;
unset($original_relate_arr[$relate_pid]);
}
}
}
$arr_final_tmp = $v_handle_arr + $original_relate_arr;
$arr_final = array();
foreach($arr_final_tmp as $arr_final_tmp_k => $arr_final_tmp_v){
$arr_final[$arr_final_tmp_k] = $arr_final_tmp_k.'_'.$arr_final_tmp_v;
}
uasort($arr_final, 'cmp');
$arr_final = array_slice($arr_final,0,10,true); // 截取数组前10个 保留键值
$value = implode(',',$arr_final);
$pvrelate_data->setValue($value)->save();
}else{
uasort($v_arr, 'cmp');
$value = implode(',',$v_arr);
$sd = array(
'p_id' => $pid,
'value' => $value
);
$pvrelate->setData($sd)->save();
}
}
最后查看每个产品的时候 异步取出关联产品数据即可。