使用@aws-sdk/client-s3上传至AWS S3后无法获取自定义元数据问题求助
嗨各位开发者,我最近碰到个头疼的问题:用@aws-sdk/client-s3往S3上传媒体文件(图片、视频)时,明明在PutObjectCommand里设置了自定义元数据(title和description),上传也显示成功,桶里能看到文件,但之后用HeadObjectCommand拉取元数据时,返回的Metadata属性一直是空的,折腾好久没解决,来求助大家!
问题详细描述
上传时我会把title和description转成小写后放到Metadata参数里,上传完成后调用fetchMedia方法拉取文件列表,再对每个文件用HeadObjectCommand获取元数据,但每次拿到的Metadata都是空对象。
我的代码
import { S3Client, PutObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3'; import { useState } from 'react'; // 补充缺失的useState导入 const s3Client = new S3Client({ region: 'MyRegion', credentials: { accessKeyId: 'MyAccessKey', secretAccessKey: 'MySecretAccessKey', }, }); export const BUCKET_BASE_URL = "MyBucketBaseURL"; export const getHrefAgainstKey = (key) => { return `${BUCKET_BASE_URL}${key}`; }; const S3MediaUploader = () => { const [file, setFile] = useState(null); const [metadata, setMetadata] = useState({ title: '', description: '' }); const [uploadedMedia, setUploadedMedia] = useState([]); const [loading, setLoading] = useState(false); const bucketName = 'MyBucketName'; const handleFileChange = (e) => { setFile(e.target.files[0]); }; const handleMetadataChange = (e) => { setMetadata({ ...metadata, [e.target.name]: e.target.value }); }; const handleUpload = async () => { if (!file) { alert('Please select a file.'); return; } setLoading(true); const reader = new FileReader(); reader.onload = async (event) => { const arrayBuffer = event.target.result; const uint8Array = new Uint8Array(arrayBuffer); const params = { Bucket: bucketName, Key: file.name, Body: uint8Array, Metadata: { title: metadata.title.toLowerCase(), description: metadata.description.toLowerCase(), }, ACL: "public-read", ContentType: file.type, }; try { const response = await s3Client.send(new PutObjectCommand(params)); alert('File uploaded successfully!'); setFile(null); setMetadata({ title: '', description: '' }); fetchMedia(); } catch (error) { console.error('Error uploading file:', error); alert('Error uploading file.'); } finally { setLoading(false); } }; reader.onerror = () => { console.error('Error reading file.'); setLoading(false); }; reader.readAsArrayBuffer(file); }; const fetchMedia = async () => { setLoading(true); try { const response = await s3Client.send(new ListObjectsV2Command({ Bucket: bucketName })); if (response.Contents) { const media = await Promise.all( response.Contents.map(async (item) => { const getObjectParams = { Bucket: bucketName, Key: item.Key, }; const metadataResponse = await s3Client.send(new HeadObjectCommand(getObjectParams)); // 这里拿到的metadataResponse.Metadata始终是空的 return { key: item.Key, href: getHrefAgainstKey(item.Key), url: BUCKET_BASE_URL, metadata: metadataResponse.Metadata, }; }) ); setUploadedMedia(media); } else { setUploadedMedia([]); } } catch (error) { console.error('Error fetching media:', error); } finally { setLoading(false); } }; };
IAM用户权限策略
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:*", "s3tables:*", "iam:PassRole" ], "Resource": "*" } ] }
依赖版本
"dependencies": { "@aws-sdk/client-s3": "^3.749.0", }
我已经试过的排查步骤
- 确认上传过程无报错,文件确实成功存入S3桶
- 试过
HeadObjectCommand和GetObjectCommand两种方式获取元数据,结果一致为空 - 检查IAM权限,已配置全S3访问权限,排除权限问题
- 按照S3要求将元数据key转为小写,避免格式不符合规范
期望与实际行为
- 期望:调用
HeadObjectCommand或GetObjectCommand时,返回结果的Metadata属性包含上传时设置的title和description值 - 实际:Metadata属性始终为空对象
给你几个排查方向(我之前踩过类似的坑)
先去S3控制台确认元数据是否真的被存储
登录AWS控制台打开你的S3桶,找到上传的文件,查看「元数据」详情页。如果控制台里也看不到自定义元数据,那问题出在上传环节;如果控制台里有,那就是获取代码的问题。简化上传参数测试
试试去掉ACL参数(或改成private),同时传最基础的元数据(比如title: "test-title"),排除特殊配置或特殊字符的影响。另外,S3自定义元数据的key只能包含字母、数字、连字符、下划线、点和斜杠,别带空格或其他特殊字符。直接传File对象而非转Uint8Array
你现在用FileReader把文件转成Uint8Array上传,其实PutObjectCommand本身支持直接接收File对象,试试修改上传参数:const params = { Bucket: bucketName, Key: file.name, Body: file, // 直接传file对象 Metadata: { title: metadata.title.toLowerCase(), description: metadata.description.toLowerCase(), }, ACL: "public-read", ContentType: file.type, };避免FileReader可能带来的未知问题。
升级SDK版本
你用的3.749.0不算太旧,但AWS SDK偶尔会有小bug,试试升级到最新版的@aws-sdk/client-s3,说不定能解决问题。打印完整的响应对象
在fetchMedia里把整个metadataResponse打印出来,确认元数据是不是藏在其他属性里,比如有没有可能在ResponseMetadata或者其他嵌套结构中?
备注:内容来源于stack exchange,提问作者Aman

