使用浏览量的协调过滤推荐算法
分类: Java 专栏: java 标签: 使用浏览量的协调过滤推荐算法
2026-03-22 23:13:08 97浏览
使用浏览量的协调过滤推荐算法
mysql
DROP TABLE IF EXISTS `sx_productview`; CREATE TABLE `sx_productview` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键自增长', `user_id` int NOT NULL COMMENT '用户唯一标识符,用于区分不同用户', `item_id` int NOT NULL COMMENT '被浏览/交互的内容项ID(如商品ID、文章ID等)', `timestamp` bigint NOT NULL COMMENT '记录事件发生的时间戳(通常为Unix时间戳,毫秒级精度)', `is_repeated` tinyint(1) NOT NULL DEFAULT '0' COMMENT '标记该次浏览是否为重复访问(true表示用户之前已看过该内容)', `duration` bigint NOT NULL COMMENT '用户停留/交互持续时间(单位通常是毫秒)', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物品浏览权重表';
package com.ruoyi.system.utils;
import com.ruoyi.system.domain.SxProductview;
import java.util.*;
import java.util.stream.Collectors;
/*
* 协同过过滤,根据用户浏览商品的数据推荐*/
public class ItemCF {
// 用户-物品浏览矩阵:<用户ID, <物品ID, 加权浏览得分>>
private Map<Integer, Map<Integer, Double>> userItemMatrix;
// 物品相似度矩阵:<物品ID, <相似物品ID, 相似度>>
private Map<Integer, Map<Integer, Double>> itemSimilarityMatrix;
public ItemCF(List<SxProductview> viewLogs) {
this.userItemMatrix = buildUserItemMatrix(viewLogs);
this.itemSimilarityMatrix = calculateSimilarity();
}
// 1. 数据预处理:构建用户-物品评分矩阵
private Map<Integer, Map<Integer, Double>> buildUserItemMatrix(List<SxProductview> logs) {
Map<Integer, Map<Integer, Double>> matrix = new HashMap<>();
long currentTime = System.currentTimeMillis();
for (SxProductview log : logs) {
double score = 1.0; // 基础分
if (log.getIsRepeated()) score += 0.5;
if (log.getDuration() > 30000) score += 2.0;
// 时间衰减:3天前的行为权重降为30%
double decay = Math.exp(-0.3 * (currentTime - log.getTimestamp()) / (86400000));
double finalScore = score * decay;
matrix.computeIfAbsent(log.getUserId(), k -> new HashMap<>())
.merge(log.getItemId(), finalScore, Double::sum);
}
return matrix;
}
// 2. 核心算法:计算物品相似度矩阵
private Map<Integer, Map<Integer, Double>> calculateSimilarity() {
Map<Integer, Map<Integer, Double>> simMatrix = new HashMap<>();
Map<Integer, Set<Integer>> itemUsersMap = new HashMap<>();
// 构建物品-用户倒排表
for (Map.Entry<Integer, Map<Integer, Double>> entry : userItemMatrix.entrySet()) {
int userId = entry.getKey();
for (int itemId : entry.getValue().keySet()) {
itemUsersMap.computeIfAbsent(itemId, k -> new HashSet<>()).add(userId);
}
}
// 计算物品两两相似度
List<Integer> itemIds = new ArrayList<>(itemUsersMap.keySet());
for (int i = 0; i < itemIds.size(); i++) {
int itemA = itemIds.get(i);
Set<Integer> usersA = itemUsersMap.get(itemA);
for (int j = i + 1; j < itemIds.size(); j++) {
int itemB = itemIds.get(j);
Set<Integer> usersB = itemUsersMap.get(itemB);
// 计算共同用户
Set<Integer> commonUsers = new HashSet<>(usersA);
commonUsers.retainAll(usersB);
if (commonUsers.isEmpty()) continue;
// 计算加权余弦相似度
double dotProduct = 0, normA = 0, normB = 0;
for (int user : commonUsers) {
double scoreA = userItemMatrix.get(user).get(itemA);
double scoreB = userItemMatrix.get(user).get(itemB);
dotProduct += scoreA * scoreB;
normA += Math.pow(scoreA, 2);
normB += Math.pow(scoreB, 2);
}
double similarity = dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
similarity *= Math.log(1 + commonUsers.size()); // 热门物品惩罚
// 双向存储相似度
simMatrix.computeIfAbsent(itemA, k -> new HashMap<>()).put(itemB, similarity);
simMatrix.computeIfAbsent(itemB, k -> new HashMap<>()).put(itemA, similarity);
}
}
return simMatrix;
}
// 3. 推荐生成:基于用户最近浏览
//TopN强调按特定规则(如评分、热度等)排序后取前N条数据,结果通常保证有序性
public List<Integer> recommend(int userId, int topN) {
if (!userItemMatrix.containsKey(userId)) {
return Collections.emptyList(); // 冷启动处理
}
// 获取用户最近浏览的5个商品
List<Integer> recentItems = userItemMatrix.get(userId).entrySet().stream()
.sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
.limit(5)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
// 合并相似商品并排序
Map<Integer, Double> candidateItems = new HashMap<>();
for (int itemId : recentItems) {
itemSimilarityMatrix.getOrDefault(itemId, Collections.emptyMap())
.forEach((similarItem, simScore) -> {
if (!userItemMatrix.get(userId).containsKey(similarItem)) {
double weightedScore = simScore * userItemMatrix.get(userId).get(itemId);
candidateItems.merge(similarItem, weightedScore, Double::sum);
}
});
}
return candidateItems.entrySet().stream()
.sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
.limit(topN)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
// 示例用法
public static void main(String[] args) {
// 模拟用户浏览数据
List<SxProductview> logs = Arrays.asList(
// new SxProductview(1,101, 2001, System.currentTimeMillis() - 100000, false, 25000l),
// new SxProductview(2,101, 2003, System.currentTimeMillis() - 50000, true, 40000l),
// new SxProductview(3,102, 2001, System.currentTimeMillis() - 200000, false, 10000l),
// new SxProductview(4,102, 2005, System.currentTimeMillis() - 220000, false, 70000l),
// new SxProductview(5,102, 2006, System.currentTimeMillis() - 300000, false, 90000l),
// new SxProductview(6,102, 2001, System.currentTimeMillis() - 400000, false, 40000l),
// new SxProductview(7,102, 2002, System.currentTimeMillis() - 500000, false, 15000l),
// new SxProductview(8,102, 2003, System.currentTimeMillis() - 600000, true, 7000l)
);
ItemCF recommender = new ItemCF(logs);
List<Integer> recommendations = recommender.recommend(101, 3);
System.out.println("推荐结果:" + recommendations);
}
}
public class SxProductview extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键自增长 */
private Integer id;
/** 用户唯一标识符,用于区分不同用户 */
@Excel(name = "用户唯一标识符,用于区分不同用户")
private Integer userId;
/** 被浏览/交互的内容项ID(如商品ID、文章ID等) */
@Excel(name = "被浏览/交互的内容项ID", readConverterExp = "如=商品ID、文章ID等")
private Integer itemId;
/** 记录事件发生的时间戳(通常为Unix时间戳,毫秒级精度) */
@Excel(name = "记录事件发生的时间戳", readConverterExp = "通=常为Unix时间戳,毫秒级精度")
private Long timestamp;
/** 标记该次浏览是否为重复访问(true表示用户之前已看过该内容) */
@Excel(name = "标记该次浏览是否为重复访问", readConverterExp = "t=rue表示用户之前已看过该内容")
private Boolean isRepeated;
/** 用户停留/交互持续时间(单位通常是毫秒) */
@Excel(name = "用户停留/交互持续时间", readConverterExp = "单=位通常是毫秒")
private Long duration;
}
添加浏览数据
@PostMapping("/add")
public AjaxResult add(@RequestBody Jfview o)
{
Jfview ol=null;
Jfview c=new Jfview();
c.setUserId(o.getUserId());
c.setItemId(o.getItemId());
List<Jfview> li=jfviewService.selectJfviewList(c);
if(li!=null&&li.size()>0)ol=li.get(0);
if(ol!=null){
if(ol.getDuration()!=null)o.setDuration(ol.getDuration()+o.getDuration());
o.setRepeated(true);
o.setId(ol.getId());
jfviewService.updateJfview(o);
}else{
o.setRepeated(false);
jfviewService.insertJfview(o);
}
return success();
}
//根据用户浏览数据推荐商品,协同过滤,要有一定量的商品,和不同用户的对商品的浏览数据controller
@GetMapping("/tj")
public AjaxResult tj( Integer myuid){
if(myuid==null||myuid<=0){
List<SxProduct> gli=new ArrayList<>();
return success(gli);
}
List<SxProductview> logs = sxProductviewService.selectSxProductviewList(new SxProductview( ));
ItemCF recommender = new ItemCF(logs);
List<Integer> recommendations = recommender.recommend(myuid, 10);
System.out.println("推荐结果:" + recommendations);
if(recommendations==null||recommendations.size()==0){
List<SxProduct> gli=new ArrayList<>();
return success(gli);
}
SxProduct o=new SxProduct();
o.setStatus("1");
o.setInidli(recommendations);
List<SxProduct> gli=sxProductService.selectSxProductList(o );
return success(gli);
}
uniapp3可以这样写
onShow(() => {
startTemp.value=new Date().getTime();
console.log("--------onShow----------startTemp.value:"+startTemp.value)
})
onDeactivated(() => {
endTemp.value=new Date().getTime();
console.log("-------onDeactivated-----------endTemp.value:"+endTemp.value)
saveView();
})
onHide(() => {
endTemp.value=new Date().getTime();
console.log("--------onHide----------endTemp.value:"+endTemp.value)
saveView();
})
网页,由于直接关闭网页会提交不了,所以使用计时器监听
created() {
this.startTemp=new Date().getTime();
if(!this.seti)this.seti=setInterval(()=>{
this.endTemp=new Date().getTime();
this.saveView();
},1000*5)
this.id=this.$route.query.id;
if(this.$store.state.user.token ){
this.islogin=1;
}
this.getDetail();
this.getProductList("")
this.getProductRatingList();
},
destroyed() {
this.endTemp=new Date().getTime();
this.saveView();
if(this.seti)clearInterval(this.seti)
},
activated() {
this.startTemp=new Date().getTime();
if(!this.seti)this.seti=setInterval(()=>{
this.endTemp=new Date().getTime();
this.saveView();
},1000*5)
},
deactivated() {
this.endTemp=new Date().getTime();
this.saveView();
if(this.seti)clearInterval(this.seti)
},
<if test="inidli != null ">
and product_id in
<foreach item="item" index="index" collection="inidli" open="(" separator="," close=")">
#{item}
</foreach>
</if>
saveView(){
if(localStorage.login_uid && this.id){
let o={
userId:localStorage.login_uid,
itemId:this.id,
timestamp:this.startTemp,
duration:this.endTemp - this.startTemp
}
addProductview(o).then(response => {
console.log('保存浏览记录成功', o);
}).catch(() => {
console.error('保存浏览记录失败');
})
}
},
data
seti:null,
startTemp:0,
endTemp:0,
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
暂无评论,快来写一下吧
展开评论
他的专栏
他感兴趣的技术





java
vue
springboot
Mysql
ssm
小程序
uniapp
js和jquery