如何编写自定义Mongoose插件实现S3对象返回签名URL?
解决Mongoose插件转换S3对象为签名URL的问题
核心思路
- 在插件初始化阶段遍历Schema,一次性收集所有标记了
signurl: true的字段,避免每次查询重复遍历Schema - 在
find/findOne的post钩子中,对查询返回的单条或多条文档,针对收集到的字段生成S3签名URL并替换原字段值
完整插件实现
const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3"); const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); // 初始化S3客户端(建议通过环境变量配置密钥,避免硬编码) const s3Client = new S3Client({ region: process.env.AWS_REGION, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY } }); const signURL = (schema) => { // 收集所有需要生成签名URL的字段路径 const signableFields = []; schema.eachPath((path, schemaType) => { if (schemaType.options.signurl === true) { signableFields.push(path); } }); // 处理find和findOne查询后的文档 schema.post(['find', 'findOne'], async function(docs) { // 单文档处理逻辑 const processSingleDoc = async (doc) => { for (const field of signableFields) { const s3Key = doc[field]; if (!s3Key) continue; // 生成有效期1小时的S3签名URL const command = new GetObjectCommand({ Bucket: process.env.AWS_S3_BUCKET, Key: s3Key }); const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 }); doc[field] = signedUrl; } }; // 区分数组(find返回)和单对象(findOne返回)处理 if (Array.isArray(docs)) { await Promise.all(docs.map(processSingleDoc)); } else if (docs) { await processSingleDoc(docs); } }); }; module.exports = signURL;
使用示例(与你的原代码兼容)
const userSchema = mongoose.Schema({ profilePhoto: { type: String, signurl: true, }, // 可扩展添加其他需要处理的字段 coverImage: { type: String, signurl: true, } }); userSchema.plugin(signURL);
关键细节说明
- 用
schema.eachPath遍历Schema字段是获取自定义signurl选项的正确方式,这一步在插件初始化时仅执行一次 - post钩子中异步处理签名URL生成,注意用
Promise.all批量处理数组文档提升效率 - S3签名URL的有效期、存储桶等参数可根据业务需求调整
内容的提问来源于stack exchange,提问作者Mervyn Lee
相关产品推荐
相关产品推荐

