使用 AWS S3 存储文件并整合到 Tio-Boot 项目中
本文将详细介绍如何在 AWS S3 上配置存储桶,并将其整合到 Tio-Boot 项目中,实现文件的上传、存储及相关信息的保存到数据库。本文包含所有必要的配置步骤、代码示例及相关说明,确保您能够顺利完成集成。
目录
AW3 S3 配置
创建存储桶
- 登录到 AWS 管理控制台。
- 转到 S3 服务页面,点击“创建存储桶”。
- 输入存储桶名称(如
rumiapp
)并选择区域(如us-west-1
)。 - 完成其他配置选项后,点击“创建存储桶”。
设置公开权限
为了让存储桶中的内容公开可访问,需要进行以下配置。但请注意,公开存储桶可能会带来数据安全风险,请在确保了解相关风险的情况下操作。
修改桶策略
使用 AWS 管理控制台
- 登录到 AWS 管理控制台并转到 S3 服务页面。
- 选择您要修改权限的存储桶。
修改桶策略
点击“权限”标签。
找到“桶策略”部分并点击“编辑”。
输入如下策略,允许公开访问(将
your-bucket-name
替换为您的存储桶名称):{ "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Handler": "s3:GetObject", "Resource": "arn:aws:s3:::your-bucket-name/*" } ] }
保存更改。
禁用阻止公共访问设置
- 在“权限”标签下,找到“阻止公共访问(Bucket settings for Block Public Access)”设置。
- 点击“编辑”。
- 确保所有选项均未勾选,或根据需要调整设置,以允许公共读取访问。
- 保存更改。
配置 CORS
如果需要跨域请求资源,请配置 CORS(跨域资源共享):
在“权限”标签下,找到“CORS 配置”。
点击“编辑”,然后输入以下配置,允许任何源的 GET 请求:
[ { "AllowedHeaders": [], "AllowedMethods": ["GET"], "AllowedOrigins": ["*"], "ExposeHeaders": [] } ]
保存更改。
获取访问密钥
为了通过编程方式访问 S3,需要获取 AWS 的访问密钥。
- 登录到 AWS IAM 控制台。
- 选择“我的安全凭证”。
- 在“访问密钥”部分,点击“创建访问密钥”。
- 下载或复制生成的访问密钥 ID 和秘密访问密钥,确保妥善保存。
整合到 Tio-Boot 项目中
以下步骤将指导您如何将 AWS S3 集成到 Tio-Boot 项目中,实现文件上传功能。
添加依赖
在您的项目 pom.xml
文件中添加 AWS S3 SDK 依赖:
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.17.100</version> <!-- 请检查并使用最新版本 -->
</dependency>
添加配置
在项目的 application.properties
文件中添加以下配置:
AWS_S3_ACCESS_KEY_ID=您的访问密钥 ID
AWS_S3_SECRET_ACCESS_KEY=您的秘密访问密钥
AWS_S3_REGION_NAME=us-west-1
AWS_S3_BUCKET_NAME=rumiapp
实现文件上传工具类 AwsS3Utils
创建一个 AwsS3Utils
工具类,用于处理文件上传及生成文件 URL。
import com.litongjava.tio.utils.environment.EnvUtils;
import com.litongjava.tio.utils.http.ContentTypeUtils;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3ClientBuilder;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
public class AwsS3Utils {
public static final String urlFormat = "https://%s.s3.us-west-1.amazonaws.com/%s";
public static final String bucketName = EnvUtils.get("AWS_S3_BUCKET_NAME");
public static final String regionName = EnvUtils.get("AWS_S3_REGION_NAME");
public static final String accessKeyId = EnvUtils.get("AWS_S3_ACCESS_KEY_ID");
public static final String secretAccessKey = EnvUtils.get("AWS_S3_SECRET_ACCESS_KEY");
public static PutObjectResponse upload(S3Client client, String bucketName, String targetName, byte[] fileContent, String suffix) {
try {
String contentType = ContentTypeUtils.getContentType(suffix);
PutObjectRequest putOb = PutObjectRequest.builder()
.bucket(bucketName)
.key(targetName)
.contentType(contentType)
.build();
PutObjectResponse putObject = client.putObject(putOb, RequestBody.fromBytes(fileContent));
return putObject;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String getUrl(String targetName) {
return String.format(AwsS3Utils.urlFormat, AwsS3Utils.bucketName, targetName);
}
public static S3Client buildClient() {
AwsBasicCredentials awsCreds = AwsBasicCredentials.create(accessKeyId, secretAccessKey);
StaticCredentialsProvider staticCredentialsProvider = StaticCredentialsProvider.create(awsCreds);
Region region = Region.of(regionName);
// 创建S3客户端
S3ClientBuilder builder = S3Client.builder();
S3Client s3 = builder.region(region)
.credentialsProvider(staticCredentialsProvider)
.build();
return s3;
}
}
配置类 AwsS3Config
创建一个配置类,用于构建 S3 客户端。
package com.litongjava.tio.boot.admin.config;
import com.litongjava.tio.utils.environment.EnvUtils;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3ClientBuilder;
public class AwsS3Config {
public S3Client buildClient() {
String accessKeyId = EnvUtils.get("AWS_S3_ACCESS_KEY_ID");
String secretAccessKey = EnvUtils.get("AWS_S3_SECRET_ACCESS_KEY");
String regionName = EnvUtils.get("AWS_S3_REGION_NAME");
AwsBasicCredentials awsCreds = AwsBasicCredentials.create(accessKeyId, secretAccessKey);
StaticCredentialsProvider staticCredentialsProvider = StaticCredentialsProvider.create(awsCreds);
Region region = Region.of(regionName);
// 创建S3客户端
S3ClientBuilder builder = S3Client.builder();
S3Client s3 = builder.region(region)
.credentialsProvider(staticCredentialsProvider)
.build();
return s3;
}
}
测试工具类 AwsS3UtilsTest
创建一个测试类,用于验证 S3 上传功能。
package com.litongjava.tio.boot.admin.utils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.Test;
import com.litongjava.tio.boot.admin.config.AwsS3Config;
import com.litongjava.tio.utils.environment.EnvUtils;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
public class AwsS3UtilsTest {
@Test
public void test() throws IOException {
EnvUtils.load();
Path path = java.nio.file.Paths.get("F:\\my_file\\my_photo\\kitty\\kitty-cat.jpg");
byte[] fileContent = Files.readAllBytes(path);
String targetName = "001.jpg"; // 上传到S3的文件名
String suffix = "jpg";
// 示例使用upload方法
try (S3Client client = new AwsS3Config().buildClient();) {
PutObjectResponse response = AwsS3Utils.upload(client, AwsS3Utils.bucketName, targetName, fileContent, suffix);
String url = String.format(AwsS3Utils.urlFormat, AwsS3Utils.bucketName, targetName);
System.out.println(url);
System.out.println(response.eTag());
}
}
}
实现文件上传服务 AwsS3StorageService
创建一个服务类,处理文件的上传及相关信息的保存到数据库。
package com.litongjava.tio.boot.admin.services;
import com.jfinal.kit.Kv;
import com.jfinal.kit.StrKit;
import com.litongjava.db.TableInput;
import com.litongjava.db.TableResult;
import com.litongjava.db.activerecord.Row;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.table.services.ApiTable;
import com.litongjava.tio.boot.admin.costants.TioBootAdminTableNames;
import com.litongjava.tio.boot.admin.dao.SystemUploadFileDao;
import com.litongjava.tio.boot.admin.utils.AwsS3Utils;
import com.litongjava.tio.boot.admin.vo.UploadResultVo;
import com.litongjava.tio.http.common.UploadFile;
import com.litongjava.tio.utils.crypto.Md5Utils;
import com.litongjava.tio.utils.hutool.FilenameUtils;
import com.litongjava.tio.utils.snowflake.SnowflakeIdUtils;
import lombok.extern.slf4j.Slf4j;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
@Slf4j
public class AwsS3StorageService implements StorageService {
public RespBodyVo upload(String category, UploadFile uploadFile) {
if (StrKit.isBlank(category)) {
category = "default";
}
String filename = uploadFile.getName();
int size = uploadFile.getSize();
byte[] fileContent = uploadFile.getData();
return RespBodyVo.ok(uploadBytes(category, filename, size, fileContent));
}
public UploadResultVo uploadBytes(String category, String originFilename, int size, byte[] fileContent) {
// 上传文件
long id = SnowflakeIdUtils.id();
String suffix = FilenameUtils.getSuffix(originFilename);
String newFilename = id + "." + suffix;
String targetName = category + "/" + newFilename;
return uploadBytes(id, originFilename, targetName, fileContent, size, suffix);
}
/**
* 上传文件并保存信息到数据库
*
* @param id
* @param originFilename
* @param targetName
* @param fileContent
* @param size
* @param suffix
* @return
*/
public UploadResultVo uploadBytes(long id, String originFilename, String targetName, byte[] fileContent, int size, String suffix) {
String md5 = Md5Utils.digestHex(fileContent);
Row row = Aop.get(SystemUploadFileDao.class).getFileBasicInfoByMd5(md5);
if (row != null) {
log.info("select table result:{}", row.toMap());
id = row.getLong("id");
String url = this.getUrl(row.getStr("bucket_name"), row.getStr("target_name"));
Kv kv = row.toKv();
kv.remove("target_name");
kv.remove("bucket_name");
kv.set("url", url);
kv.set("md5", md5);
return new UploadResultVo(id, originFilename, Long.valueOf(size), url, md5);
} else {
log.info("not found from cache table:{}", md5);
}
String etag = null;
try (S3Client client = AwsS3Utils.buildClient();) {
PutObjectResponse response = AwsS3Utils.upload(client, AwsS3Utils.bucketName, targetName, fileContent, suffix);
etag = response.eTag();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
// 记录上传信息并保存到数据库
log.info("Uploaded with ETag: {}", etag);
TableInput kv = TableInput.create()
.set("name", originFilename)
.set("size", size)
.set("md5", md5)
.set("platform", "aws s3")
.set("region_name", AwsS3Utils.regionName)
.set("bucket_name", AwsS3Utils.bucketName)
.set("target_name", targetName)
.set("file_id", etag);
TableResult<Kv> save = ApiTable.save(TioBootAdminTableNames.tio_boot_admin_system_upload_file, kv);
String downloadUrl = getUrl(AwsS3Utils.bucketName, targetName);
return new UploadResultVo(save.getData().getLong("id"), originFilename, Long.valueOf(size), downloadUrl, md5);
}
@Override
public String getUrl(String bucketName, String targetName) {
return Aop.get(SystemUploadFileService.class).getUrl(bucketName, targetName);
}
@Override
public UploadResultVo getUrlById(String id) {
return Aop.get(SystemUploadFileService.class).getUrlById(id);
}
@Override
public UploadResultVo getUrlById(long id) {
return Aop.get(SystemUploadFileService.class).getUrlById(id);
}
@Override
public UploadResultVo getUrlByMd5(String md5) {
return Aop.get(SystemUploadFileService.class).getUrlByMd5(md5);
}
}
添加 Handler 以接收上传文件
创建一个处理上传请求的 Handler 类,用于接收文件并调用上传服务。
import java.util.Map;
import com.jfinal.kit.Kv;
import com.jfinal.kit.StrKit;
import com.litongjava.db.TableInput;
import com.litongjava.db.TableResult;
import com.litongjava.db.activerecord.Row;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.open.chat.constants.TableNames;
import com.litongjava.open.chat.model.UploadResultVo;
import com.litongjava.open.chat.services.AwsS3StorageService;
import com.litongjava.table.services.ApiTable;
import com.litongjava.table.utils.TableInputUtils;
import com.litongjava.table.utils.TableResultUtils;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.boot.utils.TioRequestParamUtils;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.common.UploadFile;
import com.litongjava.tio.http.server.model.HttpCors;
import com.litongjava.tio.http.server.util.CORSUtils;
import com.litongjava.tio.http.server.util.Resps;
public class SystemFileS3Handler {
public HttpResponse upload(HttpRequest request) throws Exception {
HttpResponse httpResponse = TioRequestContext.getResponse();
CORSUtils.enableCORS(httpResponse, new HttpCors());
UploadFile uploadFile = request.getUploadFile("file");
String category = request.getParam("category");
AwsS3StorageService storageService = Aop.get(AwsS3StorageService.class);
if (uploadFile != null) {
RespBodyVo respBodyVo = storageService.upload(category, uploadFile);
return Resps.json(httpResponse, respBodyVo);
}
return Resps.json(httpResponse, RespBodyVo.ok("Fail"));
}
public HttpResponse getUploadRecordByMd5(HttpRequest request) throws Exception {
HttpResponse httpResponse = TioRequestContext.getResponse();
CORSUtils.enableCORS(httpResponse, new HttpCors());
Map<String, Object> map = TioRequestParamUtils.getRequestMap(request);
TableInput kv = TableInputUtils.camelToUnderscore(map);
// 调用 ApiTable 查询数据
TableResult<Row> jsonBean = ApiTable.get(TableNames.tio_boot_admin_system_upload_file, kv);
TableResult<Kv> tableResult = TableResultUtils.recordToKv(jsonBean);
return Resps.json(httpResponse, RespBodyVo.ok(tableResult.getData()).code(tableResult.getCode()).msg(tableResult.getMsg()));
}
public HttpResponse getUrl(HttpRequest request) throws Exception {
HttpResponse httpResponse = TioRequestContext.getResponse();
CORSUtils.enableCORS(httpResponse, new HttpCors());
AwsS3StorageService storageService = Aop.get(AwsS3StorageService.class);
RespBodyVo respBodyVo = null;
String id = request.getParam("id");
String md5 = request.getParam("md5");
if (StrKit.notBlank(id)) {
// 通过 ID 获取 URL
UploadResultVo uploadResultVo = storageService.getUrlById(id);
if (uploadResultVo == null) {
respBodyVo = RespBodyVo.fail();
} else {
respBodyVo = RespBodyVo.ok(uploadResultVo);
}
} else if (StrKit.notBlank(md5)) {
// 通过 MD5 获取 URL
UploadResultVo uploadResultVo = storageService.getUrlByMd5(md5);
if (uploadResultVo == null) {
respBodyVo = RespBodyVo.fail();
} else {
respBodyVo = RespBodyVo.ok(uploadResultVo);
}
} else {
respBodyVo = RespBodyVo.fail("id or md5 can not be empty");
}
return Resps.json(httpResponse, respBodyVo);
}
}
配置路由
在路由配置文件中添加相应的路由,指向 SystemFileS3Handler
的方法。
SystemFileS3Handler systemFileS3Handler = Aop.get(SystemFileS3Handler.class);
r.add("/api/system/file/s3/upload", systemFileS3Handler::upload);
r.add("/api/system/file/s3/md5", systemFileS3Handler::getUploadRecordByMd5);
r.add("/api/system/file/s3/url", systemFileS3Handler::getUrl);
测试上传功能
测试上传请求
使用 curl
命令发送一个 POST 请求,将文件上传到 S3。
curl --location --request POST 'http://localhost:8100/tio-boot-admin/api/system/file/upload/s3' \
--header 'authorization: 123456' \
--header 'Content-Type: multipart/form-data; boundary=--------------------------946279255222128586808179' \
--header 'Cookie: PHPSESSID=f1aa9dbe6c8e4d06aa5bc59d3b7b43b2' \
--form 'file=@"F:\\my_file\\my_photo\\aliyun\\配置.png"' \
--form 'category="sjsu/professor"'
返回数据示例
成功上传文件后,服务器将返回如下 JSON 数据:
{
"code": 1,
"data": {
"id": "394502029835268096",
"url": "https://rumiapp.s3.us-west-1.amazonaws.com/sjsu/professor/394502024734994432.png"
},
"ok": true
}
id
: 文件在数据库中的唯一标识。url
: 文件在 S3 上的访问地址。
总结
本文详细介绍了如何在 AWS S3 上配置存储桶,并将其集成到 Tio-Boot 项目中,实现文件的上传和存储。通过配置存储桶的公开权限、获取访问密钥、添加必要的依赖及代码实现,您可以轻松地将文件存储到 AWS S3 并在应用中进行管理。请确保在公开存储桶时充分了解相关的安全风险,并采取必要的安全措施以保护您的数据。
如果在配置或集成过程中遇到问题,请参考 AWS 官方文档 或相关开发文档获取更多帮助。