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

如何用Firebase云函数实现特定文件上传自动触发邮件发送

Firebase云函数自动触发邮件问题解决思路

核心错误定位

你写的云函数触发路径语法错误:functions.storage.bucket("Resumes/{uID}/Resume.pdf") 中,bucket() 方法的参数是存储桶名称(比如默认桶为 你的项目ID.appspot.com),而非文件路径。文件路径的匹配规则应该放在 object() 方法的参数里,或者在函数内部判断。

修复步骤与完整实现

1. 修正触发路径写法

正确的触发路径应该用通配符指定文件位置,同时通过 context.params 获取 uID:

exports.sendAutomatedResume = functions.storage.object("UserFiles/{uID}/Resume.pdf")
  .onFinalize(async (object, context) => {
    const uID = context.params.uID; // 直接从上下文获取用户ID
    // 后续逻辑...
  });

如果需要指定特定存储桶,写法为:

exports.sendAutomatedResume = functions.storage.bucket("your-custom-bucket-name")
  .object("UserFiles/{uID}/Resume.pdf")
  .onFinalize(async (object, context) => {
    // 逻辑...
  });

2. 获取用户文件夹下所有文件的签名下载链接

前端用 getDownloadURL(),云函数服务端需要生成带签名的临时下载链接(有效期可自定义),同时用 Promise.all 处理异步请求:

const bucket = admin.storage().bucket();
// 获取该用户文件夹下的所有文件
const [files] = await bucket.getFiles({ prefix: `UserFiles/${uID}/` });

// 批量生成签名URL
const documentURLs = await Promise.all(
  files.map(async (file) => {
    const [url] = await file.getSignedUrl({
      action: "read",
      expires: "1h" // 链接有效期1小时,可调整
    });
    return url;
  })
);

3. 获取用户邮箱与姓名

云函数无法直接获取前端的 userInfo,需要通过 uID 从Firebase Auth或Firestore中拉取用户信息:

// 从Auth获取用户信息
const userRecord = await admin.auth().getUser(uID);
const userEmail = userRecord.email;
const userName = userRecord.displayName || "匿名用户"; // 处理无昵称的情况

4. 集成Nodemailer发送邮件

复用你原有手动触发的邮件逻辑,直接在云函数中配置Nodemailer发送:

// 初始化Nodemailer transporter(建议用环境变量配置密钥,不要硬编码)
const transporter = nodemailer.createTransport({
  service: "Gmail",
  auth: {
    user: functions.config().gmail.user,
    pass: functions.config().gmail.pass
  }
});

// 组装邮件内容
const mailOptions = {
  from: "你的平台名称 <your-email@example.com>",
  to: "目标收件邮箱@example.com", // 替换为实际收件邮箱
  replyTo: userEmail,
  subject: `用户${userName}的简历文件包`,
  html: `
    <p>用户${userName}上传了以下文件:</p>
    <ul>
      ${documentURLs.map(url => `<li><a href="${url}">点击下载</a></li>`).join("")}
    </ul>
  `
};

// 发送邮件
await transporter.sendMail(mailOptions);
functions.logger.info(`邮件已成功发送给用户${uID}`);

5. 完整云函数代码

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const nodemailer = require("nodemailer");

admin.initializeApp();

// 初始化邮件发送器(通过Firebase环境变量配置,避免硬编码)
const transporter = nodemailer.createTransport({
  service: "Gmail",
  auth: {
    user: functions.config().gmail.user,
    pass: functions.config().gmail.pass
  }
});

exports.sendAutomatedResume = functions.storage.object("UserFiles/{uID}/Resume.pdf")
  .onFinalize(async (object, context) => {
    const uID = context.params.uID;
    
    try {
      // 1. 获取用户文件夹下所有文件的签名URL
      const bucket = admin.storage().bucket();
      const [files] = await bucket.getFiles({ prefix: `UserFiles/${uID}/` });
      const documentURLs = await Promise.all(
        files.map(async (file) => {
          const [url] = await file.getSignedUrl({
            action: "read",
            expires: "1h"
          });
          return url;
        })
      );

      // 2. 获取用户信息
      const userRecord = await admin.auth().getUser(uID);
      const userEmail = userRecord.email;
      const userName = userRecord.displayName || "用户";

      // 3. 发送邮件
      const mailOptions = {
        from: "你的平台 <your-email@example.com>",
        to: "收件邮箱@example.com",
        replyTo: userEmail,
        subject: `${userName}的简历文件已上传`,
        html: `
          <p>您好,用户${userName}提交了以下文件:</p>
          <ul>
            ${documentURLs.map(url => `<li><a href="${url}">下载文件</a></li>`).join("")}
          </ul>
        `
      };

      await transporter.sendMail(mailOptions);
      functions.logger.info(`邮件发送成功,用户ID:${uID}`);
      return null;

    } catch (error) {
      functions.logger.error(`处理失败:${error.message}`, error);
      throw error; // 抛出错误触发云函数重试(可选)
    }
  });

额外注意事项

  • 环境变量配置:不要硬编码邮箱密码,通过命令行设置:
    firebase functions:config:set gmail.user="your-email@gmail.com" gmail.pass="your-app-password"
    
  • 权限配置:确保云函数服务账号有Storage读取权限和Auth用户读取权限(默认服务账号已有基础权限,若需调整可在IAM中配置)。
  • 链接有效期:签名URL的有效期根据需求调整,避免过长导致安全风险。
  • 错误重试:云函数默认会对失败的请求重试,若不需要可在代码中处理错误后直接返回,不抛出异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 17:10:28