「从零搭建」用 SpringBoot + 向量搜索打造智能短视频推荐系统!

「从零搭建」用 SpringBoot + 向量搜索打造智能短视频推荐系统!

编程文章jaq1232025-10-19 6:07:5911A+A-



在短视频内容爆炸的时代,如何让用户在海量视频中快速看到“自己想看的内容”,成为推荐系统的核心问题。 传统基于规则或协同过滤的推荐方式,已无法满足实时性与语义理解需求。

于是,向量搜索(Vector Search) 结合深度语义向量嵌入(Embedding)成为主流解决方案。 本文将通过 Spring Boot + Milvus/PGVector + OpenAI Embedding + Thymeleaf + Bootstrap,构建一个可落地的短视频语义推荐系统,实现以下目标:

  • 视频元数据存储与语义向量嵌入;
  • 用户输入搜索语句时,自动生成向量并进行相似度检索;
  • 实时返回语义最相近的短视频内容;
  • 前端动态展示推荐结果。

项目结构设计

springboot-vector-recommend/
├── src/
│   ├── main/
│   │   ├── java/com/icoderoad/recommend/
│   │   │   ├── controller/
│   │   │   │   └── VideoController.java
│   │   │   ├── service/
│   │   │   │   └── VideoService.java
│   │   │   ├── model/
│   │   │   │   └── Video.java
│   │   │   └── util/
│   │   │       └── EmbeddingUtil.java
│   │   ├── resources/
│   │   │   ├── templates/
│   │   │   │   └── recommend.html
│   │   │   └── application.yml
├── pom.xml

依赖配置(pom.xml)

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.icoderoad</groupId>
    <artifactId>springboot-vector-recommend</artifactId>
    <version>1.0.0</version>


    <dependencies>
        <!-- Spring Boot 基础依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <!-- Thymeleaf 模板引擎 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>


        <!-- PostgreSQL + pgvector 支持 -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>


        <!-- MyBatis-Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.5</version>
        </dependency>


        <!-- OpenAI Embedding 工具 -->
        <dependency>
            <groupId>com.theokanning.openai-gpt3-java</groupId>
            <artifactId>client</artifactId>
            <version>0.17.1</version>
        </dependency>


        <!-- Fastjson 解析工具 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.34</version>
        </dependency>
    </dependencies>
</project>

application.yml 配置

server:
  port: 8080


spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/video_recommend
    username: postgres
    password: 123456
    driver-class-name: org.postgresql.Driver


openai:
  api-key: sk-xxxxxx  # 替换为你自己的OpenAI密钥

数据库表结构(pgvector)

执行 SQL:

CREATE EXTENSION IF NOT EXISTS vector;


CREATE TABLE video (
  id SERIAL PRIMARY KEY,
  title VARCHAR(255),
  description TEXT,
  url VARCHAR(255),
  embedding vector(1536)  -- 存储 OpenAI Embedding 向量
);

Embedding 工具类

package com.icoderoad.recommend.util;


import com.theokanning.openai.embedding.EmbeddingRequest;
import com.theokanning.openai.embedding.EmbeddingResult;
import com.theokanning.openai.service.OpenAiService;
import java.util.List;


public class EmbeddingUtil {
    private static final String MODEL = "text-embedding-3-small";
    private static final OpenAiService service = new OpenAiService(System.getenv("OPENAI_API_KEY"));


    // 生成文本向量
    public static List<Float> getEmbedding(String text) {
        EmbeddingRequest request = EmbeddingRequest.builder()
                .input(List.of(text))
                .model(MODEL)
                .build();
        EmbeddingResult result = service.createEmbeddings(request);
        return result.getData().get(0).getEmbedding();
    }
}

后端推荐服务逻辑

package com.icoderoad.recommend.service;


import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.icoderoad.recommend.model.Video;
import com.icoderoad.recommend.util.EmbeddingUtil;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;


@Service
public class VideoService {


    @Resource
    private VideoMapper videoMapper;


    /**
     * 基于语义搜索的相似视频推荐
     */
    public List<Video> recommendByText(String query) {
        List<Float> embedding = EmbeddingUtil.getEmbedding(query);
        String vectorString = embedding.toString().replace("[", "(").replace("]", ")");


        // 使用 PGVector 的相似度查询(<-> 表示余弦距离)
        String sql = "SELECT * FROM video ORDER BY embedding <-> '" + vectorString + "' LIMIT 10";
        return videoMapper.selectBySql(sql);
    }
}

Controller 层接口

package com.icoderoad.recommend.controller;


import com.icoderoad.recommend.model.Video;
import com.icoderoad.recommend.service.VideoService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;


import java.util.List;


@Controller
public class VideoController {


    private final VideoService videoService;


    public VideoController(VideoService videoService) {
        this.videoService = videoService;
    }


    @GetMapping("/")
    public String index() {
        return "recommend";
    }


    @PostMapping("/recommend")
    public String recommend(@RequestParam("query") String query, Model model) {
        List<Video> results = videoService.recommendByText(query);
        model.addAttribute("query", query);
        model.addAttribute("videos", results);
        return "recommend";
    }
}

前端展示部分

(Thymeleaf + Bootstrap 实现推荐结果列表页)

文件:
src/main/resources/templates/recommend.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="zh">
<head>
    <meta charset="UTF-8">
    <title>短视频推荐系统</title>
    <link rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
    <style>
        body {
            background-color: #f8f9fa;
        }
        .video-card {
            border-radius: 12px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
            transition: transform 0.2s;
        }
        .video-card:hover {
            transform: scale(1.02);
        }
        .video-title {
            font-weight: 600;
            color: #333;
        }
        .video-desc {
            color: #666;
            font-size: 0.9rem;
        }
    </style>
</head>


<body>
<div class="container py-5">
    <div class="text-center mb-4">
        <h2 class="fw-bold">短视频智能推荐系统</h2>
        <p class="text-muted">输入关键词,看看你会喜欢哪些视频</p>
    </div>


    <!-- 搜索框 -->
    <form method="post" th:action="@{/recommend}" class="d-flex justify-content-center mb-5">
        <input type="text" name="query" class="form-control w-50 me-2" placeholder="输入你的兴趣,如 '旅行' 或 '美食'"
               th:value="${query}">
        <button class="btn btn-primary px-4" type="submit">推荐一下</button>
    </form>


    <!-- 推荐结果列表 -->
    <div class="row" th:if="${videos != null}">
        <div th:each="v : ${videos}" class="col-md-4 mb-4">
            <div class="card video-card">
                <iframe th:src="${v.url}" class="card-img-top" height="200" allowfullscreen></iframe>
                <div class="card-body">
                    <h5 class="video-title" th:text="${v.title}"></h5>
                    <p class="video-desc" th:text="${v.description}"></p>
                </div>
            </div>
        </div>
    </div>


    <div th:if="${videos == null}" class="text-center text-muted mt-5">
        <p>输入关键词后将显示推荐结果</p>
    </div>
</div>


<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

运行效果

  1. 启动 Spring Boot 服务:
  2. mvn spring-boot:run
  3. 访问:http://localhost:8080
  4. 在输入框中输入关键词(如“音乐”、“健身”或“宠物”),点击推荐按钮;
  5. 页面将动态展示语义上相似的视频列表,并通过 Bootstrap 卡片美观呈现。

总结

通过本实战,我们完成了一个从 语义理解 → 向量检索 → 实时推荐 → 前端展示 的完整闭环系统。 它不仅适用于短视频推荐场景,也可轻松扩展至:

  • 新闻/文章语义检索
  • 音乐情绪推荐
  • 知识问答匹配

接下来你可以继续优化:

  • 将向量存储从 PostgreSQL 升级为 Milvus / Qdrant
  • 结合 ChatGPT Re-Ranker 提升结果精度;
  • 利用 Redis 缓存向量查询结果 提高响应速度。
点击这里复制本文地址 以上内容由jaq123整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

苍茫编程网 © All Rights Reserved.  蜀ICP备2024111239号-21