《60天AI学习计划启动 | Day 03: 第一个 AI 接口调用 - 前后端分离实践》

Day 03: 第一个 AI 接口调用 - 前后端分离实践

📋 学习目标


📚 核心学习内容

1. Express 框架基础

什么是 Express?

  • Node.js 的 Web 应用框架
  • 简化 HTTP 服务器开发
  • 提供路由、中间件等功能

核心概念:

// 路由(Route)
app.get('/api/users', handler)      // GET 请求
app.post('/api/users', handler)     // POST 请求
app.put('/api/users/:id', handler)  // PUT 请求
app.delete('/api/users/:id', handler) // DELETE 请求

// 中间件(Middleware)
app.use(express.json())  // 解析 JSON
app.use(cors())          // 跨域处理

安装 Express:

npm install express cors
npm install -D nodemon

2. 创建 Express 服务器

基础服务器:

import express from 'express';
import cors from 'cors';

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

// 中间件
app.use(cors());              // 允许跨域
app.use(express.json());      // 解析 JSON 请求体

// 路由
app.get('/', (req, res) => {
  res.json({ message: 'AI API Server is running!' });
});

// 启动服务器
app.listen(PORT, () => {
  console.log(`🚀 服务器运行在 http://localhost:${PORT}`);
});

3. RESTful API 设计

API 设计原则:

  • 使用名词,不用动词:/api/users 而不是 /api/getUsers
  • 使用 HTTP 方法表示操作
  • 返回 JSON 格式
  • 使用状态码表示结果

标准格式:

// 成功响应
{
  "success": true,
  "data": { ... },
  "message": "操作成功"
}

// 错误响应
{
  "success": false,
  "error": "错误信息",
  "code": "ERROR_CODE"
}

4. 封装 OpenAI API

API 路由设计:

POST /api/chat        # 聊天接口
POST /api/chat/stream # 流式聊天接口
GET  /api/health      # 健康检查

🏗️ 实践作业

作业1:创建 Express 服务器

src/server.js:

import express from 'express';
import cors from 'cors';
import { chatRouter } from './routes/chat.js';
import { logger } from './utils/logger.js';

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

// 中间件
app.use(cors({
  origin: process.env.FRONTEND_URL || 'http://localhost:5173',
  credentials: true
}));
app.use(express.json());

// 请求日志中间件
app.use((req, res, next) => {
  logger.info(`${req.method} ${req.path}`);
  next();
});

// 路由
app.get('/health', (req, res) => {
  res.json({ 
    status: 'ok', 
    timestamp: new Date().toISOString() 
  });
});

app.use('/api/chat', chatRouter);

// 404 处理
app.use((req, res) => {
  res.status(404).json({
    success: false,
    error: '接口不存在'
  });
});

// 错误处理中间件
app.use((err, req, res, next) => {
  logger.error('服务器错误:', err);
  res.status(500).json({
    success: false,
    error: err.message || '服务器内部错误'
  });
});

// 启动服务器
app.listen(PORT, () => {
  logger.success(`🚀 服务器运行在 http://localhost:${PORT}`);
});

作业2:创建聊天 API 路由

src/routes/chat.js:

import express from 'express';
import { chatWithAI } from '../services/openai.js';
import { logger } from '../utils/logger.js';

export const chatRouter = express.Router();

// POST /api/chat
chatRouter.post('/', async (req, res) => {
  try {
    const { message, conversationHistory = [] } = req.body;

    // 参数验证
    if (!message || typeof message !== 'string') {
      return res.status(400).json({
        success: false,
        error: '消息内容不能为空'
      });
    }

    logger.info(`收到消息: ${message.substring(0, 50)}...`);

    // 调用 OpenAI API
    const response = await chatWithAI(message, conversationHistory);

    res.json({
      success: true,
      data: {
        message: response.content,
        usage: response.usage
      }
    });
  } catch (error) {
    logger.error('聊天接口错误:', error);
    
    // 根据错误类型返回不同状态码
    if (error.status === 401) {
      return res.status(401).json({
        success: false,
        error: 'API Key 无效,请检查配置'
      });
    }

    if (error.status === 429) {
      return res.status(429).json({
        success: false,
        error: '请求过于频繁,请稍后重试'
      });
    }

    res.status(500).json({
      success: false,
      error: error.message || 'AI服务暂时不可用'
    });
  }
});

作业3:封装 OpenAI 服务

src/services/openai.js:

import OpenAI from 'openai';
import { logger } from '../utils/logger.js';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
});

/**
 * 与AI聊天
 * @param {string} message - 用户消息
 * @param {Array} conversationHistory - 对话历史
 * @returns {Promise<Object>} AI回复和Token使用情况
 */
export async function chatWithAI(message, conversationHistory = []) {
  try {
    // 构建消息列表
    const messages = [
      {
        role: 'system',
        content: '你是一个友好的AI助手,擅长回答各种问题。'
      },
      ...conversationHistory,
      {
        role: 'user',
        content: message
      }
    ];

    logger.info(`发送请求到 OpenAI,消息数: ${messages.length}`);

    const completion = await openai.chat.completions.create({
      model: process.env.OPENAI_MODEL || 'gpt-3.5-turbo',
      messages: messages,
      temperature: parseFloat(process.env.TEMPERATURE || '0.7'),
      max_tokens: parseInt(process.env.MAX_TOKENS || '1000')
    });

    const response = completion.choices[0].message.content;
    const usage = completion.usage;

    logger.success(`AI回复成功,Token使用: ${usage.total_tokens}`);

    return {
      content: response,
      usage: {
        prompt_tokens: usage.prompt_tokens,
        completion_tokens: usage.completion_tokens,
        total_tokens: usage.total_tokens
      }
    };
  } catch (error) {
    logger.error('OpenAI API 调用失败:', error);
    
    // 处理特定错误
    if (error instanceof OpenAI.APIError) {
      throw {
        status: error.status,
        message: error.message
      };
    }
    
    throw new Error('AI服务调用失败');
  }
}

作业4:更新 package.json

添加启动脚本:

{
  "scripts": {
    "start": "node src/server.js",
    "dev": "nodemon src/server.js"
  }
}

作业5:前端调用接口

前端代码(Vue/React):

// utils/api.js
import axios from 'axios';

const api = axios.create({
  baseURL: 'http://localhost:3000/api',
  timeout: 30000
});

// 请求拦截器
api.interceptors.request.use(
  config => {
    console.log('发送请求:', config.url);
    return config;
  },
  error => Promise.reject(error)
);

// 响应拦截器
api.interceptors.response.use(
  response => response.data,
  error => {
    console.error('请求失败:', error);
    return Promise.reject(error);
  }
);

// 聊天接口
export async function chatWithAI(message, conversationHistory = []) {
  try {
    const response = await api.post('/chat', {
      message,
      conversationHistory
    });
    
    if (response.success) {
      return response.data;
    } else {
      throw new Error(response.error);
    }
  } catch (error) {
    throw error;
  }
}

Vue 组件示例:

<template>
  <div class="chat-container">
    <div class="messages">
      <div 
        v-for="(msg, index) in messages" 
        :key="index"
        :class="['message', msg.type]">
        {{ msg.content }}
      </div>
    </div>
    
    <div class="input-area">
      <input 
        v-model="inputMessage"
        @keyup.enter="sendMessage"
        placeholder="输入消息..."
        :disabled="loading" />
      <button 
        @click="sendMessage"
        :disabled="loading || !inputMessage.trim()">
        {{ loading ? '发送中...' : '发送' }}
      </button>
    </div>
  </div>
</template>

<script>
import { chatWithAI } from '@/utils/api';

export default {
  data() {
    return {
      messages: [],
      inputMessage: '',
      loading: false,
      conversationHistory: []
    };
  },
  methods: {
    async sendMessage() {
      if (!this.inputMessage.trim() || this.loading) return;

      const userMessage = this.inputMessage.trim();
      this.inputMessage = '';
      
      // 添加用户消息
      this.messages.push({
        type: 'user',
        content: userMessage
      });

      // 更新对话历史
      this.conversationHistory.push({
        role: 'user',
        content: userMessage
      });

      this.loading = true;

      try {
        const response = await chatWithAI(userMessage, this.conversationHistory);
        
        // 添加AI回复
        this.messages.push({
          type: 'ai',
          content: response.message
        });

        // 更新对话历史
        this.conversationHistory.push({
          role: 'assistant',
          content: response.message
        });
      } catch (error) {
        this.messages.push({
          type: 'error',
          content: error.message || '请求失败,请稍后重试'
        });
      } finally {
        this.loading = false;
      }
    }
  }
};
</script>

作业6:测试 API

使用 Postman 测试:

POST http://localhost:3000/api/chat
Content-Type: application/json

{
  "message": "用一句话介绍前端开发",
  "conversationHistory": []
}

预期响应:

{
  "success": true,
  "data": {
    "message": "前端开发是构建用户界面和交互体验的技术...",
    "usage": {
      "prompt_tokens": 25,
      "completion_tokens": 35,
      "total_tokens": 60
    }
  }
}

⚠️ 遇到的问题

问题1:CORS 跨域错误

错误信息: Access to XMLHttpRequest has been blocked by CORS policy

解决方案:

// 后端添加 CORS 中间件
import cors from 'cors';

app.use(cors({
  origin: 'http://localhost:5173', // 前端地址
  credentials: true
}));

问题2:请求体解析失败

错误信息: Cannot read property 'message' of undefined

解决方案:

// 确保添加 JSON 解析中间件
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

问题3:环境变量未加载

问题: API Key 为 undefined

解决方案:

// server.js 开头添加
import 'dotenv/config';

// 或使用 dotenv
import dotenv from 'dotenv';
dotenv.config();

问题4:端口被占用

错误信息: Port 3000 is already in use

解决方案:

# Windows
netstat -ano | findstr :3000
taskkill /PID <PID> /F

# Mac/Linux
lsof -ti:3000 | xargs kill -9

📊 学习总结

今日收获

  1. ✅ 掌握 Express 框架基础使用
  2. ✅ 创建 RESTful API 服务
  3. ✅ 封装 OpenAI API 为后端服务
  4. ✅ 实现前后端分离架构
  5. ✅ 掌握错误处理和参数验证

关键知识点

  • Express 是 Web 框架,简化 HTTP 服务器开发
  • RESTful API 设计,使用标准 HTTP 方法和状态码
  • 中间件机制,处理请求和响应
  • 错误处理很重要,提供友好的错误信息
  • 前后端分离,后端提供 API,前端调用

架构设计

前端 (Vue/React)
    ↓ HTTP 请求
后端 API (Express)
    ↓ 调用
OpenAI API
    ↓ 返回
后端处理响应
    ↓ JSON 返回
前端展示结果

前端开发者的优势

  • ✅ 熟悉 HTTP 请求(axios/fetch)
  • ✅ 理解异步编程(async/await)
  • ✅ 熟悉 JSON 数据格式
  • ✅ 理解前后端交互流程

🔧 代码优化建议

1. 添加请求限流

import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100 // 最多100次请求
});

app.use('/api/', limiter);

2. 添加请求日志

app.use((req, res, next) => {
  logger.info(`${req.method} ${req.path}`, {
    body: req.body,
    query: req.query
  });
  next();
});

3. 参数验证中间件

const validateChatRequest = (req, res, next) => {
  const { message } = req.body;
  
  if (!message || typeof message !== 'string') {
    return res.status(400).json({
      success: false,
      error: '消息内容无效'
    });
  }
  
  if (message.length > 2000) {
    return res.status(400).json({
      success: false,
      error: '消息内容过长(最多2000字符)'
    });
  }
  
  next();
};

chatRouter.post('/', validateChatRequest, async (req, res) => {
  // ...
});

📅 明日计划

明天将学习:

期待明天的学习! 🚀


📚 参考资源


💻 代码仓库

项目已更新:

  • ✅ 添加 Express 服务器
  • ✅ 实现聊天 API 接口
  • ✅ 前端调用示例

GitHub 提交: Day 03 - 第一个AI接口调用


💭 写在最后

今天完成了第一个完整的 AI 接口调用,实现了前后端分离架构。虽然功能简单,但已经搭建了完整的开发流程。明天将学习流式响应,让 AI 回复更加流畅自然!

继续加油! 💪


✅ 快速检查清单

完成这些,第三天就达标了!


标签: #AI学习 #Express #RESTfulAPI #前后端分离 #学习笔记

posted @ 2025-12-16 14:22  XiaoZhengTou  阅读(3)  评论(0)    收藏  举报