从零构建一个AI流式对话应用:SSE + BFF + Vite 实战指南

当大模型遇见BFF,流式输出的体验提升不止一点点

写在前面

最近在做一个AI对话相关的项目,涉及到流式输出的场景。前端需要调用大模型API,但直接在前端调用会遇到两个问题:一是API Key暴露的安全风险,二是跨域问题。这篇文章就记录一下我是如何通过BFF层来解决这些问题的。

什么是BFF?

BFF(Backend For Frontend),直译过来就是"为前端服务的后端"。这个概念在微服务架构中很常见,但在大前端领域同样适用。

传统模式下,前端直接调用后端Java/Go服务提供的RESTful接口,实现数据的增删改查。但随着前端业务越来越复杂,特别是涉及到流式输出、二进制数据处理等场景时,直接让前端处理这些逻辑会让代码变得臃肿且难以维护。

于是我们可以在前端和后端之间加一层Node服务:

text

复制代码
前端(Vue/React) → Node(BFF) → 后端(Java/LLM)

这一层Node服务可以帮我们做很多事情:

  • 聚合多个接口的数据
  • 处理流式输出的解析逻辑
  • 隐藏敏感的API Key
  • 解决跨域问题

SSE(Server-Sent Events)是什么?

SSE是一种基于HTTP的服务器推送技术,允许服务器向客户端推送事件流。与WebSocket不同,SSE是单向的,只能由服务器向客户端发送数据。

在AI对话场景中,大模型通常以流式方式返回结果,SSE正是处理这种场景的理想方案。

项目结构一览

text

bash 复制代码
stream-bff/
├── src/
│   ├── App.vue          # 前端主组件
│   └── main.js          # Vue入口
├── server.mjs           # BFF后端服务(Express)
├── vite.config.js       # Vite配置(含代理)
├── .env.local           # 环境变量(API Key)
├── package.json         # 项目依赖
└── readme67.md          # 笔记文档

环境配置

1. 安装依赖

bash

csharp 复制代码
pnpm add express dotenv

2. 环境变量管理

.env.local中存放敏感信息:

env

ini 复制代码
VITE_DEEPSEEK_API_KEY=your_api_key_here

注意.env.local应该添加到.gitignore中,避免API Key泄露。

3. Vite代理配置

跨域问题的本质是浏览器的同源策略:只要域名、端口、协议任意一个不同,就会触发跨域限制。

vite.config.js中配置代理来解决跨域:

javascript

javascript 复制代码
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        secure: false,
        rewrite: (path) => path.replace(/^/api/, '')
      }
    }
  }
})

这样,前端请求/api/stream会被Vite代理转发到http://localhost:3000/stream,完美绕过了跨域问题。

BFF层实现

Express服务搭建

server.mjs是BFF层的核心:

javascript

javascript 复制代码
import * as dotenv from 'dotenv';
import express from 'express';

dotenv.config({
  path: ['.env.local', '.env']
});

const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.send('hello world');
});

// 流式输出接口
app.get('/stream', async (req, res) => {
  const { prompt } = req.query;
  const endpoint = 'https://api.deepseek.com/v1/chat/completions';
  
  try {
    const response = await fetch(endpoint, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.VITE_DEEPSEEK_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        model: 'deepseek-v4-flash',
        stream: true,
        messages: [{ role: 'user', content: prompt }]
      })
    });
    
    // response.body 是 ReadableStream
    // 这里可以进行流式数据的处理和转发
    console.log(response.body);
  } catch (err) {
    console.log(err);
  }
});

app.listen(port, () => {
  console.log(`服务器在${port}端口启动了`);
});

请求链路

text

bash 复制代码
前端:5173/api/stream 
  → Vite代理转发 
  → BFF:3000/stream 
  → DeepSeek API

前端实现

App.vue中的核心逻辑:

vue

xml 复制代码
<script setup>
import { ref } from 'vue';

const question = ref('');
const content = ref('');
const stream = ref(true);

const update = async () => {
  // 发送请求到BFF层
  fetch(`/api/stream?prompt=${question.value}`)
    .then(res => res.json())
    .then(data => {
      console.log(data);
    });
}
</script>

<template>
  <div class="container">
    <div>
      <label>输入:</label>
      <input class="input" v-model="question" />
      <button @click="update">提交</button>
    </div>
    <div class="output">
      <div>
        <label>Streaming</label>
        <input type="checkbox" v-model="stream" />
      </div>
      <div>{{ content }}</div>
    </div>
  </div>
</template>

<style>
.container {
  display: flex;
  flex-direction: column;
  align-items: start;
  justify-content: start;
  height: 100vh;
  font-size: 0.85rem;
}
.input {
  width: 200px;
}
.output {
  margin-top: 10px;
  min-height: 300px;
  width: 100%;
  text-align: left;
}
button {
  padding: 0 10px;
  margin-left: 6px;
}
</style>

启动服务

项目需要同时启动两个服务:

bash

bash 复制代码
# 终端1:启动Vite开发服务器(前端)
npm run dev

# 终端2:启动BFF服务(后端)
node server.mjs

踩坑记录

关于502错误

配置代理后如果遇到502,通常是因为:

  1. BFF服务没有启动(确认端口3000是否在监听)
  2. 代理的目标地址配置错误
  3. 目标服务返回了非预期的响应

关于API Key安全

前端代码中的环境变量只有在构建时才会被替换,如果直接在前端调用大模型API,用户可以通过查看源代码获取到API Key。通过BFF层转发,API Key只存在于服务端,安全性大大提高。

总结

通过BFF层的引入,我们实现了:

  1. 解决跨域问题 - Vite代理将请求转发到同源的BFF服务
  2. 隐藏敏感信息 - API Key存储在服务端,不会暴露给前端
  3. 简化前端逻辑 - 复杂的流式数据处理交给BFF层处理
  4. 架构更清晰 - 前端专注于UI交互,BFF层负责数据适配和转发

这种模式在微前端、大前端架构中非常实用,特别是当我们需要对接多个后端服务或第三方API时,BFF层的价值会更加明显。

相关推荐
ServBay17 小时前
AI Gateway 指南,如何用统一网关管理多模型 API
aigc·api·ai编程
花椒技术18 小时前
AI Coding 后半程:6 类 QA Skills 如何接住测试与发布?
单元测试·ai编程·测试
金金金__19 小时前
一篇文章掌握Superpowers~
ai编程
yunwei3719 小时前
实证研究:AI Agent 规则需要上下文与分层强制执行
linux·安全·ai编程
三声三视19 小时前
交互式用够了?用 Agent SDK 把 Claude 塞进 Python Web 服务
人工智能·python·ai·aigc·ai编程
is8120 小时前
编程小白也可以做项目,AI 辅助开发太香了
ai编程
kyriewen20 小时前
别再写useMemo了——2026年这5个React性能优化已经是反模式
前端·react.js·ai编程
赫文派20 小时前
K3 与 Kimi Code 实战:模型到终端 Agent
人工智能·ai编程
Patrick在香港21 小时前
Python实战:用数据分析拆解一场AI大会——WAIC 2026参展企业画像、议题演变与城市格局
人工智能·python·数据挖掘·数据分析·ai编程