前端直传可以使用,但需要放在清晰的权限边界内,重点保护长期密钥、关键路径和业务访问控制。
浏览器代码、移动端包体、小程序脚本都运行在用户环境中。只要长期密钥进入这些环境,就意味着它可能被抓包、反编译、复制或复用,后续任何文件读写都很难真正收口。
文件路径本质上就是业务资源路径。把它完全交给客户端,相当于把关键目录写权限部分外放。正式系统里,关键路径应由业务后端根据组织、项目、用户和业务类型统一生成。
很多团队会把浏览器上传成功误认为业务完成,但正式流程还需要做文件确认、业务记录绑定、审计日志和后续下载授权,否则后面会出现“文件已上传,但系统里没有记录”的问题。
推荐由业务后端统一生成文件路径并签发上传授权。这样目录、权限和有效期都能被后端掌控。
export async function POST(request: Request) {
const body = await request.json();
const currentUser = await requireCurrentUser();
const objectKey = [
'uploads',
currentUser.organizationId,
new Date().toISOString().slice(0, 10),
String(Date.now()) + '-' + body.fileName,
].join('/');
const uploadUrl = await createUploadUrl({
bucket: 'media-assets',
objectKey,
contentType: body.contentType,
expiresIn: 600,
});
return Response.json({
success: true,
bucket: 'media-assets',
objectKey,
uploadUrl,
expiresIn: 600,
requestId: crypto.randomUUID(),
});
}浏览器只负责申请授权、上传文件和回传 objectKey,不直接碰长期凭证。
async function uploadAsset(file: File) {
const authResponse = await fetch('/api/storage/presign-upload', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
fileName: file.name,
contentType: file.type,
}),
});
const { uploadUrl, objectKey } = await authResponse.json();
await fetch(uploadUrl, {
method: 'PUT',
headers: {
'Content-Type': file.type,
},
body: file,
});
await fetch('/api/assets/confirm-upload', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ objectKey }),
});
}不是完全不能,而是不能直接持有长期密钥。前端可以使用短期 uploadUrl 或 downloadUrl,也可以消费业务 API,但长期权限必须由后端承接。
因为对象写入成功只说明文件存在了,不说明业务系统已经认领它。正式系统里,文件和业务记录必须在后端再完成一次绑定。
建议仍然保持同样边界。内部后台一旦做大、接入更多人员和环境,没有统一的签名与审计边界会很快失控。
文档中心帮助你完成接入,客户服务页帮助你了解采购、支持与服务信息。