《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
📊 学习总结
今日收获
- ✅ 掌握 Express 框架基础使用
- ✅ 创建 RESTful API 服务
- ✅ 封装 OpenAI API 为后端服务
- ✅ 实现前后端分离架构
- ✅ 掌握错误处理和参数验证
关键知识点
- 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 #前后端分离 #学习笔记

浙公网安备 33010602011771号