资讯详情

Vue3接入通义千问:从流式SSE到Nginx反代与安全部署全攻略

📅 2026/10/15 1:33:26 | 华诺云谱 👁 阅读
Vue3接入通义千问:从流式SSE到Nginx反代与安全部署全攻略
简介面向Vue 3开发者这份资源演示了如何在前端项目中接入阿里通义千问聊天机器人并支持打包后部署到公网使用。资源包约7.51MB共2000个文件其中js、ts、mjs等脚本负责业务逻辑与API调用css、scss定义界面样式json、md提供配置参数和说明文档vue组件则展示了聊天窗口的消息发送、接收与渲染过程。目前已有309人学习下载。借助该示例开发者可以掌握Vue 3 Composition API配合响应式数据管理聊天状态使用Axios请求千问接口并通过Vite或Vue CLI完成构建部署同时资源包含了聊天组件的封装、密钥配置入口与安全注意事项能够帮助读者省去从零调研的麻烦让应用快速具备自然语言交互能力。代码结构清晰适合需要为项目增加AI对话服务的初中级前端工程师也可作为在现有项目中集成通义千问的二次开发基础。1. 一个 Vue3 聊天页面直连通义千问为什么能打包上公网却没人敢这么干vue3 调用阿里通义千问聊天例子听起来就是把一个聊天窗口接到大模型上按理说前端调用一个 HTTP 接口就行了。真正动手时才会发现本地开发一切顺利一打包部署到公网就翻车跨域拦截、密钥暴露、流式响应中断、部署服务器配置不对……这些坑一个接一个。这篇笔记基于我自己做过的模拟项目X和他人分享的经验把这整条链路——从通义千问接口选型、Vue3 里的流式调用代码到打包后部署公网必须处理的跨域与反向代理——完整走一遍给出能直接抄作业的命令和配置。适合已经会 Vue3 基础、想快速把大模型聊天接进自己的项目里并能上线演示的开发者如果你正在纠结密钥要不要放前端后面第五、六章会给你一条清晰的升级路径。2. 先把通义千问接入方式定下来API 兼容协议、密钥与最小验证命令2.1 通义千问的接入选型SDK、HTTP 直连还是兼容协议聊到调用通义千问目前摆在开发者面前的主要有三条路官方 HTTP 接口、官方 SDK、以及 OpenAI 兼容协议。在这三者之间做选择直接决定了你 Vue3 项目里要写多少代码、踩多少坑。官方 HTTP 接口是最直接的方式请求结构清晰返回格式固定适合只做一次性验证或者写后端服务。官方 SDKPython/Java 等适合后后端语言栈但前端 JavaScript 的官方 SDK 生态相对不完整很多时候会发现它内部还是包了一层 HTTP 请求与其被这层封装限制不如自己写 fetch。我在实际项目里更推荐第三种OpenAI 兼容协议。原因有两个第一它和前端生态里的各种现成库、示例代码天然兼容遇到问题能搜到的资料量完全不是一个数量级第二通义千问的这个兼容层本身就是为了迁移场景设计的请求结构和响应格式都足够清晰。在 Vue3 里最普通的 fetch 就能完成调用不需要额外引入重量级 SDK。需要注意的是不管是哪种方式本质上都要走 HTTPS 请求到阿里云百炼的网关密钥用的都是 DashScope API Key。前端直连场景下这个 key 最终一定会暴露在浏览器里这点我后面专门用一章来聊安全和规避方案你现在只需记住这个迟早要面对的前提。2.2 用一条 curl 命令先验证连通性模型、密钥与参数在你写任何 Vue3 代码之前强烈建议先用一条 curl 命令把连通性和接口格式验证通了。这能帮你把问题在链路哪一段划分清楚——是本地网络问题、密钥无效、模型名写错还是参数格式不对。我自己的习惯是前端代码开工前至少先花十分钟把这条命令跑通。通义千问兼容协议的接口地址是https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions认证方式是 Bearer Token。下面是一条最基础的聊天非流式请求curl https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions \ -H Authorization: Bearer $DASHSCOPE_API_KEY \ -H Content-Type: application/json \ -d { model: qwen-plus, messages: [ {role: system, content: 你是一个乐于助人的助手。}, {role: user, content: 用一句话介绍你自己} ], stream: false }这里有几个参数需要说明。model字段qwen-plus 是均衡型模型日常聊天和演示够用追求极致速度用 qwen-turbo需要更强推理能力用 qwen-max最终效果差异可以通过调整 model 来对比。messages是对话历史数组role 有 system、user、assistant 三种前端在做多轮对话时必须把历史消息一并传上去否则模型没有任何上下文记忆。stream在验证连通性时先设 false拿到完整的 JSON 响应最为直观等后端链路确认没问题了再改成 true 做流式。如果你用的是 Windows 环境curl 命令同样可用Win10 自带但环境变量$DASHSCOPE_API_KEY的语法不同更稳妥的做法是直接把 key 字符串粘贴到命令里替换。不过我仍然建议设置环境变量因为后面所有示例都会引用这个变量少一点复制粘贴就少一点密钥泄露的几率。正常响应会返回一个 choices 数组其中 message.content 就是模型生成的全文。如果密钥无效返回 401模型名拼错返回 404 或类似 InvalidParameter 的错误信息如果触发内容安全策略会有专门的错误码。这些信息足够帮助你完成最基础的排查了。2.3 非流式与流式响应的数据结构前端解析前必须先知道的事用一次非流式请求确认连通性之后还要看一遍流式返回的数据格式因为前端聊天体验必须用流式它决定了用户是盯着一个光标干等几秒还是能像 ChatGPT 一样看到字一个一个蹦出来。这两者的差异在前端代码里不是同一套循环逻辑。非流式stream: false的响应是一个完整的 JSON核心结构如下{ choices: [ { message: { role: assistant, content: 你好我是通义千问... }, finish_reason: stop } ], usage: { prompt_tokens: 20, completion_tokens: 30, total_tokens: 50 } }而流式stream: true的响应是 SSE 格式即一段段以data:为前缀的文本块每个块里只有增量内容最后以一个特殊的data: [DONE]结束。每个分块的 JSON 结构里choices[0].delta.content保存的是这一小段新增的文本。前端要做的事就是把这些 delta 一段一段拼接起来。这两个格式的差异直接决定了你前端代码里的解析逻辑长什么样。下面第三章就是基于流式格式来写的 Vue3 调用实现。如果你为图省事在公网环境用非流式用户体验会很差——你想想一个聊天框转圈三秒钟才整段弹出文字跟逐字输出给人的活的感觉差太多了。3. 在 Vue3 里写聊天调用fetch 流式读取、安全 header 与组件拆分3.1 为什么用 fetch 而不是 Axios 来处理 SSE 流式输出确定对接协议和数据结构之后进入 Vue3 代码实现。很多同学第一反应是用 axios因为项目里到处都是它。但针对流式聊天这个场景我更推荐用浏览器原生 fetch原因说得直白一点axios 的底层 XHR 在处理 SSE 流式响应时事件模型和 parse 方式都不如 fetch 的 ReadableStream 那么直观可控。fetch 配合response.body.getReader()可以拿到一个真正的流读取器每一块数据到达就立刻处理天然契合 SSE。axios 处理流式需要额外依赖onDownloadProgress之类的 hack还要注意它可能在响应未结束时对数据做 JSON.parse 导致报错。所以不要在 Vue3 聊天功能里用 axios 做流式读取——这是我在踩过一次坑之后给你的直接结论。以下是这个方案的核心技术栈Vue3 组合式 APIscript setup语法、fetch 原生请求、SSE 增量解析。后端接口固定为通义千问兼容协议不做多余封装目标是让代码在本地开发环境和部署后的生产环境都能用同一套逻辑跑通。3.2 最小可跑通的调用代码流式拼接、中断与错误处理下面是一个封装好的流式聊天请求函数可以直接放进你的src/api/chat.js里。它负责发起请求、逐段解析 SSE 增量、通过回调把文本实时传给页面同时处理错误和中断export async function streamChat({ messages, apiKey, model, onMessage, onDone, onError, signal }) { const url https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions const response await fetch(url, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${apiKey} }, body: JSON.stringify({ model: model || qwen-plus, messages, stream: true }), signal }) if (!response.ok) { const errText await response.text() throw new Error(请求失败${response.status}${errText}) } const reader response.body.getReader() const decoder new TextDecoder(utf-8) let buffer while (true) { const { done, value } await reader.read() if (done) break buffer decoder.decode(value, { stream: true }) const lines buffer.split(\n) buffer lines.pop() || for (const line of lines) { const trimmed line.trim() if (!trimmed.startsWith(data:)) continue const data trimmed.slice(5).trim() if (data [DONE]) { onDone onDone() return } try { const json JSON.parse(data) const delta json.choices[0]?.delta?.content || if (delta) onMessage onMessage(delta) } catch (e) { // 遇到解析失败的分片留到下一轮缓冲区再处理 buffer line \n buffer } } } }这段代码里有三个细节值得展开讲。第一TextDecoder(utf-8)配合{ stream: true }是为了处理多字节字符被截断在分块边界的问题——比如一个中文字符的 UTF-8 编码可能横跨两个分块如果不用这个方式解码就会出现乱码。第二buffer 机制是把不完整的行暂存到下一轮拼接因为网络分块不保证正好按\n切开。第三signal 参数透传给 fetch让外部组件可以随时中断请求。在 Vue3 组件里调用这个函数的方式很直接。下面是一个最小示例import { ref } from vue import { streamChat } from ../api/chat const inputText ref() const chatContent ref() const isLoading ref(false) let abortController null async function sendMessage() { if (!inputText.value.trim() || isLoading.value) return const userMsg inputText.value chatContent.value inputText.value isLoading.value true abortController new AbortController() try { await streamChat({ messages: [ { role: system, content: 你是一个有帮助的AI助手。 }, { role: user, content: userMsg } ], apiKey: import.meta.env.VITE_DASHSCOPE_API_KEY, model: qwen-plus, signal: abortController.signal, onMessage: (delta) { chatContent.value delta }, onDone: () { isLoading.value false } }) } catch (err) { if (err.name AbortError) { console.log(请求已手动中断) } else { console.error(聊天请求失败, err) } isLoading.value false } } function stopChat() { if (abortController) { abortController.abort() } }这里import.meta.env.VITE_DASHSCOPE_API_KEY是从环境变量读取密钥配置文件在项目根目录的.env或者.env.development里。注意 Vite 项目环境变量必须以VITE_为前缀才会暴露给前端代码所以密钥文件一定要加进.gitignore避免提交到仓库后被别人看到。密钥的常见获取方式是在阿里云百炼控制台里申请 API Key拿到的是sk-开头的一串字符这些信息记得不要写死在代码里。3.3 一个 Vue3 聊天页面的配套组件结构从 input 到渲染有了请求封装和发送函数还缺一个完整的组件结构把聊天页面组织起来。下面是一个实际可用的单文件组件涵盖了消息列表渲染、输入框、发送和中断按钮template div classchat-container div classchat-messages div v-for(msg, index) in messageList :keyindex classmessage-row :classmsg.role div classmessage-bubble{{ msg.content }}/div /div /div div classchat-input-area textarea v-modelinputText placeholder请输入你的问题Enter 发送ShiftEnter 换行 keydown.enter.preventhandleEnter /textarea button clicksendMessage :disabledisLoading发送/button button v-ifisLoading clickstopChat停止/button /div /div /template script setup import { ref } from vue const inputText ref() const messageList ref([]) const isLoading ref(false) function handleEnter(e) { if (e.shiftKey) return sendMessage() } async function sendMessage() { // 调用 streamChat 的完整逻辑见上一小节 } /script这个组件结构里messageList是渲染的核心数据源每次发送时 push 一条 user 消息然后建立一个空的 assistant 消息占位流式回调里不断往这个占位消息的 content 上追加文本。这种方式能保持 Vue 的响应式更新按最小粒度触发不会因为频繁修改整个列表而卡顿。这里有一个非常值得养成习惯的细节输入框用keydown.enter.prevent做发送拦截同时判断e.shiftKey让换行不受干扰。这个小细节在用户体验上相当重要否则用户按 ShiftEnter 本来想换行结果也把消息发出去了或者按 Enter 时不是发送而是触发表单默认行为导致页面刷新。这类用户体验细节第一次写可能注意不到但做一两次聊天类项目就会很敏感。4. 打包公网部署的硬骨头跨域、构建配置与 Nginx 转发4.1 前端直连通义千问的跨域限制为什么本地好好的部署到线上就崩整个方案最容易让新手崩溃的环节就是本地跑得好好的打包部署到公网后浏览器一把请求拦住控制台报 CORS error。本质原因很简单你的前端页面运行在https://your-domain.com而通义千问接口在https://dashscope.aliyuncs.com这属于跨域请求。浏览器跨域策略会先发一个 OPTIONS 预检请求服务器需要返回对应的 CORS 头才能放行。但阿里云百炼的公网网关在实践上默认不会对你的域名开放跨域白名单所以在部署环境直接 fetch基本必挂。有些同学会说那本地怎么不挂呢——因为 Vite 开发服务器帮你做了代理浏览器看到的请求是同源的。做公网部署时你会面对一个残酷的事实单纯改前端配置解决不了生产环境的跨域必须在服务端加一层转发。4.2 Vite 构建配置代理只在开发期有效公网部署要换方案Vite 开发服务器代理是本地调试的标配。在vite.config.js里这样配置export default defineConfig({ server: { proxy: { /api: { target: https://dashscope.aliyuncs.com, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, /compatible-mode) } } } })这个配置的含义是前端请求/api/v1/chat/completions时Vite 把它代理到https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions。changeOrigin: true让请求头里的 Host 变成目标域名从而通过服务端的域名校验。但请记住这个代理只在开发环境有效。当你执行npm run build之后产出的是纯静态文件不再有 Vite 服务器的代理能力线上所有的请求都直接由浏览器发出。所以生产环境的跨域问题必须在真正的 Web 服务器如 Nginx上解决而不是在 Vite 配置里想办法。这是跳过构建产物、直接改 dev server 配置最容易混淆的地方也是很多新手部署后一脸茫然的根源。4.3 Nginx 部署方案静态资源托管与 API 反代一份能抄的配置公网部署的常见做法是前端构建产物丢给 Nginx 托管静态资源同时让 Nginx 把/api/路径的请求反向代理到通义千问接口。这样一来浏览器请求的是同源/api/...不存在跨域问题真正的跨域请求发生在 Nginx 服务器端而服务端到服务端的请求不受浏览器 CORS 策略约束。下面是一份可直接落地的 Nginx 配置文件核心片段server { listen 80; server_name your-domain.com; root /var/www/chat-app/dist; index index.html; # 前端路由Vue Router 的 history 模式需要这个配置 location / { try_files $uri $uri/ /index.html; } # API 反向代理到通义千问 location /api/ { proxy_pass https://dashscope.aliyuncs.com/compatible-mode/; proxy_set_header Host dashscope.aliyuncs.com; proxy_set_header Authorization $http_authorization; proxy_http_version 1.1; proxy_set_header Connection ; chunked_transfer_encoding off; proxy_buffering off; proxy_cache off; proxy_read_timeout 600s; } }逐条拆解几个关键配置。proxy_pass末尾的/compatible-mode/是带 URI 的写法含义是把/api/之后的路径拼接到目标地址后面所以前端请求/api/v1/chat/completions最终会到达https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions正好对应兼容协议的地址。proxy_set_header Authorization $http_authorization这段是把浏览器带来的 Authorization 头原样传给通义千问这样密钥仍然存在前端Nginx 不存储、不校验只是透传——这个方法应对演示够用但离安全还有距离后面第六章的处理可以再进一步。proxy_buffering off和proxy_cache off这两行是流式响应的命门。Nginx 默认会缓冲后端返回的数据导致前端在全部文本到达前看不到任何增量输出。只有关掉缓冲SSE 才能真正做到逐字推送。proxy_read_timeout 600s是防止长对话时 Nginx 在两次读取之间间隔太久而掐断连接具体数值可以根据你的实际对话长度调整。修改 Nginx 配置后别忘了执行nginx -t检查语法然后systemctl reload nginx重载配置。这里还有一个隐藏细节如果前端代码里请求地址写的是全量 URLhttps://dashscope.aliyuncs.com/...那 Nginx 的/api/转发配置根本不会生效必须把前端请求地址改成相对路径/api/v1/chat/completions。这里的坑就是本地开发靠 Vite 代理转发线上靠 Nginx 转发两处的路径规则要保持一致最省事的做法是请求封装时用相对路径。5. 打包公网最容易翻车的 5 个坑现象、原因与解决5.1 部署后请求返回 404明明本地开发一切正常现象本地运行 Vue3 项目一切正常npm run build后部署到 Nginx页面能打开但发消息时网络面板显示请求挂在某个路径上返回 404。原因最常见的是前端代码把请求地址写死了全量 URL导致请求根本没走到 Nginx 配置的/api/转发规则上另一个常见场景是proxy_pass的路径拼接规则没配对导致实际请求的路径里多了一段前缀或少了一段。解决统一前后端路径。前端请求地址一律使用/api/v1/chat/completions这种相对路径。检查 Nginx 的location /api/块和proxy_pass末尾是否带路径用curl http://localhost/api/v1/chat/completions在服务器本机验证一下路由是否通了。5.2 流式聊天首次返回正常后续乱码且速度变慢现象第一次发送消息文字逐字出现一切正常。第二三轮开始输出里偶尔出现个别乱码响应速度也明显变慢。原因前端代码里没有处理好 SSE 分块的行缓冲和 UTF-8 解码。一个中文字符的字节可能被 TCP 分块拆开如果直接用JSON.parse处理每个分块就会遇到半截字符导致解析失败进而丢块。速度变慢则可能是因为某个分块解析失败后把未读缓冲反复拼接造成了无效循环。解决严格按照 3.2 节里buffer decoder.decode(value, { stream: true })的模式来做行缓冲。注意 buffer 的作用是保存不完整的行而不是解析失败的行。如果发现某行确实解析失败它大概率分块边界问题配合 decoder 的流式解码模式一起使用乱码问题就能解决。5.3 API Key 写进打包产物源码一眼看光现象打包部署后打开浏览器开发者工具在 Sources 面板或者 Network 面板的请求 Header 里能直接看到完整的Authorization: Bearer sk-xxx。原因有的人把 key 直接写死在api/chat.js里或者在.env文件中没有加.gitignore或者把 key 通过import.meta.env.VITE_DASKSCOPE_API_KEY暴露出来。Vite 会把构建时存在于前端代码里的环境变量直接打进 bundle公网任何人只要看打包后的 JS 文件就能提取 key。解决这事的严重程度取决于你的用途。如果是个人演示项目、key 只按量计费且设置了预算上限还可以接受如果是正式产品必须在服务端做一层转发让 key 只存放在服务端环境变量里。具体做法在第六章展开。至少你要去阿里云百炼控制台给 key 设置额度上限并且养成密钥周期性更换的习惯。5.4 部署域名还是 HTTP浏览器直接拦截混合内容现象用 IP 地址直接访问部署的网站时页面加载正常但发消息时 Chrome 控制台提示 Mixed Content 错误请求在浏览器层直接被拦截。原因通义千问接口强制 HTTPS而你的网站走的是http://协议。浏览器默认不允许在一个 HTTPS 页面里发起 HTTP 请求反过来HTTP 页面里虽然可以请求 HTTPS但很多浏览器对HTTP 页面 HTTPS 接口 跨域的组合限制更严格实际表现就是不让你发出去。解决给部署域名启用 HTTPS用免费证书Lets Encrypt 或者云厂商的免费版配置到 Nginx 上然后把server_name改成域名并加一条从 HTTP 跳转到 HTTPS 的规则。对于临时用 IP 访问的场景最稳妥的做法是意识到这个限制的存在尽早配置域名和证书——这也是上公网演示的基本门槛。在快节奏的部署过程中这一步经常被当成细节跳过结果等正式演示时才暴露。5.5 云端安全组全开结果被刷爆账单现象把云服务器所有端口对外开放或者把安全组配置成了/0全放行。某天早上起来发现账单比平时高了很多查看监控发现 API 调用量暴增。原因如果 API Key 暴露在了前端且没有设置消费上限那公网上任何人都可以通过你的 key 调用模型。服务器端口全开只是一个诱因真正的漏斗在密钥上。更隐蔽的情况是云监控能看到服务器网络流量不大但模型调用量居高不下——这说明攻击者绕过你的静态站点直接拿着 key 在本地脚本里调用接口。解决两层防护一是密钥本身设置额度上限和异常告警二是把服务器安全组收敛为只放行 80/443 端口。在实际项目中我在第一次接触这个场景时就是先优化了安全组后来才在消费账单里发现问题尽量不要走我这条弯路——先设限额再开门。6. 把密钥藏到服务端的升级路径一个 Nginx 代理即可完成的改造6.1 前端只发对话把 key 留给代理去填前面章节的临时方案是浏览器携带 Authorization 头通过 Nginx 原样透传到通义千问。第六感的同学可能已经意识到那只是换了验证方式并没有藏住密钥——只要是浏览器里有这个 key就一定会被提取。真正做到可以安全上公网的路径应该是把密钥从浏览器代码里彻底移除让服务端帮你填。改造思路不复杂前端请求/api/chat时只带对话内容messages不带密钥。Nginx或者你的后端服务收到请求后把密钥从环境变量读取出来拼上 Authorization 头再转发到通义千问。密钥自始至终只存在于服务端。如果你没有独立后端用 Nginx 配合一个小脚本可能是最快的方案。但纯 Nginx 无法动态读取环境变量拼 Header所以更常见也是更可靠的方案是加一个轻量网关服务Node/Java/Python 都可以。我一般用的是一层 Node 服务原理如下// server.js —— 一个极简的 Node 转发层 import express from express import fetch from node-fetch const app express() app.use(express.json()) app.post(/api/chat, async (req, res) { const { messages, model } req.body const upstreamRes await fetch(https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${process.env.DASHSCOPE_API_KEY} }, body: JSON.stringify({ model: model || qwen-plus, messages, stream: true }) }) res.setHeader(Content-Type, text/event-stream) res.setHeader(Cache-Control, no-cache) res.setHeader(Connection, keep-alive) // 透传上游的流式响应 upstreamRes.body.pipe(res) }) app.listen(3000, () console.log(proxy server running on :3000))注意这段代码是示意性质生产环境要补全错误处理、请求超时、鉴权、限流等逻辑。这个服务部署在同一台服务器上然后 Nginx 的/api/只转发到http://127.0.0.1:3000即可。这样浏览器端拿不到密钥跨域问题也彻底消失——因为浏览器请求的是同源/api/Node 服务内部再去请求通义千问服务端到服务端没有 CORS 概念。6.2 改造后的完整调用链Dev 环境与生产环境分别怎么走改造后的架构需要理清两条链路。开发环境浏览器 → Vite Dev Server代理 → Node 转发服务 → 通义千问生产环境浏览器 → Nginx → Node 转发服务 → 通义千问。两条链路里浏览器都是只发消息不暴露密钥。Node 服务在哪运行都在同一台云服务器上密钥只存在于那台服务器的.env文件中。对比一下两种方案的取舍。用 Node 转发层多一个服务要维护、多一层故障点但换来的是密钥安全、限流可以自己做、前端代码和生产环境不需要在密钥管理上操心。如果你只是做个课程演示或者内部小工具Nginx 直连方案可能更省事但要清楚它是能跑与安全之间的选择。6.3 验证这套方案是否完善的三个检查点以及建议保留的习惯当你完成以上改造可以用三个检查点来判断方案是否真正完善。第一到打包后的产物 JS 文件里搜一下sk-确认找不到任何密钥字符串第二浏览器开发者工具的 Network 面板里请求头 Authorization 不再是Bearer sk-开头而是已经移除或改成了别的鉴权方式第三在服务器上直接执行curl https://dashscope.aliyuncs.com/... -H Authorization: Bearer $DASHSCOPE_API_KEY验证密钥仍在服务端环境变量里生效。最后分享一个小习惯给密钥设置消费上限。在阿里云百炼的控制台里给 API Key 设置每月消费额度外加异常告警。不用等方案做到第六层从第一天写代码起就可以先把这件事做了。还有一点是我实践中自己保存下来的做法每次构建部署时把.env文件单独管理绝不进入构建产物在 CI/CD 流程里直接注入。这个做法能少掉很多不必要的麻烦。这些检查点都过了你的 Vue3 调用通义千问的聊天应用才算真正达到了可以打包公网使用的标准——不只是能访问而是密钥、流式体验、部署链路都站得住。希望这篇笔记能帮你把这条路的坑都趟平让你少走几段我走过的弯路。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑