尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
前端直连LLM必踩2大致命坑!BFF中转流式SSE完整方案,一套代码解决密钥泄露+分片解析报错
前言做Vue AI对话页面90%新手会踩两个线上致命大坑前端直接请求DeepSeek接口API Key打包进前端抓包/查看源码直接泄露被盗刷高额账单纯前端手写SSE流式解析TCP网络分包截断JSON疯狂报parse错误、文字丢失、乱码很多教程只给前端极简demo完全不提安全风险也没有完整BFF中转层落地代码。本文带你搭建Vite Vue3 Express BFF后端完整流式对话架构读完你能学到BFF层是什么、为什么AI项目必须加中转层完整三层调用链路Vue前端 → Node BFF → DeepSeek大模型Express转发SSE流式响应标准写法处理二进制流透传优化版前端流式解析代码极简buffer容错逻辑Vite代理跨域配置解决前后端端口跨域问题全套可复制运行代码 高频踩坑清单直接上线使用一、先搞懂BFF中转层解决两大核心痛点1.1 安全痛点前端不能存放大模型API密钥Vite中VITE_开头环境变量会打包进前端产物任何人打开开发者工具、抓网络请求都能拿到完整Key存在被盗刷风险。BFF解决方案密钥只存在Node服务端.env前端永远看不到真实鉴权凭证。1.2 工程化痛点统一接口、降低前端复杂度没有BFF前端要处理二进制解码、分片缓存、异常捕获、接口切换逻辑代码臃肿拥有BFF统一封装LLM调用逻辑前端只需要请求本地/api/stream后续切换大模型、加限流、过滤敏感词只改后端前端零改动1.3 完整三层调用链路Vue页面(5173端口) → Vite代理转发 → Express BFF(3000端口) → DeepSeek官方API二、项目环境初始化2.1 安装依赖# Vue前端依赖npminstallvue# BFF后端依赖npminstallexpress dotenv2.2 环境变量区分前后端项目根目录.env.localBFF后端读取不会暴露前端DEEPSEEK_API_KEYsk-你的DeepSeek密钥前端无需存放任何LLM密钥彻底规避泄露风险2.3 vite.config.js 代理跨域配置关键import{defineConfig}fromviteimportvuefromvitejs/plugin-vueexportdefaultdefineConfig({plugins:[vue()],server:{proxy:{// 匹配前端/api开头请求转发到3000端口BFF服务/api:{target:http://127.0.0.1:3000,changeOrigin:true,rewrite:pathpath.replace(/^\/api/,)}}}})前端请求/api/streamVite自动转发为http://127.0.0.1:3000/stream消除跨域报错。三、完整BFF后端代码 server.jsExpress流式转发import*asdotenvfromdotenv;importexpressfromexpress;dotenv.config({path:[.env,.env.local]});constappexpress();constport3000;// 解析url参数app.use(express.urlencoded({extended:true}));// 基础测试路由app.get(/,(req,res){res.send(BFF服务运行正常访问 /api/stream 发起AI对话);});// SSE流式中转核心接口app.get(/stream,async(req,res){const{prompt}req.query;if(!prompt)returnres.status(400).send(prompt参数不能为空);constendpointhttps://api.deepseek.com/chat/completions;constapiKeyprocess.env.DEEPSEEK_API_KEY;constmodeldeepseek-v4-flash;// SSE标准响应头必须设置否则无法持续推送流res.setHeader(Content-Type,text/event-stream);res.setHeader(Cache-Control,no-cache);res.setHeader(Connection,keep-alive);res.flushHeaders();try{// 请求DeepSeek流式接口constllmResawaitfetch(endpoint,{method:POST,headers:{Authorization:Bearer${apiKey},Content-Type:application/json,},body:JSON.stringify({model,stream:true,messages:[{role:user,content:prompt}],}),});// 把大模型返回的二进制流直接透传给前端llmRes.body.pipe(res);// 客户端关闭连接时销毁请求避免资源占用req.on(close,(){llmRes.body.destroy();res.end();});}catch(error){console.error(LLM接口请求失败,error);res.write(data: {error:服务异常}\n\n);res.write(data: [DONE]\n\n);res.end();}});app.listen(port,(){console.log(BFF中转服务启动成功端口${port});});后端核心逻辑说明SSE响应头三件套告知浏览器保持长连接、禁用缓存持续接收数据流pipe(res)二进制流直接透传无需后端解析JSON性能更高密钥存放服务端环境变量前端完全无法获取监听客户端断开事件主动销毁流防止后台无效请求堆积四、Vue3前端完整代码 App.vue优化版流式解析script setup import { ref } from vue // 页面响应式状态 const question ref(讲一个中国龙的故事) const stream ref(true) // 是否开启打字机流式输出 const content ref() // 存储完整回答用于关闭流式时一次性渲染 let accumulatedText // 核心请求函数 async function update() { if (!question.value.trim()) return content.value accumulatedText // 请求本地BFF代理接口不会跨域、无密钥泄露 const url /api/stream?prompt${encodeURIComponent(question.value)} const response await fetch(url) const reader response.body.getReader() const decoder new TextDecoder(utf-8, { stream: true }) let buffer while (true) { const { done, value } await reader.read() if (done) break // 二进制转文本stream:true兼容跨分片中文 buffer decoder.decode(value, { stream: true }) // 按换行分割所有报文 const lines buffer.split(\n) // 最后一行大概率是不完整分片存入buffer下次拼接 buffer lines.pop() || // 逐行解析有效data报文 for (const line of lines) { if (!line.startsWith(data: )) continue const dataStr line.slice(6) // 流结束标识直接跳过 if (dataStr [DONE]) continue try { const json JSON.parse(dataStr) // 提取增量文字 const deltaText json.choices?.[0]?.delta?.content || accumulatedText deltaText // 流式开启则实时更新页面打字机效果 if (stream.value) { content.value accumulatedText } } catch (err) { // JSON解析失败直接忽略残缺片段已存入buffer continue } } } // 关闭流式全部接收完成后一次性渲染全文 if (!stream.value) { content.value accumulatedText } } /script template div classcontainer !-- 提问输入区 -- div label输入提问/label input classinput v-modelquestion placeholder输入你的问题 / button clickupdate提交AI问答/button /div !-- 流式开关 AI回答展示 -- div classoutput div stylemargin-bottom:8px; label开启Streaming流式打字机/label input typecheckbox v-modelstream / /div div stylewhite-space: pre-wrap;{{ content }}/div /div /div /template style scoped .container { display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start; height: 100vh; font-size: 0.85rem; padding: 20px; } .input { width: 300px; padding: 4px 8px; } .output { margin-top: 12px; min-height: 300px; width: 100%; text-align: left; line-height: 1.7; } button { padding: 4px 12px; margin-left: 8px; cursor: pointer; } /style前端流式优化点对比纯前端直连方案请求地址统一代理只访问本地/api/stream不暴露第三方LLM接口极简buffer容错分割后把最后一行残缺报文放回buffer逻辑更简洁稳定TextDecoder({stream:true})解决跨分片中文乱码问题分离accumulatedText完整缓存支持一键切换流式/一次性渲染增加输入空值拦截避免无效请求五、完整运行步骤新建.env.local填入DeepSeek密钥终端启动BFF后端服务nodeserver.js新开终端启动Vite前端npmrun dev打开页面输入提问勾选/取消Streaming测试两种输出模式六、开发高频踩坑清单必看坑1前端直接请求LLM接口密钥泄露被盗刷现象上线后账单暴涨开发者工具Network面板可见完整Authorization密钥解决方案全部请求走BFF中转密钥仅存Node服务端环境变量坑2BFF未设置SSE响应头流式一次性返回现象页面等待全部生成后才展示文字无逐字打字效果修复必须添加text/event-stream、no-cache、keep-alive三个响应头坑3Vite代理502跨域报错原因BFF后端未启动、端口不匹配、代理rewrite路径错误检查确认BFF运行在3000端口vite代理配置正确坑4TCP分包截断JSON控制台频繁parse报错根源网络分片把一条data: JSON拆成两段解决代码中buffer缓存末尾残缺行下一轮循环拼接完整再解析坑5中文跨分片出现乱码问号修复new TextDecoder(utf-8, { stream: true })开启流式解码坑6切换流式开关文字错乱、重复叠加优化每次请求重置accumulatedText完整缓存分开实时渲染与最终渲染逻辑七、两种架构方案对比方案前端直连LLMBFF中转架构本文方案密钥安全极低极易泄露极高密钥仅存服务端接口维护切换模型需修改全部前端代码仅修改后端前端无感知前端复杂度高需处理鉴权、流、异常低仅处理本地流式解析扩展能力无法加限流、敏感词过滤后端统一加权限、限流、校验线上风险盗刷、接口暴露安全可控生产推荐八、项目扩展优化方向增加请求限流后端引入express-rate-limit防止恶意刷接口POST传参替代URL拼接避免长prompt参数超长改用body传递提问中断生成搭配AbortController实现停止AI输出功能多轮对话扩展BFF接口接收完整messages数组支持连续聊天异常统一处理后端捕获401/429/500错误标准化错误报文返回前端Markdown渲染流接收完成后使用marked解析富文本格式九、总结生产环境绝对禁止前端直连大模型APIBFF中转层是安全底线杜绝密钥泄露盗刷SSE流式输出核心依靠二进制长连接buffer缓存是解决TCP分片截断的必备容错逻辑Vite代理转发解决前后端跨域Express通过pipe透传LLM二进制流性能最优整套架构分层清晰前端负责页面交互、BFF负责安全中转、大模型负责文本生成可直接落地商用AI对话产品。
RELATED

相关推荐

Gamma怎样用结构提示生成演示框架_Gamma用结构提示生成演示框架【结构提示】

Gamma怎样用结构提示生成演示框架_Gamma用结构提示生成演示框架【结构提示】

利用结构化提示能迅速弄出清晰的演示框架, 其一, 借助预定义模板输入涵盖引言、背景、核心技术等内容方向的提示, 其二, 运用分段式模块逐个生成引言、技术说明、案例与挑战分析再进行整合, 其三, 凭借“问题 - 原因 - 解决方案”等关键词触发内置结构模式来提升效率。具备AI智…

📅 2026/9/11 5:06:10
基于pytest的企业级多租户自动化测试实践

基于pytest的企业级多租户自动化测试实践

1. 企业级多租户测试体系概述 在当今SaaS服务盛行的时代,多租户架构已成为企业级应用的标配。这种架构允许单个应用实例服务多个客户(租户),每个租户的数据和配置相互隔离。作为质量保障的关键环节,自动化测试体系必须…

📅 2026/9/11 8:32:19
【C语言零基础】通关01: 初识C语言

【C语言零基础】通关01: 初识C语言

目录1. 初识C语言2. C语言的历史和辉煌2.1 历史2.2 C语言的活跃度3. 学习C语言需要什么工具?3.1 翻译工具:编译器链接器3.2 编译器的对比4.第一个C语言程序4.1 VS项目,源文件,头文件介绍4.2 第一个C语言函数4.3 main函数&#xff…

📅 2026/9/11 14:20:03
MORE NEWS

更多资讯

📰

3招搞定马云的演讲文本分析,面试性能优化不再虚

3招搞定马云的演讲文本分析,面试性能优化不再虚 上周二,一位刚毕业的学弟在群里哭诉,说大厂二面挂了。面试官问:“如果给你100万条用户评论,你怎么快速提取出‘马云的演讲’这类高频观点,还要保证响应时间低于200ms?”他愣了五秒,只憋出一句…

📰

Python随机森林文旅经济数据分析与预测实战拆解

简介:这是一份围绕“文旅现象对地方经济影响”的Python机器学习分析与预测项目,基于随机森林算法实现,面向毕业设计、课程设计或期末大作业场景,适合已掌握Python基础、希望实战机器学习的读者。资源压缩包约2.81MB,共…

📰

PyTorch高分遥感语义分割:GF2影像地物分类的U-Net实践与工程避坑

简介:面向遥感影像智能解译的PyTorch语义分割项目实践资源,基于高分遥感数据完成地物分类,适合算法工程师、科研人员及深度学习初学者参考。压缩包共858个文件、约548MB,含35个Python脚本,覆盖模型训练、膨胀预测、后处…

📰

基于深度学习的太阳能光伏板积灰识别:四分类图像任务实战

简介:这份资源是面向计算机相关专业学生与深度学习入门者的太阳能光伏板积灰识别实战项目,可用于毕业设计、课程设计或期末大作业,重点解决光伏板表面灰尘的自动分类识别问题。项目采用自制灰尘数据集完成四分类任务,方法上覆盖普…

📰

基于Chrome DevTools的前端埋点校验方案实践

1. 项目背景与痛点解析前端埋点校验一直是困扰开发者的高频痛点问题。在大型Web项目中,数据采集的准确性直接影响业务决策和产品优化方向。传统埋点验证方式通常需要反复查看网络请求、手动比对参数、甚至依赖后端日志配合,整个过程耗时费力且容易遗漏关…

📰

卫星轨道坐标系RTN、UNW、VVLH详解:定义、转换与工程避坑指南

1. 三种坐标系到底在解决什么问题第一次接触卫星轨道力学的人,看到 RTN、UNW、VVLH 这三个缩写,大概率会愣一下:不都是描述卫星姿态和位置的坐标系吗,为什么搞出这么多套?我当初也是这么想的,直到有一次做编…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬