全栈Node.js开发:集成第三方AI提供商Token的完整指南

引言

在现代应用开发中,集成AI能力已成为提升产品竞争力的关键。本文将详细介绍如何在全栈Node.js应用中调用主流AI提供商(如OpenAI、Anthropic、Google Gemini等)的API token进行产品开发,涵盖从环境配置到前后端集成的完整流程。

一、项目初始化与配置

1.1 创建Node.js项目

bash 复制代码
mkdir ai-integration-app
cd ai-integration-app
npm init -y

1.2 安装核心依赖

bash 复制代码
npm install express dotenv axios cors
npm install --save-dev nodemon

1.3 项目结构设计

vbscript 复制代码
ai-integration-app/
├── .env
├── .gitignore
├── package.json
├── server/
│   ├── config/
│   │   └── aiConfig.js
│   ├── controllers/
│   │   └── aiController.js
│   ├── routes/
│   │   └── aiRoutes.js
│   ├── services/
│   │   └── aiService.js
│   └── server.js
├── client/
│   └── public/
│       ├── index.html
│       ├── css/
│       └── js/
└── scripts/
    └── test-api.js

二、AI提供商Token管理

2.1 获取API密钥

主流AI提供商获取token的方式:

2.2 安全存储配置

.env 文件示例:

env 复制代码
# OpenAI
OPENAI_API_KEY=your_openai_key_here
OPENAI_ORG_ID=your_org_id_here

# Anthropic
ANTHROPIC_API_KEY=your_anthropic_key_here

# Google Gemini
GEMINI_API_KEY=your_gemini_key_here

# Server
PORT=3000
CORS_ORIGIN=http://localhost:8080

2.3 配置管理模块

server/config/aiConfig.js:

javascript 复制代码
require('dotenv').config();

module.exports = {
  openai: {
    apiKey: process.env.OPENAI_API_KEY,
    organization: process.env.OPENAI_ORG_ID,
    baseUrl: 'https://api.openai.com/v1'
  },
  anthropic: {
    apiKey: process.env.ANTHROPIC_API_KEY,
    baseUrl: 'https://api.anthropic.com/v1'
  },
  gemini: {
    apiKey: process.env.GEMINI_API_KEY,
    baseUrl: 'https://generativelanguage.googleapis.com/v1beta'
  },
  // 添加其他提供商配置...
};

三、后端服务实现

3.1 创建AI服务层

server/services/aiService.js:

javascript 复制代码
const axios = require('axios');
const aiConfig = require('../config/aiConfig');

class AIService {
  constructor() {
    this.providers = {
      openai: this._createOpenAIClient(),
      anthropic: this._createAnthropicClient(),
      gemini: this._createGeminiClient()
    };
  }

  _createOpenAIClient() {
    return axios.create({
      baseURL: aiConfig.openai.baseUrl,
      headers: {
        'Authorization': `Bearer ${aiConfig.openai.apiKey}`,
        'OpenAI-Organization': aiConfig.openai.organization,
        'Content-Type': 'application/json'
      }
    });
  }

  _createAnthropicClient() {
    return axios.create({
      baseURL: aiConfig.anthropic.baseUrl,
      headers: {
        'x-api-key': aiConfig.anthropic.apiKey,
        'anthropic-version': '2023-06-01',
        'Content-Type': 'application/json'
      }
    });
  }

  _createGeminiClient() {
    return axios.create({
      baseURL: aiConfig.gemini.baseUrl,
      params: {
        key: aiConfig.gemini.apiKey
      },
      headers: {
        'Content-Type': 'application/json'
      }
    });
  }

  async chatCompletion(provider, payload) {
    try {
      let response;
      switch(provider.toLowerCase()) {
        case 'openai':
          response = await this.providers.openai.post('/chat/completions', payload);
          break;
        case 'anthropic':
          response = await this.providers.anthropic.post('/messages', payload);
          break;
        case 'gemini':
          response = await this.providers.gemini.post(`/models/gemini-pro:generateContent`, {
            contents: [{
              parts: [{
                text: payload.prompt
              }]
            }]
          });
          break;
        default:
          throw new Error('Unsupported AI provider');
      }
      return this._formatResponse(provider, response.data);
    } catch (error) {
      console.error(`AI API Error (${provider}):`, error.response?.data || error.message);
      throw new Error(`AI service error: ${error.message}`);
    }
  }

  _formatResponse(provider, data) {
    // 统一不同提供商的响应格式
    switch(provider.toLowerCase()) {
      case 'openai':
        return {
          text: data.choices[0].message.content,
          usage: data.usage
        };
      case 'anthropic':
        return {
          text: data.content[0].text,
          usage: data.usage
        };
      case 'gemini':
        return {
          text: data.candidates[0].content.parts[0].text,
          usage: null // Gemini目前不提供使用量数据
        };
      default:
        return data;
    }
  }
}

module.exports = new AIService();

3.2 创建控制器

server/controllers/aiController.js:

javascript 复制代码
const aiService = require('../services/aiService');

exports.chat = async (req, res) => {
  try {
    const { provider, message, model, temperature, maxTokens } = req.body;
    
    // 验证输入
    if (!provider || !message) {
      return res.status(400).json({ error: 'Provider and message are required' });
    }

    // 构建提供商特定的payload
    let payload;
    switch(provider.toLowerCase()) {
      case 'openai':
        payload = {
          model: model || 'gpt-3.5-turbo',
          messages: [{ role: 'user', content: message }],
          temperature: temperature || 0.7,
          max_tokens: maxTokens || 1000
        };
        break;
      case 'anthropic':
        payload = {
          model: model || 'claude-2.1',
          messages: [{ role: 'user', content: message }],
          max_tokens: maxTokens || 1000,
          temperature: temperature || 0.7
        };
        break;
      case 'gemini':
        payload = {
          prompt: message,
          // Gemini参数略有不同
          generationConfig: {
            temperature: temperature || 0.7,
            maxOutputTokens: maxTokens || 1000
          }
        };
        break;
      default:
        return res.status(400).json({ error: 'Unsupported provider' });
    }

    const result = await aiService.chatCompletion(provider, payload);
    res.json(result);
  } catch (error) {
    console.error('Chat error:', error);
    res.status(500).json({ error: error.message });
  }
};

exports.listModels = async (req, res) => {
  try {
    const { provider } = req.params;
    
    // 在实际应用中,你可能需要缓存这些模型列表
    const models = {
      openai: ['gpt-4', 'gpt-3.5-turbo', 'text-davinci-003'],
      anthropic: ['claude-2.1', 'claude-instant-1.2'],
      gemini: ['gemini-pro', 'gemini-ultra']
    };
    
    if (!models[provider]) {
      return res.status(404).json({ error: 'Provider not found' });
    }
    
    res.json(models[provider]);
  } catch (error) {
    console.error('List models error:', error);
    res.status(500).json({ error: error.message });
  }
};

3.3 创建路由

server/routes/aiRoutes.js:

javascript 复制代码
const express = require('express');
const router = express.Router();
const aiController = require('../controllers/aiController');

// 聊天接口
router.post('/chat', aiController.chat);

// 获取模型列表
router.get('/models/:provider', aiController.listModels);

module.exports = router;

3.4 创建Express服务器

server/server.js:

javascript 复制代码
const express = require('express');
const cors = require('cors');
const dotenv = require('dotenv');
const aiRoutes = require('./routes/aiRoutes');

dotenv.config();

const app = express();
const port = process.env.PORT || 3000;

// 中间件
app.use(cors({
  origin: process.env.CORS_ORIGIN || '*'
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 路由
app.use('/api/ai', aiRoutes);

// 健康检查
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'healthy' });
});

// 错误处理
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

四、前端集成

4.1 基本HTML结构

client/public/index.html:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>AI Integration Demo</title>
  <link rel="stylesheet" href="/css/styles.css">
</head>
<body>
  <div class="container">
    <h1>AI Chat Interface</h1>
    
    <div class="provider-selector">
      <label for="provider">AI Provider:</label>
      <select id="provider">
        <option value="openai">OpenAI</option>
        <option value="anthropic">Anthropic</option>
        <option value="gemini">Google Gemini</option>
      </select>
      
      <label for="model">Model:</label>
      <select id="model">
        <!-- 动态加载 -->
      </select>
    </div>
    
    <div class="chat-container">
      <div id="chat-history" class="chat-history"></div>
      
      <div class="input-area">
        <textarea id="user-input" placeholder="Type your message here..."></textarea>
        <button id="send-button">Send</button>
      </div>
    </div>
    
    <div class="settings">
      <label>
        Temperature:
        <input type="range" id="temperature" min="0" max="1" step="0.1" value="0.7">
        <span id="temp-value">0.7</span>
      </label>
      
      <label>
        Max Tokens:
        <input type="number" id="max-tokens" min="100" max="4000" value="1000">
      </label>
    </div>
  </div>
  
  <script src="/js/app.js"></script>
</body>
</html>

4.2 JavaScript交互逻辑

client/public/js/app.js:

javascript 复制代码
document.addEventListener('DOMContentLoaded', () => {
  const providerSelect = document.getElementById('provider');
  const modelSelect = document.getElementById('model');
  const chatHistory = document.getElementById('chat-history');
  const userInput = document.getElementById('user-input');
  const sendButton = document.getElementById('send-button');
  const temperatureSlider = document.getElementById('temperature');
  const tempValueDisplay = document.getElementById('temp-value');
  const maxTokensInput = document.getElementById('max-tokens');

  // 初始化模型列表
  providerSelect.addEventListener('change', async () => {
    const provider = providerSelect.value;
    try {
      const response = await fetch(`/api/ai/models/${provider}`);
      const models = await response.json();
      
      modelSelect.innerHTML = '';
      models.forEach(model => {
        const option = document.createElement('option');
        option.value = model;
        option.textContent = model;
        modelSelect.appendChild(option);
      });
    } catch (error) {
      console.error('Failed to load models:', error);
      alert('Failed to load models. Check console for details.');
    }
  });

  // 触发初始加载
  providerSelect.dispatchEvent(new Event('change'));

  // 温度滑块显示
  temperatureSlider.addEventListener('input', () => {
    tempValueDisplay.textContent = temperatureSlider.value;
  });

  // 发送消息
  sendButton.addEventListener('click', sendMessage);
  userInput.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      sendMessage();
    }
  });

  async function sendMessage() {
    const message = userInput.value.trim();
    if (!message) return;

    const provider = providerSelect.value;
    const model = modelSelect.value;
    const temperature = parseFloat(temperatureSlider.value);
    const maxTokens = parseInt(maxTokensInput.value);

    // 添加到聊天历史
    addMessageToHistory('user', message);
    userInput.value = '';

    try {
      // 显示加载状态
      const loadingId = addMessageToHistory('assistant', 'Thinking...', true);
      
      const response = await fetch('/api/ai/chat', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          provider,
          message,
          model,
          temperature,
          maxTokens
        })
      });

      const data = await response.json();
      
      // 更新加载中的消息
      updateMessageInHistory(loadingId, 'assistant', data.text);
    } catch (error) {
      console.error('Chat error:', error);
      addMessageToHistory('assistant', `Error: ${error.message}`);
    }
  }

  function addMessageToHistory(role, content, isLoading = false) {
    const messageId = `msg-${Date.now()}`;
    const messageElement = document.createElement('div');
    messageElement.id = messageId;
    messageElement.className = `message ${role} ${isLoading ? 'loading' : ''}`;
    messageElement.textContent = content;
    chatHistory.appendChild(messageElement);
    chatHistory.scrollTop = chatHistory.scrollHeight;
    return messageId;
  }

  function updateMessageInHistory(id, role, content) {
    const messageElement = document.getElementById(id);
    if (messageElement) {
      messageElement.className = `message ${role}`;
      messageElement.textContent = content;
    }
  }
});

4.3 基本样式

client/public/css/styles.css:

css 复制代码
body {
  font-family: Arial, sans-serif;
  line-height: 1.6;
  margin: 0;
  padding: 20px;
  background-color: #f5f5f5;
}

.container {
  max-width: 800px;
  margin: 0 auto;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.provider-selector {
  display: flex;
  gap: 15px;
  margin-bottom: 20px;
  align-items: center;
}

.chat-container {
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
}

.chat-history {
  height: 400px;
  overflow-y: auto;
  padding: 15px;
  background: #fafafa;
}

.message {
  margin-bottom: 15px;
  padding: 10px 15px;
  border-radius: 18px;
  max-width: 80%;
}

.message.user {
  background: #007bff;
  color: white;
  margin-left: auto;
  border-bottom-right-radius: 4px;
}

.message.assistant {
  background: #e9ecef;
  margin-right: auto;
  border-bottom-left-radius: 4px;
}

.message.loading {
  color: #666;
  font-style: italic;
}

.input-area {
  display: flex;
  border-top: 1px solid #ddd;
  padding: 10px;
  background: white;
}

.input-area textarea {
  flex: 1;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  resize: none;
  min-height: 60px;
}

.input-area button {
  margin-left: 10px;
  padding: 0 20px;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.input-area button:hover {
  background: #0056b3;
}

.settings {
  margin-top: 20px;
  padding: 15px;
  background: #f8f9fa;
  border-radius: 8px;
}

.settings label {
  display: block;
  margin-bottom: 10px;
}

.settings input[type="range"] {
  width: 200px;
  vertical-align: middle;
}

五、测试与优化

5.1 API测试脚本

scripts/test-api.js:

javascript 复制代码
const axios = require('axios');
const dotenv = require('dotenv');
dotenv.config();

const BASE_URL = 'http://localhost:3000/api/ai';

async function testChat(provider, message) {
  try {
    console.log(`Testing ${provider} with message: "${message}"`);
    
    const response = await axios.post(`${BASE_URL}/chat`, {
      provider,
      message,
      temperature: 0.7,
      maxTokens: 500
    });
    
    console.log('Response:', {
      provider,
      text: response.data.text,
      usage: response.data.usage
    });
  } catch (error) {
    console.error(`Error with ${provider}:`, error.response?.data || error.message);
  }
}

async function runTests() {
  const testMessage = "Explain the concept of artificial intelligence in simple terms";
  
  await testChat('openai', testMessage);
  await testChat('anthropic', testMessage);
  await testChat('gemini', testMessage);
}

runTests();

5.2 性能优化建议

  1. 缓存层实现:
javascript 复制代码
// 在aiService.js中添加缓存
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 }); // 10分钟缓存

class AIService {
  async chatCompletion(provider, payload) {
    const cacheKey = `${provider}:${JSON.stringify(payload)}`;
    const cached = cache.get(cacheKey);
    if (cached) return cached;
    
    // ...原有逻辑...
    
    const result = this._formatResponse(provider, response.data);
    cache.set(cacheKey, result);
    return result;
  }
}
  1. 速率限制中间件:
javascript 复制代码
// 创建rateLimiter.js
const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100, // 每个IP限制100次请求
  message: 'Too many requests from this IP, please try again later'
});

module.exports = apiLimiter;

// 在aiRoutes.js中使用
const apiLimiter = require('../middleware/rateLimiter');
router.post('/chat', apiLimiter, aiController.chat);
  1. 负载均衡策略:
javascript 复制代码
// 在aiService.js中添加提供商轮询
class AIService {
  constructor() {
    this.providerQueue = ['openai', 'anthropic', 'gemini'];
    this.currentIndex = 0;
  }

  async smartChatCompletion(payload) {
    // 简单轮询负载均衡
    const provider = this.providerQueue[this.currentIndex];
    this.currentIndex = (this.currentIndex + 1) % this.providerQueue.length;
    
    try {
      return await this.chatCompletion(provider, payload);
    } catch (error) {
      console.log(`Provider ${provider} failed, trying next`);
      return this.smartChatCompletion(payload); // 递归尝试下一个
    }
  }
}

六、部署与监控

6.1 生产环境部署

  1. 使用PM2进行进程管理:
bash 复制代码
npm install pm2 -g
pm2 start server/server.js --name "ai-api"
pm2 save
pm2 startup
  1. 环境变量管理:
bash 复制代码
# 创建生产环境.env文件
echo "NODE_ENV=production" > .env.production
echo "OPENAI_API_KEY=prod_key_here" >> .env.production
# ...其他变量...
  1. Docker化部署:
dockerfile 复制代码
# Dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install --production

COPY . .

ENV NODE_ENV production
ENV PORT 3000

EXPOSE 3000

CMD ["node", "server/server.js"]

6.2 监控与日志

  1. 添加健康检查端点:
javascript 复制代码
// 在server.js中添加
const health = require('express-ping');

app.use(health.ping());
  1. 日志记录中间件:
javascript 复制代码
const fs = require('fs');
const morgan = require('morgan');

// 创建日志目录
const logDir = './logs';
if (!fs.existsSync(logDir)) {
  fs.mkdirSync(logDir);
}

// 设置日志
app.use(morgan('combined', {
  stream: fs.createWriteStream(`${logDir}/access.log`, { flags: 'a' })
}));
  1. 错误跟踪集成:
javascript 复制代码
// 使用Sentry进行错误跟踪
const Sentry = require('@sentry/node');

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 1.0
});

app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.errorHandler());

七、安全最佳实践

  1. API密钥轮换:
javascript 复制代码
// 实现密钥自动轮换
const keyVault = {
  openai: {
    current: process.env.OPENAI_API_KEY,
    backup: process.env.OPENAI_API_KEY_BACKUP
  }
  // 其他提供商...
};

function rotateKey(provider) {
  if (keyVault[provider]?.backup) {
    const temp = keyVault[provider].current;
    keyVault[provider].current = keyVault[provider].backup;
    keyVault[provider].backup = temp;
    console.log(`Rotated ${provider} API key`);
  }
}

// 定期轮换或基于错误触发
setInterval(() => rotateKey('openai'), 86400000); // 每天轮换
  1. 请求验证:
javascript 复制代码
// 在aiController.js中添加验证中间件
exports.validateRequest = (req, res, next) => {
  const { provider, message } = req.body;
  
  // 检查必需字段
  if (!provider || !message) {
    return res.status(400).json({ error: 'Missing required fields' });
  }
  
  // 检查消息长度
  if (message.length > 10000) {
    return res.status(400).json({ error: 'Message too long' });
  }
  
  // 检查支持的提供商
  const validProviders = ['openai', 'anthropic', 'gemini'];
  if (!validProviders.includes(provider)) {
    return res.status(400).json({ error: 'Invalid provider' });
  }
  
  next();
};

// 在路由中使用
router.post('/chat', aiController.validateRequest, aiController.chat);
  1. 输出内容过滤:
javascript 复制代码
// 在aiService.js中添加内容过滤
const badWords = ['敏感词1', '敏感词2']; // 实际应用中应该从数据库或文件加载

function filterContent(text) {
  let filtered = text;
  badWords.forEach(word => {
    const regex = new RegExp(word, 'gi');
    filtered = filtered.replace(regex, '***');
  });
  return filtered;
}

// 在_formatResponse方法中应用
_formatResponse(provider, data) {
  const result = ...; // 原有逻辑
  result.text = filterContent(result.text);
  return result;
}

八、成本优化策略

  1. 使用量监控:
javascript 复制代码
// 在aiService.js中添加使用量跟踪
class AIService {
  constructor() {
    this.usageStats = {
      openai: { count: 0, tokens: 0 },
      anthropic: { count: 0, tokens: 0 },
      gemini: { count: 0 }
    };
  }

  async chatCompletion(provider, payload) {
    // ...原有逻辑...
    
    // 更新使用统计
    this.usageStats[provider].count += 1;
    if (result.usage) {
      this.usageStats[provider].tokens += result.usage.total_tokens || 0;
    }
    
    return result;
  }

  getUsageStats() {
    return this.usageStats;
  }
}

// 添加统计端点
router.get('/stats', (req, res) => {
  res.json(aiService.getUsageStats());
});
  1. 成本限制中间件:
javascript 复制代码
// 创建costLimiter.js
class CostLimiter {
  constructor(monthlyLimit) {
    this.monthlyLimit = monthlyLimit;
    this.currentUsage = 0;
    this.resetDate = new Date();
    this.resetDate.setMonth(this.resetDate.getMonth() + 1);
  }

  checkLimit(estimatedCost) {
    if (this.currentUsage + estimatedCost > this.monthlyLimit) {
      return false;
    }
    this.currentUsage += estimatedCost;
    return true;
  }

  timeUntilReset() {
    return this.resetDate - new Date();
  }
}

// 使用示例
const costLimiter = new CostLimiter(100); // $100每月限制

// 在路由中使用
router.post('/chat', (req, res, next) => {
  const estimatedCost = 0.02; // 根据模型和token数估算
  if (!costLimiter.checkLimit(estimatedCost)) {
    return res.status(429).json({
      error: 'Monthly cost limit exceeded',
      resetIn: costLimiter.timeUntilReset()
    });
  }
  next();
});

结语

通过本文的详细指南,您已经掌握了如何在Node.js全栈应用中集成多个AI提供商的API token。关键要点包括:

  1. 安全地管理和轮换API密钥
  2. 统一不同AI提供商的接口规范
  3. 实现前后端完整的交互流程
  4. 添加性能优化和安全防护措施
  5. 监控使用量和控制成本

随着AI技术的快速发展,建议定期更新各提供商的SDK和API规范,同时关注新兴的AI服务提供商,不断扩展您的应用能力。

相关推荐
小飞悟7 分钟前
那些年我们忽略的高频事件,正在拖垮你的页面
javascript·设计模式·面试
中微子20 分钟前
闭包面试宝典:高频考点与实战解析
前端·javascript
G等你下课1 小时前
告别刷新就丢数据!localStorage 全面指南
前端·javascript
爱编程的喵1 小时前
JavaScript闭包实战:从类封装到防抖函数的深度解析
前端·javascript
前端Hardy1 小时前
8个你必须掌握的「Vue」实用技巧
前端·javascript·vue.js
星月日1 小时前
深拷贝还在用lodash吗?来试试原装的structuredClone()吧!
前端·javascript
爱学习的茄子1 小时前
JavaScript闭包实战:解析节流函数的精妙实现 🚀
前端·javascript·面试
今夜星辉灿烂1 小时前
nestjs微服务-系列4
javascript·后端
吉吉安1 小时前
两张图片对比clip功能
javascript·css·css3
布兰妮甜1 小时前
开发在线商店:基于Vue2+ElementUI的电商平台前端实践
前端·javascript·elementui·vue