基于Jaccard算法的用户浏览历史推荐商品系统实战+springboot+vue源码实现

大家好,这里是小罗毕设工作室。今天给大家带来了一套完整的推荐系统: “基于Jaccard算法的用户浏览历史推荐商品系统”。 系统源码后端实现是springboot,前端是vue3。 

 

视频演示

https://githubs.xyz/show/87cbc1f7-d449-4a03-a124-7b6e24f0aa8a.mp4

 

 

 

算法原理介绍

假设我们有两个商品:商品A和商品B。商品A被用户{小明,小红,小张}浏览过,商品B被用户{小红,小张,小李}浏览过。

得到两个用户群体集合 : {小明,小红,小张}    {小红,小张,小李}  。

我们通过Jaccard算法,将两个集合先取交集得到 {小红,小张} ,大小是2 , 在取并集得到 {小明,小红,小张,小李},大小是4。

因此Jaccard相似度=2/4=0.5,这个值表示两个商品的相似程度,值越接近1表示两个商品越相似(被相同用户群体浏览),系统会优先推荐相似度高的商品。

 

 

实现流程

 

基础数据准备

首先我们需要准备用户的行为数据表:

CREATE TABLE `user_behaviors` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '行为ID,自增主键',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `item_id` bigint NOT NULL COMMENT '物品ID',
  `rating` decimal(3,1) DEFAULT NULL COMMENT '评分(1-5分)',
  `behavior_type` varchar(20) NOT NULL COMMENT '行为类型(VIEW-浏览,LIKE-喜欢,PURCHASE-购买)',
  `timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '行为发生时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='用户行为记录表';

之所以这里有评分字段,是因为后续要扩展 基于协同过滤算法的用户评分推荐系统

当然这里还要有用户表和商品表:

CREATE TABLE `items` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '物品ID,自增主键',
  `name` varchar(200) NOT NULL COMMENT '物品名称',
  `description` text COMMENT '物品描述',
  `category` varchar(50) DEFAULT NULL COMMENT '物品类别',
  `price` decimal(10,2) DEFAULT NULL COMMENT '物品价格',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='商品';

CREATE TABLE `users` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID,自增主键',
`username` varchar(50) NOT NULL COMMENT '用户名',
`email` varchar(100) NOT NULL COMMENT '用户邮箱',
`password` varchar(100) NOT NULL COMMENT '用户密码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB  COMMENT='用户表';

 

表建立好之后,我们需要写好基本的用户登录,商品列表,商品详情等基础接口,然后保证有丰富的行为数据落库,这样推荐系统才能发挥作用。

这些基本的接口我就不讲了,主要讲解一下用户推荐接口的实现。

 

完整系统源码我已经整理清除:

gitcode巅抗目/hadluo2/springboot_vue.git

用户推荐接口的实现

我将代码的流程整理成了一个流程图,首先我们需要构造一个Map结构数据:

 这些数据就是查询用户行为表,将所有的行为数据,按照商品分类,每一个商品都对应了一个用户群体集合。

然后还是查询用户行为表 , 将当前用户的行为数据查询出来,进行下面流程:

 关键的核心点就是:将当前推荐用户买过的商品id对应其他也买过的这个商品的用户作为一个用户集合,然后取所有商品对应的用户群体集合(很多个),然后一个一个经过Jaccard算法计算相似度,把这个相似度最为评分,最后取出评分高的商品最为推荐商品。

 

讲到这里,你应该能懂了,下面看下关键代码实现:


/**
完整代码实现:gitcode巅抗目/hadluo2/springboot_vue.git
* 基于用户浏览历史推荐物品
* 主要步骤如下:
* 1. 获取用户最近的浏览记录,按时间倒序排列
* 2. 获取所有用户的浏览记录,构建物品-用户倒排索引
* 3. 计算用户最近浏览物品与其他物品的相似度
* 4. 考虑时间衰减因素,为每个候选物品计算最终得分
* 5. 返回得分最高的N个物品作为推荐结果
* @param userId 用户ID
* @param numRecommendations 推荐数量
* @return 推荐的物品列表
*/
public List<Item> recommendBasedOnBrowsingHistory(Long userId, int numRecommendations) {
// 1. 获取用户最近的浏览记录
// 创建查询条件:匹配用户ID和浏览行为,按时间戳降序排序
LambdaQueryWrapper<UserBehavior> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserBehavior::getUserId, userId)
.eq(UserBehavior::getBehaviorType, "VIEW")
.orderByDesc(UserBehavior::getTimestamp);
List<UserBehavior> userViews = userBehaviorRepository.selectList(wrapper);

// 如果用户没有浏览记录,返回空列表
if (CollectionUtils.isEmpty(userViews)) {
return Collections.emptyList();
}

// 2. 获取所有用户的浏览记录
// 查询所有用户的浏览行为,用于后续计算物品相似度
List<UserBehavior> allViews = userBehaviorRepository.selectList(
new LambdaQueryWrapper<UserBehavior>()
.eq(UserBehavior::getBehaviorType, "VIEW")
);

// 3. 构建物品-用户的倒排索引
// key: 物品ID, value: 浏览过该物品的用户ID集合
Map<Long, Set<Long>> itemUserMap = new HashMap<>();
for (UserBehavior view : allViews) {
// computeIfAbsent: 如果key不存在,则创建一个新的HashSet
itemUserMap.computeIfAbsent(view.getItemId(), k -> new HashSet<>())
.add(view.getUserId());
}

// 4. 计算物品相似度和推荐得分
// 存储每个候选物品的最终得分
Map<Long, Double> itemScores = new HashMap<>();
// 获取用户已浏览过的物品ID集合,用于排除推荐
Set<Long> userViewedItemIds = userViews.stream()
.map(UserBehavior::getItemId)
.collect(Collectors.toSet());

// 遍历用户的每个浏览记录
for (UserBehavior userView : userViews) {
// 获取 浏览该物品的 所有用户
Set<Long> itemUsers = itemUserMap.get(userView.getItemId());
if (itemUsers == null) continue;

// 遍历所有物品,计算与当前浏览物品的相似度
for (Map.Entry<Long, Set<Long>> entry : itemUserMap.entrySet()) {
Long otherItemId = entry.getKey();
// 该用户浏览过了这个商品id, 直接排除
if (userViewedItemIds.contains(otherItemId)) continue;
// 浏览过这个商品的 其他用户
Set<Long> otherItemUsers = entry.getValue();
// 使用Jaccard相似度计算物品相似度
// Jaccard相似度 = 两个集合的交集大小 / 并集大小
//完整代码请见:gitcode巅抗目/hadluo2/springboot_vue.git
            double similarity = calculateJaccardSimilarity(itemUsers, otherItemUsers);

// 考虑时间衰减因素 相当于一个优化因子
// 浏览时间越近,权重越大(1/1, 1/2, 1/3, ...)
//完整代码请见:gitcode巅抗目/hadluo2/springboot_vue.git
            // 累加相似度得分(考虑时间权重)
itemScores.merge(otherItemId, similarity * timeWeight, Double::sum);
}
}

// 5. 生成推荐结果
// 对候选物品按得分降序排序,选择前N个
return itemScores.entrySet().stream()
.sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
.limit(numRecommendations)
.map(e -> itemRepository.selectById(e.getKey()))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
 

 代码只贴了部分,原理已经教给大家。

 

posted @ 2025-05-04 12:03  qq3993387644  阅读(93)  评论(0)    收藏  举报