You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node.js中生成带过期时间的一次性下载链接?

实现Node.js一次性过期下载链接方案

核心思路是生成带签名与过期时间的令牌URL,后端通过验证令牌的有效性(是否过期、是否已使用)来决定是否允许文件下载,具体步骤如下:

1. 生成带过期时间的下载令牌

使用JWT(JSON Web Token)对文件路径、过期时间进行签名,避免令牌被篡改。JWT自带过期验证逻辑,无需额外存储过期时间(若需严格一次性使用,后续再添加令牌黑名单)。

先安装依赖:

npm install jsonwebtoken nodemailer express

生成链接的代码:

const jwt = require('jsonwebtoken');
// 生产环境务必用环境变量存储密钥,不要硬编码
const SECRET_KEY = process.env.JWT_SECRET || 'your_strong_custom_secret';

// 生成下载链接,expiresIn支持'1d'(1天)、'7d'(1周)等格式
function generateDownloadUrl(filePath, expiresIn = '1d') {
  const token = jwt.sign(
    { filePath }, // 令牌中携带文件路径
    SECRET_KEY,
    { expiresIn }
  );
  // 替换为你的实际域名和下载路由
  return `http://your-domain.com/download?token=${token}`;
}

2. 创建下载验证路由

后端接收令牌后,先验证其有效性,再检查是否已被使用(实现一次性),验证通过则触发文件下载:

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

// 生产环境建议用Redis存储已使用令牌,避免服务器重启后数据丢失
const usedTokens = new Set();

app.get('/download', (req, res) => {
  const { token } = req.query;

  if (!token) {
    return res.status(400).send('缺少下载令牌');
  }
  if (usedTokens.has(token)) {
    return res.status(403).send('链接已失效');
  }

  try {
    const decoded = jwt.verify(token, SECRET_KEY);
    const fullFilePath = path.resolve(__dirname, decoded.filePath);

    // 标记令牌已使用,确保一次性
    usedTokens.add(token);

    // 触发文件下载,res.download自动处理文件头
    res.download(fullFilePath, (err) => {
      if (err) res.status(404).send('文件不存在');
    });
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      return res.status(403).send('链接已过期');
    }
    return res.status(403).send('无效的下载链接');
  }
});

3. 发送下载链接邮件

用nodemailer实现邮件发送,将生成的链接推送给用户:

const nodemailer = require('nodemailer');

// 根据你的邮箱服务商配置SMTP,比如Gmail、QQ邮箱等
const transporter = nodemailer.createTransport({
  service: 'Gmail',
  auth: {
    user: process.env.EMAIL_USER || 'your-email@gmail.com',
    pass: process.env.EMAIL_PASS || 'your-app-password' // 注意用授权码而非邮箱密码
  }
});

async function sendDownloadEmail(userEmail, downloadUrl) {
  const mailOptions = {
    from: '你的服务名称 <your-email@gmail.com>',
    to: userEmail,
    subject: '您的专属文件下载链接',
    html: `
      <p>点击下方链接下载文件:</p>
      <a href="${downloadUrl}" target="_blank">${downloadUrl}</a>
      <p>链接1天后过期,且仅可使用一次</p>
    `
  };

  try {
    await transporter.sendMail(mailOptions);
    console.log(`下载链接已发送至${userEmail}`);
  } catch (err) {
    console.error('邮件发送失败:', err);
  }
}

4. 整合使用示例

// 处理用户的下载请求
app.post('/request-download', express.json(), async (req, res) => {
  const { userEmail } = req.body;
  // 替换为你实际的文件路径,注意限制目录范围避免安全风险
  const targetFile = './public/assets/example.pdf';

  const downloadUrl = generateDownloadUrl(targetFile, '1d');
  await sendDownloadEmail(userEmail, downloadUrl);

  res.send('下载链接已发送到您的邮箱,请查收');
});

app.listen(3000, () => {
  console.log('服务器运行在http://localhost:3000');
});

关键注意事项

  • 密钥安全:生产环境必须用环境变量存储JWT密钥和邮箱密码,绝不能硬编码到代码中
  • 令牌存储:内存Set仅适合测试,生产环境用Redis或数据库存储已使用的令牌,保证服务器重启后数据不丢失
  • 文件权限:限制令牌中的文件路径只能访问指定目录,防止恶意用户通过篡改令牌访问敏感文件
  • 邮件配置:不同邮箱服务商的SMTP规则不同,比如QQ邮箱需要开启SMTP服务并使用授权码登录

内容的提问来源于stack exchange,提问作者M A

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 07:31:06