Easton Notes
知识八股Express

业务响应与资源处理

在 Express 中如何处理静态文件的缓存

回答重点

在 Express 中处理静态文件的缓存可以通过使用 express.static 中间件并设置适当的缓存头来实现。关键步骤包括:

1)安装并引入 express 模块。 2)使用 express.static 中间件,并通过配置对象设置 maxAge 选项,以定义静态文件的缓存过期时间。

具体代码如下:

const express = require('express');
const path = require('path');
const app = express();
// 设置静态文件目录和缓存时间 app.use(express.static(path.join(__dirname, 'public'), {
maxAge: '1d'
// 静态文件缓存时间为1天
}
));
app.listen(3000, () => {
  console.log('Server is running on port 3000');
}
);

在这个例子中,public 目录中的静态文件将被缓存一天 (1d 表示一天的时间)。

扩展知识

1)缓存控制头: HTTP 缓存主要依赖于 Cache-Control 头部。常见的配置如:

app.use(express.static('public', {
  maxAge: '1d',
  // 客户端缓存 1 天 setHeaders: (res, path) => {
  // 设置其他的缓存头部 res.setHeader('Cache-Control', 'public, max-age=86400');
}
}
));

这个代码不仅设置了 maxAge ,还自定义了 Cache-Control 头部。

2)条件请求和验证: HTTP 条件请求头部如 ETagLast-Modified 允许浏览器只请求发生变化的资源。Express 静态中间件会自动处理这些头部。

// 自动生成 ETag 和 Last-Modified app.use(express.static('public', {
etag: true, lastModified: true
}
));

3)缓存优化策略: 在实际代码中,可以通过结合短时间缓存和版本号来使得缓存管理更加高效。例如,文件可以以 filename.[hash].ext 的方式命名,当文件内容变化时,生成不同的哈希值,确保变化的文件能被浏览器重新请求。

4)内容分发网络(CDN): 如果你的网站流量很大,建议考虑使用 CDN 来托管和缓存静态文件。CDN 会自动处理缓存并优化内容的传输速度。

Express 中如何处理文件下载功能?

回答重点

在 Express 中处理文件下载功能相对简单,主要通过 res.download() 方法就能实现。下面是一个快速场景示例:

1)首先,确保你安装并引入了 express 模块。

const express = require('express');
const app = express();
const path = require('path');

2)设置一个路由来处理文件下载请求。在这个请求处理器内,调用 res.download() 方法,指定要下载的文件路径。

app.get('/download', (req, res) => {
  const file = path.join(__dirname, 'files', 'example.txt');
  // 假设你的文件在这个路径 res.download(file, (err) => {
  if (err) {
    console.error('File download error:', err);
    res.status(500).send('Error in file download');
  } else {
  console.log('File download success');
}
}
);
}
);

3)启动服务器,监听端口。

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

这样,访问 http://localhost:3000/download 时,浏览器会自动下载 example.txt 文件。

扩展知识

这个例子主要展示了基本的文件下载流程。以下内容可以帮助你更深入理解和扩展这个功能:

1)文件路径的安全性: 需要注意,直接暴露文件路径可能带来一定的安全风险。建议使用文件别名或动态生成文件路径。

2)传输大文件: 对于传输大文件,你可能需要考虑流的方式。比如,使用 stream 模块来优化资源使用,并避免内存占用过高:

const fs = require('fs');
app.get('/download', (req, res) => {
  const filePath = path.join(__dirname, 'files', 'example.txt');
  const stat = fs.statSync(filePath);
  res.writeHead(200, {
    'Content-Type': 'application/octet-stream', 'Content-Length': stat.size
  }
  );
  const readStream = fs.createReadStream(filePath);
  readStream.pipe(res);
}
);

3)权限系统: 针对不同用户或角色,可能需要设置访问和下载权限。可以结合 JWT 或其他认证机制来实现用户认证和权限控制。

4)文件压缩: 如果是多文件下载,一种常见做法是先将这些文件压缩成一个 zip 文件,再提供下载。这可以利用 archiver 模块来实现:

const archiver = require('archiver');
app.get('/download-zip', (req, res) => {
  const archive = archiver('zip', {
    zlib: {
      level: 9
    }
  }
  );
  res.attachment('archive.zip');
  archive.pipe(res);
  archive.directory('files/', false);
  archive.finalize().catch((err) => {
    console.error('Archive error:', err);
    res.status(500).send('Error in creating archive');
  }
  );
}
);

5)断点续传: 对于大型文件断点续传功能,可以加上 HTTP Range 头部信息并处理相应的逻辑。当然,这个比简单下载要复杂得多,需要比较深入研究 HTTP 协议和文件处理技术。

Express 中如何处理大规模的文件存储和管理?

回答重点

在使用 Express 处理大规模的文件存储和管理时,可以利用中间件处理上传、MongoDB/GridFS 来存储文件、和阿里云OSS或AWS S3等对象存储服务结合来优化存储解决方案。

具体方法可以总结为以下几步:

1)利用 multer 处理文件上传:multer 是一款非常强大的 Node.js 中间件,可以轻松处理 multipart/form-data 类型的表单数据,用于上传文件。 2)选择合适的存储方法:对于小文件,可以直接存储到数据库或者文件系统。对于大文件,推荐使用对象存储服务如阿里云OSS或AWS S3。 3)使用 MongoDB GridFS:如果选择自建存储服务,MongoDB 的 GridFS 可以很好地处理大文件存储,适合需要高性能和高可靠性的场景。 4)结合缓存层:通过缓存服务如 Redis 进行缓存,可以减少对存储服务的请求频次,提高系统的响应速度。

扩展知识

下面是对这些重点的详细解释和代码示例:

1)利用 multer 处理文件上传: Multers 是一个用于处理 multipart/form-data 的中间件,处理文件上传非常简单。简单示例如下:

const express = require('express');
const multer = require('multer');
const app = express();
// 设置上传文件夹
const upload = multer({
  dest: 'uploads/'
}
);
app.post('/upload', upload.single('file'), (req, res) => {
  // 文件信息保存在 req.file console.log(req.file);
  res.send('File uploaded!');
}
);
app.listen(3000, () => {
  console.log('Server started on http://localhost:3000');
}
);

2)选择合适的存储方法: 对于文件存储,一般有两种选择:文件系统存储和云存储。

  • 文件系统存储简单易用,但不适合分布式系统,扩展性有限。
  • 云存储如阿里云 OSS 或 AWS S3,支持大规模存储和高并发访问,同时具备良好的扩展性和可靠性。

3)使用 MongoDB GridFS: GridFS 是 MongoDB 附带的一种分布式文件系统,专为存储大文件设计。下面是简单的 GridFS 使用示例:

const mongoose = require('mongoose');
const Grid = require('gridfs-stream');
const multer = require('multer');
const {
  GridFsStorage
} = require('multer-gridfs-storage');
mongoose.connect('mongodb://localhost:27017/yourdb', {
  useNewUrlParser: true, useUnifiedTopology: true
}
).then(() => {
  console.log('MongoDB connected');
}
).catch(err => console.log(err));
const conn = mongoose.connection;
let gfs;
conn.once('open', () => {
  gfs = Grid(conn.db, mongoose.mongo);
  gfs.collection('uploads');
}
);
const storage = new GridFsStorage({
  url: 'mongodb://localhost:27017/yourdb', file: (req, file) => {
    return {
      bucketName: 'uploads', filename: file.originalname
    }
    ;
  }
}
);
const upload = multer({
  storage
}
);
app.post('/upload', upload.single('file'), (req, res) => {
  res.send('File uploaded to GridFS');
}
);

4)结合缓存层: 利用 Redis 等缓存服务,可以提高文件访问的性能。使用时,可以先检查缓存是否命中,如果未命中再从存储服务中获取文件并缓存,这样可以减少存储服务的压力。

const redis = require('redis');
const client = redis.createClient();
app.get('/file/:id', async (req, res) => {
  const fileId = req.params.id;
  // 尝试从缓存中获取文件 client.get(fileId, (err, data) => {
  if (data) {
    return res.send(data);
    // 返回缓存中的数据
  } else {
  // 从存储服务获取文件并缓存 gfs.files.findOne({
  _id: mongoose.Types.ObjectId(fileId)
}
, (err, file) => {
  if (!file || file.length === 0) {
    return res.status(404).json({
      err: 'No file exists'
    }
    );
  }
  const readstream = gfs.createReadStream(file.filename);
  readstream.on('data', (chunk) => {
    client.setex(fileId, 3600, chunk);
  }
  );
  readstream.pipe(res);
}
);
}
}
);
}
);

这些方法结合起来,可以有效地解决在 Express 中处理大规模文件存储和管理的问题,具备良好的性能和扩展性。

在 Express 中如何实现数据的分页和排序?

回答重点

在 Express 中实现数据分页和排序通常需要以下几个步骤:

1)接收客户端的分页和排序参数。 2)根据这些参数从数据库中查询数据。 3)返回分页后的数据和相关的分页信息。

具体步骤示例:

  1. 首先我们需要建立一个 Express 应用,并确保安装了数据库驱动,例如 MongoDB 可以使用 mongoose
  2. 在路由处理函数中获取分页和排序参数,例如 pagelimitsortBy 和 sortOrder
  3. 使用这些参数构建数据库查询,获取相关数据并进行分页和排序处理。
  4. 将处理结果返回给客户端。

示例代码:

const express = require("express");
const mongoose = require("mongoose");
const app = express();
const PORT = 3000;
// 假设有个简单的 User 模型
const User = mongoose.model("User", new mongoose.Schema({
  name: String, age: Number, email: String,
}
));
// 连接数据库 mongoose.connect("mongodb://localhost:27017/mydatabase", {
useNewUrlParser: true, useUnifiedTopology: true
}
);
// 路由处理函数实现分页和排序 app.get("/users", async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const sortField = req.query.sortBy || 'name';
const sortOrder = req.query.sortOrder === 'desc' ? -1 : 1;
try {
  const users =
  await User.find() .skip((page - 1) * limit) .limit(limit) .sort({
    [sortField]: sortOrder
  }
  );
  const total =
  await User.countDocuments();
  const totalPages = Math.ceil(total / limit);
  res.json({
    users, total, totalPages, currentPage: page
  }
  );
}
catch (error) {
  res.status(500).json({
    error: error.message
  }
  );
}
}
);
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
}
);

扩展知识

1)数据库驱动选择:在上面的示例中,我使用了 MongoDB 以及 mongoose 库进行数据操作。如果你使用不同的数据库例如 MySQL,SQLite 等,可以参考对应的驱动文档,例如 sequelize 等。

2)分页参数的处理:分页参数如 pagelimit 通常从查询参数中获取,同时要提供默认值,防止参数缺失导致异常。

3)排序参数的灵活性:在实际应用中,排序字段和排序顺序也应该尽量灵活和健壮。例如,你可以进一步优化参数验证,确保用户只能按正常字段排序,避免 SQL 注入风险。

4)前端配合:前端与分页数据的结合也很重要,前端需要根据后端返回的分页信息(如总页数、当前页)来实现相应的分页逻辑,一般需要配合一些分页组件来实现顺畅的用户体验。

5)性能考虑:分页与排序涉及到数据库查询性能,可能需要合理建立数据库索引,避免大数据量情况下的查询效率问题。此外,将分页逻辑尽量交给数据库处理,而不是在应用内存中处理,能减少服务器内存占用并提高性能。

Express 中如何实现自定义的错误页面

回答重点

在 Express 中实现自定义的错误页面,你需要通过中间件来捕获错误,并渲染对应的错误视图。以下是一个基本的实现步骤:

1)在主应用文件(如 app.jsserver.js)中,通过中间件捕获 404 错误:

app.use((req, res, next) => {
  res.status(404);
  res.render('404', {
    title: "404: Page Not Found"
  }
  );
}
);

2)捕获其他错误并渲染自定义错误页面:

app.use((err, req, res, next) => {
  res.status(err.status || 500);
  res.render('error', {
    title: "500: Internal Server Error", message: err.message, error: err
  }
  );
}
);

通过这些中间件,你可以自定义不同错误代码的页面内容。

扩展知识

1)错误处理中间件

  • 在 Express 中,错误处理中间件需要四个参数:err, req, res, next。这是为区分普通中间件和错误处理中间件而设计的。通过这种方式,Express 可以根据参数数量来决定调用哪种类型的中间件。

2)设置 views 模板引擎

  • 在使用 res.render 方法渲染页面时,通常需要配置模板引擎。这可以通过如下代码实现:
const express = require('express');
const app = express();
app.set('view engine', 'pug');
// 例如使用 Pug 模板引擎 app.set('views', path.join(__dirname, 'views'));

3)自定义错误页面的具体内容

  • 在 views 文件夹中创建相应的模板文件,如 404.pug 和 error.pug
// in views/404.pug h1 404: Page Not Found p The page you are looking for does not exist.
// in views/error.pug h1 #{title} p #{message}

4)在开发和生产环境中的不同处理

  • 在开发环境中,详细的错误信息有助于调试,而在生产环境中,给用户显示详细的错误信息可能不安全。因此,可以通过 app.get('env') 来决定展示的内容:
app.use((err, req, res, next) => {
  res.status(err.status || 500);
  res.render('error', {
    title: "500: Internal Server Error", message: err.message, error: app.get('env') === 'development' ? err : {}
  }
  );
}
);

Express 中的错误处理中间件如何编写?

回答重点

在 Express 中,错误处理中间件的编写稍有不同于普通中间件。其独特之处在于它必须接受四个参数:errreqresnext。一个简单的错误处理中间件示例如下:

function errorHandler(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
} app.use(errorHandler);

在这个例子中,errorHandler 函数先打印错误堆栈信息,然后返回 500 状态码和错误信息。app.use 是用来注册这个中间件的。

扩展知识

下面可以扩展了解一些常用的错误处理策略,以及在不同场景下如何优化错误处理。

1) 捕获异步错误:在 Express 4.x 版本中,原生支持同步错误的捕获,但对于异步错误(例如,Promise 中的错误)就需特别处理。通常,会用到一个包装器函数,例如:

const asyncHandler = fn => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
// 用法示例 app.get('/someRoute', asyncHandler(async (req, res, next) => {
const result =
await someAsyncOperation();
res.json(result);
}
));

2) 分类处理错误:有时候,我们可能需要分类处理不同类型的错误。例如,400 错误是一种客户端错误,可以用来表示请求无效。我们可以在错误处理中间件中添加逻辑判断:

function errorHandler(err, req, res, next) {
  if (err.name === 'ValidationError') {
    return res.status(400).json({
      error: err.message
    }
    );
  } console.error(err.stack);
  res.status(500).send('Something broke!');
}

3) 自定义错误类:为了更灵活地管理和识别错误,我们可以创建自定义的错误类:

class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
  }
}
// 使用自定义错误类 app.get('/anotherRoute', (req, res, next) => {
const error = new AppError('Custom error occurred', 400);
next(error);
}
);

4) 统一错误响应格式:在实际的开发中,保持错误响应的格式统一是非常有用的。这有助于前端应用更容易解析和处理错误。例如,可以统一返回一个 JSON 对象,包含 statusmessage 字段:

function errorHandler(err, req, res, next) {
  res.status(err.statusCode || 500).json({
    status: 'error', message: err.message || 'Internal Server Error'
  }
  );
}

On this page