🔥 前端直连大模型?别慌!Vite + Fetch 教你优雅地“套娃”API,顺便把 Key 藏好!

5 阅读4分钟

🔥 前端直连大模型?别慌!Vite + Fetch 教你优雅地“套娃”API,顺便把 Key 藏好!

摘要:还在为前端调用 LLM 头疼?担心 API Key 裸奔被黑产扫号?本文手把手带你用 Vite 搭建全栈脚手架,利用 Fetch 发起复杂请求,并用 .env 环境变量给 Key 穿上“隐身衣”。拒绝硬编码,从你我做起!🚫🔑


😱 前言:当“Hello World”遇上“API Key 泄露”

兄弟们,大模型(LLM)火得一塌糊涂。你也想在自己的网页里接个 DeepSeek 或者 ChatGPT,让用户跟 AI 聊聊天。

于是你兴冲冲地写了这段代码:

// ❌ 危险操作!千万别这么干!
const apiKey = "sk-1234567890abcdef..."; // 直接写死在代码里
fetch('https://api.deepseek.com...', {
    headers: { 'Authorization': `Bearer ${apiKey}` }
});

然后你把代码推到了 GitHub。 三分钟后:你的邮箱炸了,信用卡账单多了几笔奇怪的消费,矿机正在你的 Key 上疯狂运转。💸

为什么? 因为前端代码是公开的!任何人在浏览器按个 F12,你的 Key 就像没穿衣服一样暴露在阳光下。

别怕!今天咱们就用 Vite环境变量,给这个 Key 穿上一件“防弹衣”,顺便把 Fetch 复杂请求讲得明明白白。


🛠️ 第一步:工程化起手式 —— Vite 脚手架

别再用原生 HTML/CSS/JS 手动创建文件了,那是石器时代的做法。咱们要用 Vite,快如闪电,配置简单。

1. 初始化项目

打开终端,敲下这行咒语:

npm create vite@latest my-llm-app -- --template vanilla
# 或者你想用 TS/React/Vue 也可以,这里为了演示原理,我们用原生 JS (vanilla)
cd my-llm-app
npm install

2. 配置 .env —— Key 的保险箱

在项目根目录下(和 package.json 同级),创建一个名为 .env 的文件。

⚠️ 重点来了(敲黑板): Vite 有一个特殊规定:只有以 VITE_ 开头的环境变量才会被暴露给客户端代码。 这是为了防止你不小心把数据库密码也传给了前端。

.env 文件内容:

# .env
# 这里的 VITE_ 前缀绝对不能少!
VITE_DEEPSEEK_API_KEY=sk-你的真实-Key-在这里

🔒 安全小贴士: 赶紧把 .env 加到 .gitignore 里!

echo ".env" >> .gitignore

这样,就算你手滑 push 了代码,Key 也不会上传到 GitHub。


🚀 第二步:Fetch 复杂请求 —— 像侦探一样拆解 HTTP

前端调用 LLM,本质上就是发一个 HTTP POST 请求。咱们把请求拆成三部分,像剥洋葱一样看清楚。

1. 请求行 (Request Line)

告诉服务器:我要干嘛?去哪?用什么协议?

  • Method: POST (因为我们要发送数据)
  • URL: https://api.deepseek.com/chat/completions (注意必须是 HTTPS,http 会被浏览器拦截)
  • Version: HTTP/1.1 (浏览器自动处理,不用管)

2. 请求头 (Headers) —— 身份证和通行证

  • Content-Type: application/json: 告诉服务器,“我发给你的是 JSON 格式的文本,别解析错了”。
  • Authorization: Bearer <Token>: 这是关键!格式固定为 Bearer 空格加上你的 Key。

3. 请求体 (Body) —— 真正的干货

浏览器不能直接发送 JS 对象 { role: 'user'... },必须序列化成字符串

  • 工具: JSON.stringify(payload)

💻 第三步:代码实战 —— 异步函数的优雅舞步

来,看看如何在 main.js (或你的入口文件) 中写出既健壮又优雅的代码。

// src/main.js (或者 main.ts)

// 🔴 1. 获取 Endpoint (必须是 HTTPS)
const endpoint = 'https://api.deepseek.com/chat/completions';

// 🔴 2. 读取环境变量
// Vite 通过 import.meta.env 访问 .env 中以 VITE_ 开头的变量
const apiKey = import.meta.env.VITE_DEEPSEEK_API_KEY;

if (!apiKey) {
    console.error('❌ 错误:未找到 API Key,请检查 .env 文件及 VITE_ 前缀');
    document.body.innerHTML = '<h1>配置错误:缺少 API Key</h1>';
    throw new Error('Missing API Key');
}

// 🔴 3. 构建请求头
const headers = {
    "Authorization": `Bearer ${apiKey}`, // 动态拼接 Token
    'Content-Type': 'application/json'   // 声明内容类型
};

// 🔴 4. 构建请求体 (Payload)
const payload = {
    model: 'deepseek-chat', // 确认模型名称正确
    messages: [
        {
            role: 'system', // System prompt 放前面,设定人设
            content: '你是一个幽默风趣的技术专家,擅长用比喻解释复杂概念。'
        },
        {
            role: 'user',
            content: '你好,DeepSeek!请用一句话解释什么是闭包。'
        }
    ],
    temperature: 0.7 // 可选参数,控制创造性
};

// 🔴 5. 发起请求 (Async/Await 语法糖,比 .then() 链式调用更易读)
async function callLLM() {
    const loadingEl = document.getElementById('loading');
    const responseEl = document.getElementById('response');
    
    if(loadingEl) loadingEl.style.display = 'block';
    if(responseEl) responseEl.textContent = '';

    try {
        // 发起 fetch 请求,返回一个 Promise
        const response = await fetch(endpoint, {
            method: 'POST',
            headers: headers,
            body: JSON.stringify(payload) // ⚠️ 必须 stringify!
        });

        // 🔴 6. 关键步骤:先检查 HTTP 状态码!
        // fetch 只有在网络故障时才 reject,404/500 等错误不会抛出异常,需要手动判断
        if (!response.ok) {
            // 尝试读取错误信息,方便调试
            const errorText = await response.text();
            console.error('❌ API 响应错误:', response.status, errorText);
            throw new Error(`请求失败:HTTP ${response.status}`);
        }

        // 解析 JSON 数据
        const data = await response.json();
        
        // 提取 AI 的回答
        const aiContent = data.choices[0].message.content;
        
        console.log('✅ 成功收到回复:', aiContent);
        
        if(responseEl) {
            responseEl.textContent = aiContent;
            // 简单的打字机效果可以加在这里
        }

    } catch (error) {
        console.error('❌ 请求过程出错:', error);
        if(responseEl) {
            responseEl.textContent = `出错了:${error.message}`;
            responseEl.style.color = 'red';
        }
    } finally {
        if(loadingEl) loadingEl.style.display = 'none';
    }
}

// 绑定按钮事件(假设你有个 <button id="sendBtn">)
document.getElementById('sendBtn')?.addEventListener('click', callLLM);

// 页面加载自动调用一次测试
// callLLM(); 

🧐 代码亮点分析(面试/装逼专用)

  1. import.meta.env: 这是 Vite 的魔法。它在构建时(Build Time)就把 .env 里的值替换进了代码里。用户在浏览器看到的源码里,Key 虽然还在,但至少你没把它明文写在 Git 仓库里。(注:严格来说,纯前端项目 Key 终究会暴露,生产环境建议加一层后端代理,后面会说)。
  2. async/await: 相比 .then().catch() 的回调地狱,这种写法像同步代码一样流畅,错误处理也用标准的 try/catch,维护性满分。
  3. !response.ok 检查: 很多新手直接 await response.json(),如果接口报 401 (Key 错了) 或 500,response.json() 可能会解析失败或者拿到错误对象导致后续代码崩溃。先判断状态码是成熟开发者的标志。
  4. JSON.stringify: 再次强调,HTTP 传输的是字符串流,JS 对象必须序列化。

🤔 灵魂拷问:这样 Key 就真的安全了吗?

真相时刻:😰 即使用了 .env,只要代码运行在用户的浏览器里,懂行的人打开 Network 面板或者查看打包后的 JS 文件,依然能看到那个 Key

.env 有啥用?

  1. 防止误提交:避免你把 Key 硬编码在代码里推送到 GitHub,被扫描机器人秒删账号。
  2. 环境隔离:开发环境用测试 Key,生产环境用正式 Key,一套代码多处运行。

🚨 生产环境最佳实践(进阶): 如果你要上线商业项目,千万不要让前端直接调 LLM API! 正确架构前端 -> 你的后端(Node/Go/Python) -> LLM 厂商 API

  • 前端只发请求给你的后端(不带 Key,或者带用户登录 Token)。
  • 后端在服务器内部保存 Key,转发请求给 DeepSeek。
  • 这样 Key 永远不出服务器,这才是真正的安全!

但在个人项目、原型验证、内网工具中,直接用 Vite + 前端调用是完全没问题的,效率最高!


📝 总结

  1. 工程化:用 npm create vite 快速起步,别手写 HTML 了。
  2. 藏 Key:创建 .env 文件,变量名必须加 VITE_ 前缀,并加入 .gitignore
  3. 发请求
    • URL 用 HTTPS。
    • Header 带上 Authorization: Bearer ...Content-Type: application/json
    • Body 用 JSON.stringify()
  4. 处理响应async/await 真香,记得先判断 response.ok

💬 互动话题: 你在使用大模型 API 时,遇到过最奇葩的报错是什么?是“余额不足”还是“由于您太聪明,被系统判定为机器人”? 欢迎在评论区分享你的“踩坑”经历!点赞最高的送虚拟后端代理服务一份(我会用意念帮你转发请求)!🐶

💬 建议实操: 我们是否可以通过AI agent 直接solo把前端调用llm的项目直接转化为全栈项目使得 vite 实现呢?

作者:你的稀土掘金写作家 标签:#前端 #LLM #DeepSeek #Vite #JavaScript #API集成 #避坑指南