「從零搭建」用 SpringBoot + 向量搜索打造智能短視頻推薦系統(tǒng)!實時推薦爽到飛起
在短視頻內(nèi)容爆炸的時代,如何讓用戶在海量視頻中快速看到“自己想看的內(nèi)容”,成為推薦系統(tǒng)的核心問題。 傳統(tǒng)基于規(guī)則或協(xié)同過濾的推薦方式,已無法滿足實時性與語義理解需求。
于是,向量搜索(Vector Search) 結(jié)合深度語義向量嵌入(Embedding)成為主流解決方案。 本文將通過 Spring Boot + Milvus/PGVector + OpenAI Embedding + Thymeleaf + Bootstrap,構(gòu)建一個可落地的短視頻語義推薦系統(tǒng),實現(xiàn)以下目標:
- 視頻元數(shù)據(jù)存儲與語義向量嵌入;
- 用戶輸入搜索語句時,自動生成向量并進行相似度檢索;
- 實時返回語義最相近的短視頻內(nèi)容;
- 前端動態(tài)展示推薦結(jié)果。
項目結(jié)構(gòu)設計
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密鑰數(shù)據(jù)庫表結(jié)構(gòu)(pgvector)
執(zhí)行 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 實現(xiàn)推薦結(jié)果列表頁)
文件:src/main/resources/templates/recommend.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="zh">
<head>
<meta charset="UTF-8">
<title>短視頻推薦系統(tǒng)</title>
<link rel="stylesheet"
>
<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">短視頻智能推薦系統(tǒng)</h2>
<p class="text-muted">輸入關(guān)鍵詞,看看你會喜歡哪些視頻</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>
<!-- 推薦結(jié)果列表 -->
<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>輸入關(guān)鍵詞后將顯示推薦結(jié)果</p>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>運行效果
- 啟動 Spring Boot 服務:
mvn spring-boot:run- 訪問:http://localhost:8080
- 在輸入框中輸入關(guān)鍵詞(如“音樂”、“健身”或“寵物”),點擊推薦按鈕;
- 頁面將動態(tài)展示語義上相似的視頻列表,并通過 Bootstrap 卡片美觀呈現(xiàn)。
總結(jié)
通過本實戰(zhàn),我們完成了一個從 語義理解 → 向量檢索 → 實時推薦 → 前端展示 的完整閉環(huán)系統(tǒng)。 它不僅適用于短視頻推薦場景,也可輕松擴展至:
- 新聞/文章語義檢索
- 音樂情緒推薦
- 知識問答匹配
接下來你可以繼續(xù)優(yōu)化:
- 將向量存儲從 PostgreSQL 升級為 Milvus / Qdrant;
- 結(jié)合 ChatGPT Re-Ranker 提升結(jié)果精度;
- 利用 Redis 緩存向量查詢結(jié)果 提高響應速度。
































