MST

星途 面试题库

面试题:Node.js Express 处理复杂场景下 GET 和 POST 请求的架构设计

假设你正在开发一个高并发的电商 API 服务,其中涉及到多种复杂的 GET 和 POST 请求场景,例如商品查询(GET)、订单创建(POST)、用户登录(POST 且需处理 Token 验证)等。请设计一个合理的 Express 架构来处理这些请求,包括目录结构、中间件分层、错误处理机制等方面。并说明如何在这种复杂场景下确保请求处理的高效性、稳定性和可扩展性,给出相应的设计思路和关键代码片段。
39.0万 热度难度
前端开发Node.js

知识考点

AI 面试

面试题答案

一键面试

目录结构

project/
│
├── app.js
├── routes/
│   ├── productRoutes.js
│   ├── orderRoutes.js
│   ├── userRoutes.js
│
├── middlewares/
│   ├── authMiddleware.js
│   ├── errorMiddleware.js
│   ├── loggerMiddleware.js
│
├── controllers/
│   ├── productController.js
│   ├── orderController.js
│   ├── userController.js
│
├── models/
│   ├── productModel.js
│   ├── orderModel.js
│   ├── userModel.js
│
├── config/
│   ├── database.js
│   ├── config.js
│
└── public/
  1. app.js: 主应用文件,用于初始化 Express 应用,引入中间件、路由等。
  2. routes: 存放各个模块的路由文件,将不同功能的路由分离。
  3. middlewares: 包含各种中间件,如身份验证、错误处理、日志记录等。
  4. controllers: 处理业务逻辑,调用模型和数据库操作,响应请求。
  5. models: 定义数据库模型,与数据库进行交互。
  6. config: 配置文件,如数据库连接配置、应用配置等。
  7. public: 存放静态资源,如图片、CSS、JavaScript 等。

中间件分层

  1. 全局中间件
    • 日志记录中间件:在 middlewares/loggerMiddleware.js 中定义,用于记录每个请求的基本信息,如请求方法、URL 等。
    const logger = (req, res, next) => {
        console.log(`${req.method} ${req.url}`);
        next();
    };
    module.exports = logger;
    
    • 错误处理中间件:在 middlewares/errorMiddleware.js 中定义,捕获整个应用中未处理的异常并返回合适的错误响应。
    const errorHandler = (err, req, res, next) => {
        console.error(err.stack);
        res.status(500).send('Something went wrong!');
    };
    module.exports = errorHandler;
    
  2. 路由特定中间件
    • 身份验证中间件:在 middlewares/authMiddleware.js 中定义,用于验证用户登录状态和 Token。例如,对于用户登录后才能访问的路由,如订单创建和某些商品查询(可能涉及用户特定信息)。
    const jwt = require('jsonwebtoken');
    const auth = (req, res, next) => {
        const token = req.headers['authorization'];
        if (!token) return res.status(401).send('Access denied. No token provided.');
        try {
            const decoded = jwt.verify(token, process.env.JWT_SECRET);
            req.user = decoded;
            next();
        } catch (err) {
            res.status(400).send('Invalid token.');
        }
    };
    module.exports = auth;
    

错误处理机制

  1. 同步错误处理:在控制器函数中,如果发生同步错误,直接调用 next(err) 将错误传递给错误处理中间件。
// controllers/productController.js
const Product = require('../models/productModel');
const getProduct = async (req, res, next) => {
    try {
        const product = await Product.findById(req.params.id);
        if (!product) {
            throw new Error('Product not found');
        }
        res.send(product);
    } catch (err) {
        next(err);
    }
};
module.exports = { getProduct };
  1. 异步错误处理:对于异步操作,使用 try - catch 块捕获错误并传递给 next 函数。同时,在 app.js 中注册错误处理中间件,确保所有未处理的错误都能被捕获。
// app.js
const express = require('express');
const app = express();
const logger = require('./middlewares/loggerMiddleware');
const errorHandler = require('./middlewares/errorMiddleware');
const productRoutes = require('./routes/productRoutes');
const orderRoutes = require('./routes/orderRoutes');
const userRoutes = require('./routes/userRoutes');

app.use(logger);
app.use(express.json());

app.use('/products', productRoutes);
app.use('/orders', orderRoutes);
app.use('/users', userRoutes);

app.use(errorHandler);

const port = process.env.PORT || 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

确保高效性、稳定性和可扩展性的设计思路

  1. 高效性
    • 路由优化:将不同功能的路由分离,减少单个路由文件的复杂度,提高路由匹配速度。例如,在 routes 目录下分别定义 productRoutes.jsorderRoutes.jsuserRoutes.js
    • 数据库查询优化:在模型层对数据库查询进行优化,例如使用索引。在 models/productModel.js 中,可以为常用查询字段添加索引。
    const mongoose = require('mongoose');
    const productSchema = new mongoose.Schema({
        name: String,
        price: Number
    });
    productSchema.index({ name: 1 }); // 为name字段添加索引
    const Product = mongoose.model('Product', productSchema);
    module.exports = Product;
    
  2. 稳定性
    • 错误处理:完善的错误处理机制,包括全局错误处理中间件和控制器内的错误捕获,确保应用不会因为未处理的错误而崩溃。
    • 中间件验证:使用中间件进行输入验证、身份验证等,确保请求的合法性,减少异常情况的发生。
  3. 可扩展性
    • 模块化设计:通过目录结构的设计,将应用划分为不同的模块,如路由、控制器、模型等,便于添加新功能或修改现有功能。例如,添加新的商品属性时,只需在 models/productModel.js 和相关的控制器、路由中进行修改。
    • 微服务架构:随着业务的增长,可以将各个模块进一步拆分为微服务,实现独立部署和扩展。例如,将订单服务、用户服务、商品服务分别部署,通过 API 进行通信。

关键代码片段汇总

  1. app.js
const express = require('express');
const app = express();
const logger = require('./middlewares/loggerMiddleware');
const errorHandler = require('./middlewares/errorMiddleware');
const productRoutes = require('./routes/productRoutes');
const orderRoutes = require('./routes/orderRoutes');
const userRoutes = require('./routes/userRoutes');

app.use(logger);
app.use(express.json());

app.use('/products', productRoutes);
app.use('/orders', orderRoutes);
app.use('/users', userRoutes);

app.use(errorHandler);

const port = process.env.PORT || 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});
  1. routes/productRoutes.js
const express = require('express');
const router = express.Router();
const { getProduct } = require('../controllers/productController');

router.get('/:id', getProduct);

module.exports = router;
  1. controllers/productController.js
const Product = require('../models/productModel');
const getProduct = async (req, res, next) => {
    try {
        const product = await Product.findById(req.params.id);
        if (!product) {
            throw new Error('Product not found');
        }
        res.send(product);
    } catch (err) {
        next(err);
    }
};
module.exports = { getProduct };
  1. models/productModel.js
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
    name: String,
    price: Number
});
productSchema.index({ name: 1 });
const Product = mongoose.model('Product', productSchema);
module.exports = Product;
  1. middlewares/loggerMiddleware.js
const logger = (req, res, next) => {
    console.log(`${req.method} ${req.url}`);
    next();
};
module.exports = logger;
  1. middlewares/errorMiddleware.js
const errorHandler = (err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('Something went wrong!');
};
module.exports = errorHandler;
  1. middlewares/authMiddleware.js
const jwt = require('jsonwebtoken');
const auth = (req, res, next) => {
    const token = req.headers['authorization'];
    if (!token) return res.status(401).send('Access denied. No token provided.');
    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        req.user = decoded;
        next();
    } catch (err) {
        res.status(400).send('Invalid token.');
    }
};
module.exports = auth;