本教程将带您实现一个基于Swagger的图书管理系统API主要功能包括图书的增删改查操作分页查询条件过滤API文档自动生成在线接口测试技术栈Node.js ExpressSwagger UI ExpressSwagger JSDoc环境准备安装Node.js确保您的系统已安装 Node.js建议使用v14.0.0或更高版本。检查环境node -v npm -v项目初始化创建项目目录mkdir book-management-api cd book-management-api npm init -y安装依赖包npm install express cors swagger-ui-express swagger-jsdoc nodemon --save创建基础项目结构book-management-api/ ├── node_modules/ ├── models/ │ └── book.js ├── routes/ │ └── books.js ├── middleware/ │ └── auth.js ├── swagger/ │ └── swagger.js ├── app.js └── package.json创建入口文件 app.js实例const express require(express);const cors require(cors);const swaggerUi require(swagger-ui-express);const swaggerSpec require(./swagger/swagger);const booksRouter require(./routes/books);const app express();const PORT process.env.PORT || 3000;// 中间件app.use(cors());app.use(express.json());app.use(express.urlencoded({ extended: true }));// API路由app.use(/api/books, booksRouter);// Swagger文档app.use(/api-docs, swaggerUi.serve, swaggerUi.setup(swaggerSpec));// 启动服务器app.listen(PORT, () {console.log(服务已启动访问 http://localhost:${PORT});console.log(API文档地址: http://localhost:${PORT}/api-docs);});API 设计与实现创建图书模型在models/book.js中创建一个简单的内存数据模型实例// models/book.jslet books [{ id: 1, title: 深入理解JavaScript, author: Douglas Crockford, publishDate: 2008-01-01, isbn: 978-0596517748, category: 编程 },{ id: 2, title: Node.js实战, author: Alex Young, publishDate: 2014-08-01, isbn: 978-1617290572, category: 编程 },{ id: 3, title: 三体, author: 刘慈欣, publishDate: 2008-01-01, isbn: 978-7536692387, category: 科幻 }];let nextId 4;module.exports {// 获取所有图书getAll: (page 1, limit 10, filter {}) {let result [...books];// 应用过滤条件if (filter.category) {result result.filter(book book.category filter.category);}if (filter.author) {result result.filter(book book.author.includes(filter.author));}if (filter.title) {result result.filter(book book.title.includes(filter.title));}// 计算分页const startIndex (page - 1) * limit;const endIndex page * limit;return {total: result.length,page: page,limit: limit,data: result.slice(startIndex, endIndex)};},// 获取单本图书getById: (id) {return books.find(book book.id id);},// 创建图书create: (book) {const newBook { ...book, id: nextId };books.push(newBook);return newBook;},// 更新图书update: (id, bookData) {const index books.findIndex(book book.id id);if (index ! -1) {books[index] { ...books[index], ...bookData };return books[index];}return null;},// 删除图书delete: (id) {const index books.findIndex(book book.id id);if (index ! -1) {const deletedBook books[index];books.splice(index, 1);return deletedBook;}return null;}};实现路由控制器在routes/books.js中实现 API 路由实例// routes/books.jsconst express require(express);const router express.Router();const Book require(../models/book);/*** swagger* components:* schemas:* Book:* type: object* required:* - title* - author* - isbn* properties:* id:* type: integer* description: 图书ID* title:* type: string* description: 图书标题* author:* type: string* description: 作者* publishDate:* type: string* format: date* description: 出版日期* isbn:* type: string* description: ISBN编号* category:* type: string* description: 图书分类*//*** swagger* /api/books:* get:* summary: 获取图书列表* description: 返回所有图书支持分页和过滤* parameters:* - in: query* name: page* schema:* type: integer* default: 1* description: 页码* - in: query* name: limit* schema:* type: integer* default: 10* description: 每页数量* - in: query* name: category* schema:* type: string* description: 按分类过滤* - in: query* name: author* schema:* type: string* description: 按作者过滤* - in: query* name: title* schema:* type: string* description: 按标题过滤* responses:* 200:* description: 成功获取图书列表* content:* application/json:* schema:* type: object* properties:* total:* type: integer* page:* type: integer* limit:* type: integer* data:* type: array* items:* $ref: #/components/schemas/Book*/router.get(/, (req, res) {const page parseInt(req.query.page) || 1;const limit parseInt(req.query.limit) || 10;const filter {};if (req.query.category) filter.category req.query.category;if (req.query.author) filter.author req.query.author;if (req.query.title) filter.title req.query.title;const result Book.getAll(page, limit, filter);res.json(result);});/*** swagger* /api/books/{id}:* get:* summary: 获取单本图书* description: 通过ID获取特定图书的详细信息* parameters:* - in: path* name: id* required: true* schema:* type: integer* description: 图书ID* responses:* 200:* description: 成功获取图书* content:* application/json:* schema:* $ref: #/components/schemas/Book* 404:* description: 图书不存在*/router.get(/:id, (req, res) {const id parseInt(req.params.id);const book Book.getById(id);if (book) {res.json(book);} else {res.status(404).json({ message: 图书不存在 });}});/*** swagger* /api/books:* post:* summary: 创建新图书* description: 添加一本新图书到系统* requestBody:* required: true* content:* application/json:* schema:* type: object* required:* - title* - author* - isbn* properties:* title:* type: string* author:* type: string* publishDate:* type: string* format: date* isbn:* type: string* category:* type: string* responses:* 201:* description: 图书创建成功* content:* application/json:* schema:* $ref: #/components/schemas/Book* 400:* description: 输入数据无效*/router.post(/, (req, res) {// 简单验证if (!req.body.title || !req.body.author || !req.body.isbn) {return res.status(400).json({ message: 标题、作者和ISBN是必填字段 });}const newBook Book.create(req.body);res.status(201).json(newBook);});/*** swagger* /api/books/{id}:* put:* summary: 更新图书* description: 通过ID更新特定图书的信息* parameters:* - in: path* name: id* required: true* schema:* type: integer* description: 图书ID* requestBody:* required: true* content:* application/json:* schema:* type: object* properties:* title:* type: string* author:* type: string* publishDate:* type: string* format: date* isbn:* type: string* category:* type: string* responses:* 200:* description: 图书更新成功* content:* application/json:* schema:* $ref: #/components/schemas/Book* 404:* description: 图书不存在*/router.put(/:id, (req, res) {const id parseInt(req.params.id);const updatedBook Book.update(id, req.body);if (updatedBook) {res.json(updatedBook);} else {res.status(404).json({ message: 图书不存在 });}});/*** swagger* /api/books/{id}:* delete:* summary: 删除图书* description: 通过ID删除特定图书* parameters:* - in: path* name: id* required: true* schema:* type: integer* description: 图书ID* responses:* 200:* description: 图书删除成功* 404:* description: 图书不存在*/router.delete(/:id, (req, res) {const id parseInt(req.params.id);const deletedBook Book.delete(id);if (deletedBook) {res.json({ message: 图书删除成功, book: deletedBook });} else {res.status(404).json({ message: 图书不存在 });}});module.exports router;Swagger 文档配置配置 Swagger创建swagger/swagger.js文件实例// swagger/swagger.jsconst swaggerJsdoc require(swagger-jsdoc);const options {definition: {openapi: 3.0.0,info: {title: 图书管理API,version: 1.0.0,description: 使用Express和Swagger构建的图书管理系统API,contact: {name: 开发者,email: devexample.com}},servers: [{url: http://localhost:3000,description: 开发服务器}]},// 扫描所有包含注解的JS文件apis: [./routes/*.js]};const swaggerSpec swaggerJsdoc(options);module.exports swaggerSpec;添加认证中间件创建middleware/auth.js实例// middleware/auth.jsconst jwt require(jsonwebtoken);// 简单JWT验证中间件const authMiddleware (req, res, next) {const token req.header(Authorization)?.replace(Bearer , );if (!token) {return res.status(401).json({ message: 未提供认证令牌 });}try {// 在实际应用中这里需要验证JWT令牌// const decoded jwt.verify(token, process.env.JWT_SECRET);// req.user decoded.user;// 为简化教程我们跳过验证req.user { id: 1, role: admin };next();} catch (error) {return res.status(401).json({ message: 无效的认证令牌 });}};module.exports authMiddleware;接口功能测试启动服务使用以下命令启动服务# 如果已配置了package.json中的scripts执行 npm start # 或者直接使用nodemon npx nodemon app.js6.2 访问Swagger文档打开浏览器访问 http://localhost:3000/api-docs使用 Swagger 测试接口获取所有图书点击 GET /api/books 接口点击 Try it out 按钮可以设置分页参数 page 和 limit以及过滤条件点击 Execute 按钮发送请求观察结果创建新图书点击 POST /api/books 接口点击 Try it out 按钮在请求体中填入图书信息例如{ title: RESTful API设计, author: Martin Fowler, publishDate: 2020-01-01, isbn: 978-1234567890, category: 编程 }点击 Execute 按钮发送请求观察结果获取、更新和删除使用类似方法测试其他接口常见问题排查跨域问题如果前端应用和 API 不在同一域下运行可能会遇到跨域问题。我们已经在应用入口配置了 CORS 中间件app.use(cors());如果需要更精细的控制可以这样配置实例const corsOptions {origin: [http://localhost:8080, https://your-frontend-domain.com],methods: [GET, POST, PUT, DELETE],allowedHeaders: [Content-Type, Authorization],credentials: true};app.use(cors(corsOptions));认证配置错误认证问题通常出现在以下几个方面JWT 配置错误确保JWT_SECRET环境变量已正确设置// 在应用启动时检查 if (!process.env.JWT_SECRET) { console.warn(警告: JWT_SECRET环境变量未设置认证可能不安全); }认证中间件使用错误确保在需要认证的路由上正确应用中间件const auth require(../middleware/auth); // 公开路由 - 无需认证 router.get(/, (req, res) { /* ... */ }); // 受保护路由 - 需要认证 router.post(/, auth, (req, res) { /* ... */ }); router.put(/:id, auth, (req, res) { /* ... */ }); router.delete(/:id, auth, (req, res) { /* ... */ });Swagger文档中的认证配置更新Swagger配置以支持Bearer Token认证// swagger/swagger.js const options { definition: { // ... 其他配置 ... components: { securitySchemes: { bearerAuth: { type: http, scheme: bearer, bearerFormat: JWT } } }, security: [ { bearerAuth: [] } ] }, // ... 其他配置 ... };请求头配置确保客户端请求时正确设置了认证头// 前端示例代码 - 使用fetch API const token localStorage.getItem(token); fetch(http://localhost:3000/api/books, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${token} }, body: JSON.stringify(bookData) }) .then(response response.json()) .then(data console.log(data)) .catch(error console.error(错误:, error));Swagger 文档不更新如果更新了 JSDoc 注释但 Swagger 文档没有更新尝试以下解决方案确保路径配置正确// swagger/swagger.js const options { // ... 其他配置 ... apis: [./routes/*.js, ./models/*.js] // 确保包含所有需要生成文档的文件 };重启服务器有时候需要重启服务器才能看到新的Swagger文档。清除浏览器缓存使用CtrlF5强制刷新页面。来自 https://www.zjcp.cc/ask/7011.html进阶功能添加 API 版本控制修改应用入口文件// app.js // ... 其他导入 ... // API v1 app.use(/api/v1/books, booksRouter); // 为未来的API v2预留 // app.use(/api/v2/books, booksRouterV2); // ... 其他代码 ...添加请求验证使用 Express 验证中间件如express-validatornpm install express-validator --save然后在路由中使用实例// routes/books.jsconst { body, validationResult } require(express-validator);// 创建图书的验证规则const validateBook [body(title).notEmpty().withMessage(标题不能为空),body(author).notEmpty().withMessage(作者不能为空),body(isbn).notEmpty().withMessage(ISBN不能为空).matches(/^(?(?:\D*\d){10}(?:(?:\D*\d){3})?$)[\d-]$/).withMessage(无效的ISBN格式),body(publishDate).optional().isDate().withMessage(出版日期格式无效),// 验证结果处理中间件(req, res, next) {const errors validationResult(req);if (!errors.isEmpty()) {return res.status(400).json({ errors: errors.array() });}next();}];// 在路由中使用验证router.post(/, validateBook, (req, res) {// ... 创建图书的代码 ...});添加响应压缩使用压缩中间件减小响应体积npm install compression --save然后在应用入口文件中添加// app.js const compression require(compression); // ... 其他代码 ... // 启用压缩 app.use(compression()); // ... 其他代码 ...添加响应缓存为了提高性能可以添加响应缓存npm install apicache --save然后在应用入口中使用// app.js const apicache require(apicache); const cache apicache.middleware; // ... 其他代码 ... // 缓存GET请求5分钟 app.use(/api, cache(5 minutes)); // ... 其他代码 ...添加请求限流防止 API 滥用npm install express-rate-limit --save然后在应用入口中使用// app.js const rateLimit require(express-rate-limit); // ... 其他代码 ... // 创建限流中间件 const apiLimiter rateLimit({ windowMs: 15 * 60 * 1000, // 15分钟 max: 100, // 每个IP在windowMs时间内最多100个请求 message: 来自此IP的请求过多请稍后再试 }); // 应用限流中间件 app.use(/api, apiLimiter); // ... 其他代码 ...