技术点

协同过滤算法分为基于用户的协同过滤(UserCF)和基于物品的协同过滤(ItemCF)。Spring Boot音乐推荐系统通常采用ItemCF,核心是通过用户行为计算物品相似度。技术栈包括:

  • Spring Boot: 后端框架,提供RESTful API
  • Spring Data JPA/Hibernate: 数据库ORM
  • MySQL/Redis: 存储用户行为数据与缓存相似度矩阵
  • Slope One/余弦相似度: 物品相似度计算算法
  • JUnit/Mockito: 单元测试与集成测试

功能设计

用户模块
  • 注册/登录:JWT认证
  • 行为采集:播放、收藏、评分记录
  • 偏好标签:用户画像生成
推荐模块
  • 实时推荐:基于最近行为
  • 离线推荐:定时计算相似度矩阵
  • 混合推荐:结合热榜与CF结果
管理模块
  • 歌曲CRUD:元数据管理
  • 用户行为分析:Dashboard展示

核心代码实现

相似度计算
// ItemCF核心算法
public Map<Long, Map<Long, Double>> calculateItemSimilarities(List<UserBehavior> behaviors) {
    // 1. 构建用户-物品倒排表
    Map<Long, List<Long>> userItemMap = behaviors.stream()
        .collect(Collectors.groupingBy(
            UserBehavior::getUserId,
            Collectors.mapping(UserBehavior::getSongId, Collectors.toList())
        ));

    // 2. 计算共现矩阵
    Map<Long, Map<Long, AtomicInteger>> coOccurrenceMatrix = new HashMap<>();
    userItemMap.values().forEach(itemList -> {
        for (int i = 0; i < itemList.size(); i++) {
            coOccurrenceMatrix.computeIfAbsent(itemList.get(i), k -> new HashMap<>());
            for (int j = 0; j < itemList.size(); j++) {
                if (i == j) continue;
                coOccurrenceMatrix.get(itemList.get(i))
                    .computeIfAbsent(itemList.get(j), k -> new AtomicInteger(0))
                    .incrementAndGet();
            }
        }
    });

    // 3. 余弦相似度计算
    Map<Long, Map<Long, Double>> similarityMatrix = new HashMap<>();
    coOccurrenceMatrix.forEach((itemId, relatedItems) -> {
        relatedItems.forEach((relatedItemId, count) -> {
            double similarity = count.get() / Math.sqrt(
                userItemMap.values().stream().filter(list -> list.contains(itemId)).count() *
                userItemMap.values().stream().filter(list -> list.contains(relatedItemId)).count()
            );
            similarityMatrix.computeIfAbsent(itemId, k -> new HashMap<>())
                .put(relatedItemId, similarity);
        });
    });

    return similarityMatrix;
}

推荐服务
@Service
public class RecommendationService {
    @Cacheable(value = "recommendations", key = "#userId")
    public List<Song> recommendItems(Long userId) {
        // 1. 获取用户近期行为
        List<UserBehavior> behaviors = behaviorRepository.findTop10ByUserIdOrderByTimestampDesc(userId);

        // 2. 获取相似度矩阵
        Map<Long, Map<Long, Double>> simMatrix = similarityService.getSimilarityMatrix();

        // 3. 生成推荐候选集
        Map<Long, Double> candidateScores = new HashMap<>();
        behaviors.forEach(behavior -> {
            simMatrix.getOrDefault(behavior.getSongId(), Collections.emptyMap())
                .forEach((relatedSongId, similarity) -> {
                    candidateScores.merge(relatedSongId, similarity, Double::sum);
                });
        });

        // 4. 过滤已听过的歌曲并排序
        return candidateScores.entrySet().stream()
            .filter(e -> !behaviors.stream().map(UserBehavior::getSongId).collect(Collectors.toSet()).contains(e.getKey()))
            .sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
            .limit(10)
            .map(e -> songRepository.findById(e.getKey()).orElseThrow())
            .collect(Collectors.toList());
    }
}

数据库设计

主要表结构
CREATE TABLE `user` (
  `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
  `username` VARCHAR(50) UNIQUE,
  `password` VARCHAR(100)
);

CREATE TABLE `song` (
  `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
  `title` VARCHAR(100),
  `artist` VARCHAR(100),
  `genre` VARCHAR(50),
  `release_date` DATE
);

CREATE TABLE `user_behavior` (
  `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
  `user_id` BIGINT,
  `song_id` BIGINT,
  `behavior_type` ENUM('PLAY', 'LIKE', 'RATE'),
  `rating` TINYINT,
  `timestamp` DATETIME,
  FOREIGN KEY (`user_id`) REFERENCES `user`(`id`),
  FOREIGN KEY (`song_id`) REFERENCES `song`(`id`)
);

CREATE TABLE `similarity_matrix` (
  `item_id` BIGINT,
  `related_item_id` BIGINT,
  `similarity` DOUBLE,
  PRIMARY KEY (`item_id`, `related_item_id`),
  INDEX `idx_item` (`item_id`),
  INDEX `idx_related` (`related_item_id`)
);

系统测试点

单元测试
@Test
public void testSimilarityCalculation() {
    List<UserBehavior> mockBehaviors = Arrays.asList(
        new UserBehavior(1L, 101L, "PLAY", null, Instant.now()),
        new UserBehavior(1L, 102L, "PLAY", null, Instant.now()),
        new UserBehavior(2L, 101L, "LIKE", null, Instant.now()),
        new UserBehavior(2L, 103L, "PLAY", null, Instant.now())
    );

    Map<Long, Map<Long, Double>> result = recommendationAlgorithm.calculateItemSimilarities(mockBehaviors);
    assertEquals(0.5, result.get(101L).get(102L), 0.01);
}

集成测试
  1. 推荐准确性测试:验证推荐结果与用户历史行为的关联性
  2. 性能测试:确保相似度矩阵计算在10万级数据量下完成时间<5分钟
  3. 缓存测试:Redis缓存命中率应>90%
  4. 冷启动测试:新用户应收到热门歌曲推荐
  5. 压力测试:API在100并发下响应时间<500ms
监控指标
  • 推荐覆盖率:推荐歌曲占全库的比例
  • 点击通过率:推荐结果的用户点击比例
  • 多样性指数:推荐列表的风格分布熵值
  • 实时性延迟:行为发生到影响推荐的时间间隔

更多推荐