elunez/eladmin · error · BadRequestException
从 S3 下载文件时出错: {}
Error message
从 S3 下载文件时出错: {} What it means
BadRequestException passthrough thrown from the catch (S3Exception e) block around s3Client.getObject(...) in privateDownload: the message is '从 S3 下载文件时出错: ' + e.awsErrorDetails().errorMessage(). S3Exception is the AWS SDK v2 service error — the most common detail is 'The specified key does not exist' (404 NoSuchKey), since the DB record exists but the object at storage.getFilePath() is gone or inaccessible in the default bucket.
Source
Thrown at eladmin-tools/src/main/java/me/zhengjie/service/impl/S3StorageServiceImpl.java:196
if (storage == null) {
throw new BadRequestException("文件不存在或已被删除");
}
// 创建 GetObjectRequest,指定存储桶和文件键
GetObjectRequest getObjectRequest = GetObjectRequest.builder()
.bucket(amzS3Config.getDefaultBucket())
.key(storage.getFilePath())
.build();
String base64Data;
// 使用 try-with-resources 确保流能被自动关闭
// s3Client.getObject() 返回一个 ResponseInputStream,它是一个包含S3对象数据的输入流
try (ResponseInputStream<GetObjectResponse> s3InputStream = s3Client.getObject(getObjectRequest)) {
// 使用 IOUtils.toByteArray 将输入流直接转换为字节数组
byte[] fileBytes = IOUtils.toByteArray(s3InputStream);
// 使用 Java 内置的 Base64 编码器将字节数组转换为 Base64 字符串
base64Data = Base64.getEncoder().encodeToString(fileBytes);
} catch (S3Exception e) {
// 处理 AWS 特定的异常
throw new BadRequestException("从 S3 下载文件时出错: " + e.awsErrorDetails().errorMessage());
} catch (IOException e) {
// 处理通用的 IO 异常 (IOUtils.toByteArray 可能会抛出)
throw new BadRequestException("读取 S3 输入流时出错: " + e.getMessage());
}
// 构造返回数据
Map<String, String> responseData = new HashMap<>();
// 文件名
responseData.put("fileName", storage.getFileName());
// 文件类型
responseData.put("fileMimeType", storage.getFileMimeType());
// 文件内容
responseData.put("base64Data", base64Data);
return responseData;
}
/**
* 检查云存储桶是否存在
* @param bucketName 存储桶名称View on GitHub (pinned to 55fbf70595)
Solutions
- Read the appended AWS detail: NoSuchKey -> object really absent; AccessDenied -> IAM/object-ownership issue.
- Verify the object exists: aws s3api head-object --bucket <defaultBucket> --key <filePath from s3_storage>.
- If the default bucket changed since upload, copy/migrate old objects to the current bucket or store bucket per record.
- Reconcile s3_storage rows against actual bucket contents (a consistency job) to flag orphan records.
- Adjust lifecycle rules to retain objects as long as DB records reference them.
Example fix
// before
catch (S3Exception e) {
throw new BadRequestException("从 S3 下载文件时出错: " + e.awsErrorDetails().errorMessage());
}
// after: distinguish missing key from other failures
catch (S3Exception e) {
if (e.statusCode() == 404) {
throw new BadRequestException("文件对象在存储桶中不存在(key: " + storage.getFilePath() + ")");
}
log.error("S3 getObject failed, key={}", storage.getFilePath(), e);
throw new BadRequestException("从 S3 下载文件时出错: " + e.awsErrorDetails().errorMessage());
} Defensive patterns
Strategy: try-catch
Validate before calling
// confirm the object exists before building the download response
try {
s3Client.headObject(HeadObjectRequest.builder()
.bucket(amzS3Config.getDefaultBucket())
.key(storage.getFilePath()).build());
} catch (NoSuchKeyException e) {
throw new BadRequestException("文件对象已不在存储桶中");
} Try / catch
try { s3StorageService.privateDownload(id); } catch (BadRequestException e) { if (e.getMessage().startsWith("从 S3 下载文件时出错")) { String detail = e.getMessage().substring(e.getMessage().indexOf(':') + 1).trim(); if (detail.contains("does not exist")) { /* orphan DB record: remove/flag it */ } else if (detail.contains("AccessDenied")) { /* fix IAM */ } } } Prevention
- Keep S3 lifecycle rules from expiring objects still referenced by s3_storage rows.
- When changing the default bucket config, migrate old objects or store the bucket per record.
- Periodically reconcile DB records against head-object results to find orphans.
When it happens
Trigger: GET private-download for an id whose s3_storage row exists but whose object key (filePath like `2026/08/uuid.ext`) is missing in the bucket: object deleted out-of-band/lifecycle rule, record points at a different bucket than current defaultBucket, or permission issues (403 AccessDenied).
Common situations: S3 lifecycle rules expiring objects while DB rows remain; bucket config changed after older uploads (defaultBucket renamed) so keys live elsewhere; manual bucket cleanup; cross-account access revoked.
Related errors
AI-assisted analysis of elunez/eladmin@55fbf70595 (2026-08-14).
Data as JSON: /api/errors/57d6e48d81d997d1.
Report an issue: GitHub.