🚀 构建 Spring AI + Milvus + Vue 全栈语义搜索 MVP

本文将引导你从零搭建一个最小可行产品(MVP):
前端使用 Vue 3 + TypeScript,后端基于 Spring Boot + Spring AI,向量数据库选用 Milvus,实现用户输入自然语言 → 后端生成嵌入向量 → 在 Milvus 中进行相似性检索 → 返回结果到前端。


🛠️ 第一步:环境准备

1. 安装 Node.js(前端依赖)

根据你的操作系统选择安装方式:

🪟 Windows
🍏 macOS(推荐 Homebrew)
# 安装 Homebrew(若未安装)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# 安装 Node.js
brew install node
🐧 Ubuntu/Debian(使用 NodeSource)
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs

建议:使用 nvm 管理多版本 Node(nvm-windows / nvm


2. 创建 Vue 3 前端项目(使用 Vite)

Vite 是现代、快速的构建工具,官方推荐用于 Vue 3。

# 创建项目(自动使用 Vite)
npm create vue@latest my-vue-app

# 按提示选择(示例):
# ✔ Add TypeScript? … Yes
# ✔ Add Vue Router? … Yes
# ✔ Add Pinia? … Yes
# ✔ Add ESLint / Prettier? … Yes

cd my-vue-app
npm install
npm run dev  # 启动开发服务器(默认 http://localhost:5173)

💡 推荐 VS Code + Volar 插件 获得最佳开发体验。


🗃️ 第二步:启动 Milvus 向量数据库

使用 Docker 快速部署 Milvus Standalone:

# 下载官方 docker-compose.yml
wget https://raw.githubusercontent.com/milvus-io/milvus/master/deployments/docker/standalone/docker-compose.yml

# 启动服务(后台运行)
docker-compose up -d

✅ 默认端口:19530(gRPC),确保该端口未被占用。

⚠️ 注意:你需要提前创建集合 demo_collection,包含字段:

  • vectorFLOAT_VECTOR(维度需匹配嵌入模型,如 OpenAI 的 text-embedding-ada-002 为 1536)
  • textVARCHAR(存储原始文本)

⚙️ 第三步:构建 Spring Boot 后端

项目结构

backend/
├── src/main/java/com/example/demo/
│   ├── DemoApplication.java
│   ├── controller/QueryController.java
│   ├── service/VectorSearchService.java
│   └── config/MilvusConfig.java
├── application.yml
└── pom.xml

1. pom.xml(关键依赖)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        <version>0.8.1</version>
    </dependency>
    <dependency>
        <groupId>io.milvus</groupId>
        <artifactId>milvus-sdk-java</artifactId>
        <version>2.4.5</version>
    </dependency>
</dependencies>

<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots><enabled>false</enabled></snapshots>
    </repository>
</repositories>

2. application.yml

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      embedding:
        options:
          model: text-embedding-ada-002

server:
  port: 8080

3. Milvus 客户端配置 (MilvusConfig.java)

@Configuration
public class MilvusConfig {
    @Bean
    public MilvusServiceClient milvusClient() {
        return new MilvusServiceClient(
            ConnectParam.newBuilder()
                .withHost("localhost")
                .withPort(19530)
                .build()
        );
    }
}

4. 向量搜索服务 (VectorSearchService.java)

@Service
public class VectorSearchService {

    @Autowired private EmbeddingModel embeddingModel;
    @Autowired private MilvusServiceClient milvusClient;

    private static final String COLLECTION_NAME = "demo_collection";

    public List<String> search(String query, int topK) {
        // 1. 生成嵌入向量
        Embedding embedding = embeddingModel.embed(query);
        List<Float> vector = embedding.getValues();

        // 2. Milvus 搜索
        SearchParam param = SearchParam.newBuilder()
            .withCollectionName(COLLECTION_NAME)
            .withMetricType(MetricType.COSINE)
            .withOutFields(List.of("text"))
            .withTopK(topK)
            .withVectors(List.of(vector))
            .withVectorFieldName("vector")
            .build();

        R<SearchResults> resp = milvusClient.search(param);
        if (resp.getStatus() != R.Status.Success) {
            throw new RuntimeException("Milvus search failed");
        }

        // 3. 提取文本结果
        return resp.getData().getResults().stream()
            .flatMap(r -> r.getFieldsDataList().stream())
            .filter(f -> "text".equals(f.getFieldName()))
            .map(f -> f.getScalars().getStringData().getData(0))
            .collect(Collectors.toList());
    }
}

5. REST 控制器 (QueryController.java)

@RestController
@RequestMapping("/api")
public class QueryController {

    @Autowired private VectorSearchService searchService;

    @PostMapping("/search")
    public List<String> search(@RequestBody Map<String, String> payload) {
        return searchService.search(payload.get("query"), 3);
    }
}

启动后端

cd backend
export OPENAI_API_KEY=your_openai_api_key_here
./mvnw spring-boot:run

🌐 第四步:开发 Vue 前端(含跨域代理)

1. 安装依赖

cd frontend
npm install axios

2. 配置 Vite 代理(解决开发阶段 CORS 问题)

编辑 vite.config.ts

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true
      }
    }
  }
});

3. 封装 API 请求 (src/api.ts)

import axios from 'axios';

const api = axios.create({ baseURL: '/api' });

export const search = (query: string) => {
  return api.post<string[]>('/search', { query });
};

4. 搜索组件 (src/components/SearchBox.vue)

<template>
  <div class="search-box">
    <input v-model="query" @keyup.enter="handleSearch" placeholder="Enter your query..." />
    <button @click="handleSearch">Search</button>
    <ul v-if="results.length">
      <li v-for="(result, i) in results" :key="i">{{ result }}</li>
    </ul>
    <p v-else-if="loading">Searching...</p>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { search } from '../api';

const query = ref('');
const results = ref<string[]>([]);
const loading = ref(false);

const handleSearch = async () => {
  if (!query.value.trim()) return;
  loading.value = true;
  try {
    const res = await search(query.value);
    results.value = res.data;
  } catch (error) {
    console.error('Search error:', error);
    results.value = [];
  } finally {
    loading.value = false;
  }
};
</script>

<style scoped>
.search-box { max-width: 600px; margin: 2rem auto; }
input, button { padding: 0.5rem; font-size: 1rem; }
ul { margin-top: 1rem; list-style: none; }
li { padding: 0.3rem 0; border-bottom: 1px solid #eee; }
</style>

5. 根组件 (src/App.vue)

<template>
  <div id="app">
    <h1>🔍 Spring AI + Milvus 语义搜索</h1>
    <SearchBox />
  </div>
</template>

<script setup lang="ts">
import SearchBox from './components/SearchBox.vue';
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

▶️ 启动整个项目

# 终端 1:启动 Milvus(已运行可跳过)
docker-compose up -d

# 终端 2:启动后端
cd backend
export OPENAI_API_KEY=sk-xxxxxx
./mvnw spring-boot:run

# 终端 3:启动前端
cd frontend
npm run dev

访问 👉 http://localhost:5173


🔒 安全与生产注意事项

项目 建议
OpenAI API Key 切勿硬编码!使用环境变量或密钥管理服务
CORS 生产环境应配置 Nginx 反向代理,而非依赖 Vite 代理
Milvus 集合 确保维度、索引类型(如 IVF_FLAT)与查询一致
错误处理 前后端均需完善异常捕获与用户提示

📦 项目结构总览

spring-ai-milvus-vue-mvp/
├── backend/              # Spring Boot + Spring AI
│   ├── src/main/java/...
│   ├── application.yml
│   └── pom.xml
│
└── frontend/             # Vue 3 + TypeScript + Vite
    ├── src/
    │   ├── components/SearchBox.vue
    │   ├── App.vue
    │   ├── main.ts
    │   └── api.ts
    ├── vite.config.ts
    ├── tsconfig.json
    └── package.json

恭喜!你已成功构建一个现代化的 AI 语义搜索应用 MVP。

🌟 下一步建议

  • 添加文档预处理与批量插入脚本(Python/Java)
  • 集成 LangChain 或 Spring AI 的 VectorStore 抽象
  • 部署到云平台(如 AWS ECS + RDS + S3)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐