Skip to content
标签
欢迎扫码关注公众号

使用 S3 协议从前端上传文件到 OSS

本来是通过后端服务中转来上传文件到 OSS 的,但是为了降低后端服务的带宽流量和服务器资源消耗,决定改为通过前端直接上传到。

通过前端上传时必须要考虑鉴权的问题,公司使用的主要是七牛云,之前的项目中使用过通过 项目凭证 的方式:后端接口给前端提供一次性且带有效期的令牌,前端使用该令牌上传文件。

当前项目有些不一样,使用了 AWS S3 协议来兼容多个品牌的对象存储,所以最好是修改后仍然能兼容其它品牌。

文心一言:

对于 S3 上传,AWS 通常使用 预签名 URL签名策略 来实现。这些签名机制通常涉及 AWS Signature Version 4,这是一种用于对 AWS 请求进行身份验证的机制。

如果你想要生成一个预签名的 S3 URL 用于上传,你应该使用 aws-java-sdk-s3 库中的相关类。例如,你可以使用 AmazonS3 客户端的 generatePresignedUrl 方法来生成一个预签名的 PUT 请求 URL,该 URL 允许用户在指定的时间范围内上传文件到 S3。

根据文心一言的回答,有两种实现方式:预签名 URL签名策略

签名策略 的方案感觉有些类似于七牛云的项目凭证,但是没有找到具体的 API 和示例代码。

七牛云的开发者文档(AWS SDK for Java)中提供了 预签名 URL 方式的示例代码,只是使用的依赖和项目中的不一样。项目中使用的是 aws-java-sdk-s3:1.12.540 ,在 com.amazonaws.services.s3.AmazonS3 类中提供了 generatePresignedUrl 方法来生成预签名的 URL。

示例代码:

java
/**
 * 获取预上传 URL 链接
 *
 * @param objectKey 对象 KEY
 * @param second    授权时间
 */
public String getPreuploadUrl(String objectKey, Integer second) {
    GeneratePresignedUrlRequest generatePresignedUrlRequest =
        new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey)
            .withMethod(HttpMethod.PUT)
            .withExpiration(new Date(System.currentTimeMillis() + 1000L * second));
    URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
    return url.toString();
}

七牛云获取的预签名 URL 结构:

txt
https://test-domain.s3.cn-south-1.qiniucs.com/test/2024/05/30/cab0fe332cd843289142807feb2512e2.svg
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Date=20240530T092929Z
&X-Amz-SignedHeaders=host
&X-Amz-Expires=1199
&X-Amz-Credential=azq7eaI1pgl4N78TRBmV4zdV5WH-_U9QI52jn12d%2F20240530%2F%2Fs3%2Faws4_request
&X-Amz-Signature=0db13fda7b4982a4c2704b79e84db4215bcdb1457ab0013da1c9954c76bad35d

最后前端通过发送 PUT 请求到该地址即可将文件上传到服务器。

bash
curl -X PUT --upload-file "<path/to/file>" "<presigned url>"

2024-08-09 追记

最近发现直接打开图片地址时,没做作为图片展示,而是展示一长串乱码的字符,但是在页面中通过 img 标签的 src 属性直接加载图片地址是可以正常展示图片的。

最后发现是对象存储中的文件类型不对,正常应该为 image/jpeg,但实却是 application/json(按理正常情况下二进制流上传时应该为 application/octet-stream 才对,不知道为什么是 application/json,怀疑是前端上传时指定了这个 Content-Type)。

好在创建预签名 URL 时支持指定 Content-Type,修改后的代码如下:

java
/**
 * 获取预上传 URL 链接
 *
 * @param objectKey   对象 KEY
 * @param second      授权时间
 * @param contentType Content-Type
 */
public String getPreuploadUrl(String objectKey, Integer second, String contentType) {
    GeneratePresignedUrlRequest generatePresignedUrlRequest =
        new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey)
            .withMethod(HttpMethod.PUT)
            .withContentType(contentType)
            .withExpiration(new Date(System.currentTimeMillis() + 1000L * second));
    URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
    return url.toString();
}

另外 Java 中可以通过如下代码根据文件名获取 Content-Type

java
import jakarta.activation.FileTypeMap;

String contentType = FileTypeMap.getDefaultFileTypeMap().getContentType(fileName);

这个方法默认情况下是加载这个包下面的 META-INF/mime.types 文件(文件内容如下),如果匹配不到则使用 application/octet-stream

txt
#
# Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Distribution License v. 1.0, which is available at
# http://www.eclipse.org/org/documents/edl-v10.php.
#
# SPDX-License-Identifier: BSD-3-Clause
#

#
# A simple, old format, mime.types file
#
text/html		html htm HTML HTM
text/plain		txt text TXT TEXT
image/gif		gif GIF
image/ief		ief
image/jpeg		jpeg jpg jpe JPG
image/tiff		tiff tif
image/png		png PNG
image/x-xwindowdump	xwd
application/postscript	ai eps ps
application/rtf		rtf
application/x-tex	tex
application/x-texinfo	texinfo texi
application/x-troff	t tr roff
audio/basic		au
audio/midi		midi mid
audio/x-aifc		aifc
audio/x-aiff            aif aiff
audio/x-mpeg		mpeg mpg
audio/x-wav             wav
video/mpeg		mpeg mpg mpe
video/quicktime		qt mov
video/x-msvideo		avi

Page Layout Max Width

Adjust the exact value of the page width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the page layout
A ranged slider for user to choose and customize their desired width of the maximum width of the page layout can go.

Content Layout Max Width

Adjust the exact value of the document content width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the content layout
A ranged slider for user to choose and customize their desired width of the maximum width of the content layout can go.