构建前后端协作的 AI 图片生成应用:Koa 框架与 OpenAI 的实践
引言
随着人工智能技术的迅猛发展,AI 在各个领域的应用变得越来越广泛和深入。在图像生成领域,OpenAI 推出的 DALL-E 系列模型,以其强大的图像生成能力和创新性,引起了广泛的关注和应用。DALL-E 3 作为这一系列的最新版本,能够根据文本描述生成高质量、复杂且创意无限的图像,为开发者提供了强大的工具。
今天呆同学和大家分享的是一个从前端到后端的 AI 全栈开发项目,具体内容是利用 Node.js 驱动的 DALL-E 3 进行 AI 图片生成。通过这次学习学会如何配置开发环境、搭建前后端架构、与 DALL-E 3 API 进行交互,并实现一个功能完备的图像生成应用。
AI全栈实战
今天这个项目我们将前后端分离的模式来进行,前端frontend文件和后端backend文件,那么接下来我们便开始吧。
frontend (前端)
图片:
html 部分
Bootstrap 引入
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
从 CDN 引入 Bootstrap 3.3.0 的 CSS 文件,提供预定义的样式和布局类。
页面布局
页面布局可以大体分为两个部分,一部分是表单内容,一部分是图片展示内容。其中,表单部分的布局我们通过Bootstrap
实现一个快速搭建,也是十分重要的内容。它由一个图片名称的文本输入框、一个图片描述内容的多行问输入框和一个生成图片的按钮组成,点击按钮后通过按钮的提交事件触发请求,将用户输入的数据通过post请求发送给后端服务器。另一个部分则是在在后面拿到生成图片的URL,动态添加一个img
标签展示图片
Bootstrap 是一个流行的前端框架,提供了一组预定义的 CSS 和 JavaScript 类,用于快速开发响应式和美观的网页。代码中使用了 Bootstrap 的
container
、row
、col-md-6
、col-md-offset-3
、form-group
、form-control
、btn
和btn-primary
类,来实现布局和样式。
<!-- bootstrap 框架 -->
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form name="logoForm">
<div class="form-group">
<label for="titleInput">Bot名称</label>
<input type="text" class="form-control" id="titleInput" name="title" placeholder="请输入Bot名称"
required>
</div>
<div class="form-group">
<label for="descInput">Bot介绍</label>
<textarea class="form-control" name="desc" id="descInput" placeholder="请输入Bot介绍"></textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">生成图标</button>
</div>
</form>
</div>
<div class="row" id="logo">
</div>
</div>
</div>
1. Bootstrap 网格系统:
-
<div class="container">
:Bootstrap 的容器类,包含所有内容并提供固定宽度和响应式行为。 -
<div class="row">
:Bootstrap 的行类,用于创建水平布局。 -
<div class="col-md-6 col-md-offset-3">
:Bootstrap 的列类,设置宽度为 6 列(中等屏幕),并在左侧偏移 3 列,使表单居中。
2. 表单元素:
-
<div class="form-group">
:Bootstrap 的表单组类,提供样式和间距。 -
<label for="titleInput">
和<label for="descInput">
:输入字段的标签。 -
<input type="text" class="form-control" id="titleInput" name="title" placeholder="请输入Bot名称" required>
:文本输入框,使用 Bootstrap 的form-control
类。 -
<textarea class="form-control" name="desc" id="descInput" placeholder="请输入Bot介绍"></textarea>
:文本区域,使用 Bootstrap 的form-control
类。 -
<button type="submit" class="btn btn-primary">生成图片</button>
:提交按钮,使用 Bootstrap 的btn
和btn-primary
类,提供按钮样式。
JavaScript 部分
1. 获取表单和图标容器
const oForm = document.forms['logoForm'];
const oLogo = document.getElementById('logo');
获取所有表单元素和图标容器,用于获取用户输入的信息和设置监听提交事件的监听器。
2. 表单提交事件监听
oForm.addEventListener('submit', function (e) {
e.preventDefault(); // 阻止默认提交行为
const title = this["title"].value.trim(); // 获取并去除标题输入框的首尾空格
const desc = this["desc"].value.trim(); // 获取并去除介绍输入框的首尾空格
在监听提交事件的监听器中,需要阻止默认的页面刷新行为,this在这里引用的是表单oForm,this["title"].value
和 this["desc"].value
访问的是表单中 name
属性分别为 title
和 desc
的输入值。
3. 检查数据并整理数据
if (title) {
const data = {
title,
desc
}
在发送请求之前,我们先检查一下是否有值,如果有则继续处理数据
4. 发送数据到服务器
// post 提交表单
fetch('http://localhost:3000/logo', {
method: 'POST',
// 请求体 字符串或文件二进制
body: JSON.stringify(data), // 对象转字符串
headers: {
'Content-Type': 'application/json' // 表示发送的是json数据
}
})
.then(res => res.json())
提交表单数据,使用Fetch API 向服务器发送 POST 请求,然后将 data
对象转换为 JSON 字符串,并作为请求体发送。设置请求头,表示请求体的内容类型为 JSON。最后解析服务器响应为 JSON 格式。
5. 处理服务器响应
.then(data => {
if (data.code == 200) { // 检查响应代码
const { url } = data; // 获取生成的图标 URL
const oImag = document.createElement('img'); // 创建图像元素
oImag.src = url; // 设置图像源
oImag.onload = function () { // 图像加载完成后
document.getElementById('logo').appendChild(oImag); // 将图像添加到页面
}
} else {
console.log('出错了'); // 错误处理
}
})
}
});
处理解析后的 JSON 数据,先检查响应中的code
是否为 200,是则表示请求成功,然后从响应数据中提取出url
属性,即生成的图片的 URL,再创建一个新的图片标签,设置图像的src
属性为生成的图片的 URL,最后在图像加载完后,执行函数,将图片渲染在页面上id="logo"
的容器中。
完整前端代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
<style>
#logo img {
display: block;
width: 200px;
height: 200px;
}
</style>
</head>
<body>
<!-- bootstrap 框架 -->
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form name="logoForm">
<div class="form-group">
<label for="titleInput">Bot名称</label>
<input type="text" class="form-control" id="titleInput" name="title" placeholder="请输入Bot名称"
required>
</div>
<div class="form-group">
<label for="descInput">Bot介绍</label>
<textarea class="form-control" name="desc" id="descInput" placeholder="请输入Bot介绍"></textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">生成图标</button>
</div>
</form>
</div>
<div class="row" id="logo">
</div>
</div>
</div>
<script>
const oForm = document.forms['logoForm'];
const oLogo = document.getElementById('logo');
oForm.addEventListener('submit', function (e) {
e.preventDefault();
const title = this["title"].value.trim();
const desc = this["desc"].value.trim();
// console.log(title, desc, '///');
if (title) {
const data = {
title,
desc
}
// post 提交表单
fetch('http://localhost:3000/logo', {
method: 'POST',
// 请求体 字符串或文件二进制
body: JSON.stringify(data), // 对象转字符串
headers: {
'Content-Type': 'application/json' // 表示发送的是json数据
}
})
.then(res => res.json())
.then(data => {
// console.log(data);
if (data.code == 200) {
const { url } = data;
const oImag = document.createElement('img');
oImag.src = url;
oImag.onload = function () {
document.getElementById('logo').appendChild(oImag);
}
} else {
console.log('出错了');
}
})
}
});
</script>
</body>
</html>
backend (后端 + OpenAI)
接下来便开始用 Node.js 搭建我们的后端,在后端中我将引入 OpenAI 的服务。
初始化项目
首先初始化一个Node.js后端项目
npm init -y
安装模块
安装项目所需模块
npm i koa
npm i koa-router
npm i @koa/cors
npm i -g nodemon
npm i @koa/bodyparser
npm i openai
npm i dotenv
一起来看看每个模块的作用吧:
-
koa 是一个用于构建 Web 应用程序和 API 的新型 Web 框架, 基于中间件的架构,使得处理 HTTP 请求和响应更灵活。使用现代 JavaScript 特性,如
async
和await
,编写异步代码更简单。旨在提供更强大的中间件功能,处理 HTTP 请求和响应的流更简洁。 -
Koa Router 是一个为 Koa 提供路由功能的中间件,使得定义和组织路由更加方便。作用是定义不同的 HTTP 方法(如 GET、POST)的路由。组织和管理路由,使代码更清晰和可维护。
-
@koa/cors 是一个 Koa 的中间件,用于处理跨域资源共享(CORS)。作用是允许服务器接受来自不同源(域)的请求,解决浏览器的同源策略限制。可以配置允许的来源、HTTP 方法、请求头等。
-
Nodemon 是一个开发工具,用于在检测到文件变更时自动重启 Node.js 应用程序。作用是提高开发效率,自动检测代码变更并重启服务器,这样避免了代码更新后重新启动node服务。支持多种文件类型的监控,如
.js
,.json
,.md
等。 -
@koa/bodyparser 是一个 Koa 中间件,用于解析请求体中的数据,支持 JSON 和 URL-encoded 格式。 作用是自动解析请求体数据,并将解析后的数据绑定到
ctx.request.body
上。支持多种格式,如 JSON、表单数据等。 -
OpenAI 是一个 SDK,用于与 OpenAI 的 API 进行交互,如 DALL-E、GPT-3 等。作用是提供与 OpenAI API 的接口,便于开发者集成和使用 OpenAI 的各项服务。支持文本生成、图像生成、代码生成等多种功能。
-
Dotenv 是一个模块,用于将环境变量从
.env
文件加载到process.env
中。作用是使得在开发过程中管理环境变量更方便和安全。支持将敏感信息(如 API 密钥)存储在.env
文件中,避免硬编码到代码中。
中间件(Middleware)是指在请求处理过程中,被用来执行一系列任务的软件。它介于客户端请求和服务器响应之间,负责处理、修改、或管理请求和响应。
在 Web 开发中,中间件通常用于:
- 请求预处理:在请求到达实际路由处理器之前,对请求进行检查或修改。例如,验证用户身份、解析请求体、添加 CORS 头等。
- 响应后处理:在响应返回客户端之前,对响应进行检查或修改。例如,格式化响应数据、压缩响应等。
- 日志记录:记录请求和响应的信息,用于调试和监控。
- 错误处理:捕获并处理请求处理过程中的错误。
在 Koa 中使用中间件
Koa 框架通过其简洁的中间件机制提供了强大的扩展能力。Koa 中的中间件是一个函数,它接收两个参数
ctx
(上下文)和next
(调用下一个中间件的函数)。中间件的执行顺序是按定义顺序进行的,形成一个中间件栈。
导入模块
我们将安装好的模块导入,导入我们通过 ES6 模块使用import导入,后面用export导出。
import Koa from 'koa';
import Router from 'koa-router';
import cors from '@koa/cors';
import { bodyParser } from '@koa/bodyparser';
import OpenAI from 'openai';
import dotenv from 'dotenv';
获取 API Key
想要调用 OpenAI 的服务必须要有 API Key,没有的话可以去任何一个代理处获取一个,这里提供一个代理处:WildCard,选择 “API 随心用” 这一栏,接下来便是要充值了,充值完后就会得到自己的 API Key。要注意的是,这个 Key 要保存好,不要泄露,否则别人用的话会扣钱的,说多了都是泪啊。
然后就在创建好的.env文件中存储你的 API key。
OPENAI_KEY='你的API Key'
配置环境变量
dotenv.config({
path: '.env'
});
加载 .env
文件中的环境变量,使其可以通过 process.env
访问。
配置 OpenAI 客户端
const client = new OpenAI({
apiKey: process.env.OPENAI_KEY,
baseURL: 'https://api.gptsapi.net/v1'
});
创建 OpenAI 客户端实例client
,使用从环境变量中加载的 API 密钥,并通过baseURL
这个代理间接访问 OpenAI 的 API 服务。
创建 Koa 应用
const app = new Koa();
const router = new Router();
使用 Koa 框架和 Koa-Router 中间件分别创建 Koa 应用实例和 Koa-Router 实例。有了 Koa 框架和路由系统,后续我们就可以定义和操作路由。
使用中间件
// 挂载跨域功能
app.use(cors());
// 挂载post请求体解析功能
app.use(bodyParser());
-
app.use(cors())
:使用跨域中间件,允许跨域请求。 -
app.use(bodyParser())
:使用请求体解析中间件,解析 JSON 请求体。
定义路由
router.post('/logo', async (ctx) => {
const { title, desc } = ctx.request.body;
const prompt = `
你是一位举世闻名的美术设计师,请为图片名为${title},
描述细节为${desc}的内容
设计一张符合图片名和描述细节的高质量图片
`;
try {
const response = await client.images.generate({
model: "dall-e-3",
prompt: prompt,
n: 1,
size: "1024x1024",
});
ctx.body = {
code: 200,
url: response.data[0].url
};
} catch (error) {
console.error(error);
ctx.body = {
code: 500,
msg: '出错了'
};
}
});
router.get('/', (ctx) => {
ctx.body = 'index page';
});
-
定义一个处理
/logo
路径的 POST 请求的路由和一个处理函数,用于接收表单数据并调用 OpenAI API 生成图片。- 从请求体中提取
title
和desc
。 - 构建
prompt
字符串,用于生成图片。 - 调用 OpenAI API 的
dall-e-3
接口,通过images.generate()
方法生成图片。 - 将生成的图片 URL 返回给客户端。
- 从请求体中提取
-
再定义 GET 路由,在用户访问根路径
http://localhost:3000/
时返回一个响应,设置响应体为index page
,这意味着服务器会返回一个简单的文本响应 " index page ", 这段简单的路由可以用于调试和测试,确保服务器和路由配置正确。。
使用路由中间件并启动服务
// router.routes 路由上的所有路由的声明加上去
app.use(router.routes());
// http 服务 端口号 3000
app.listen(3000, () => {
console.log('server is running at http://localhost:3000');
});
- 将路由中间件添加到 Koa 应用中,使其生效。
- 启动服务器,监听 3000 端口,并输出提示信息。
注意:前端和后端定义的路径和方法需要保持一致,以确保前端请求能够正确地到达后端,并得到期望的响应。
一致性检查
- 请求方法:前端使用
fetch
方法发送 method: POST 请求,而后端也定义了一个 POST 路由来处理该请求。二者保持一致。- 请求路径:前端请求的路径是
http://localhost:3000/logo
,后端定义的路由路径是/logo
。二者保持一致。如果路径或请求方法不一致,前端请求将无法正确到达后端。例如:
- 如果前端使用
fetch('http://localhost:3000/logo')
而后端定义了router.post('/logos')
,则前端请求不会匹配到后端的路由。- 如果前端使用
method: 'GET'
而后端定义了router.post('/logo')
,同样会导致请求无法匹配到正确的路由。
效果展示
总结
今天的学习内容涉及前后端的协调与整合,特别是在构建一个基于 Node.js 和 Koa 框架的后端服务,同时通过前端与其交互,完成特定任务的实现。以下是今天学习的总结:
- 前端与后端路径和方法一致性:学会了如何确保前端发起的请求与后端定义的路由保持一致,这是前后端交互的基础。
- Koa 框架的使用:深入了解了 Koa 框架的基本使用,包括如何创建 Koa 应用、定义路由、使用中间件处理请求和响应、以及启动服务器。掌握了跨域处理(@koa/cors)和请求体解析(@koa/bodyparser)的基本操作。
- 环境变量管理:学习了使用 dotenv 管理环境变量的方法,确保敏感信息(如 API 密钥)不被硬编码在代码中,提升了应用的安全性和灵活性。
- 调用 OpenAI API:通过实例了解了如何使用 OpenAI SDK 调用 DALL-E 3 模型,根据用户输入生成高质量的图片。学会了处理异步请求和错误处理的基本方法。
- 前端表单处理:学会了如何在前端使用 fetch API 发送 POST 请求,如何获取表单数据、发送 JSON 数据,以及处理响应。
- 调试和测试:理解了在开发过程中如何通过简单的 GET 路由进行调试和测试,以确保服务器和路由配置正确。
通过这些学习,你已经掌握了构建一个前后端协作的基础知识,能够创建一个基本的 Web 应用,实现从用户输入到后端处理再到结果展示的完整流程。这些技能将为你未来开发更复杂的应用奠定坚实的基础。继续加油!
转载自:https://juejin.cn/post/7398736473091014693