Hướng dẫn tích hợp API lưu trữ S3/MinIO
Tài liệu hướng dẫn tích hợp API lưu trữ S3/MinIO theo chuẩn S3 Compatible: cấu hình endpoint, xác thực AWS Signature V4, presigned URL, AWS CLI, MinIO Client, SDK Node.js/PHP, CORS, policy bảo mật và checklist bàn giao.
1. Tổng quan
MinIO là hệ thống lưu trữ object storage tương thích với Amazon S3. Ứng dụng của khách có thể upload,
download, liệt kê, xóa file và tạo link tải tạm thời thông qua S3 API.
Điểm quan trọng: request gọi trực tiếp đến API phải được ký bằng AWS Signature Version 4.
Không sử dụng Basic Auth hoặc Bearer Token thông thường cho các API S3.
Khái niệm chính
- Bucket: vùng chứa file, tương tự thư mục gốc.
- Object: file được lưu trong bucket.
- Object key: đường dẫn của file, ví dụ invoices/2026/INV001.pdf.
- Presigned URL: link có thời hạn để upload hoặc download mà không lộ secret key.
Luồng sử dụng phổ biến
- Hệ thống backend nhận file từ người dùng.
- Backend upload file lên MinIO/S3 bằng access key và secret key.
- Backend lưu lại bucket, object key, mime type, dung lượng vào database.
- Khi cần tải file, backend tạo presigned URL và trả về cho client.
2. Thông tin kết nối
Thay các giá trị mẫu dưới đây bằng thông tin thật trước khi bàn giao cho khách hàng.
<div class="vps-table-block">
<table>
<thead><tr><th>Thông tin</th><th>Giá trị mẫu</th><th>Ghi chú</th></tr></thead>
<tbody>
<tr><td>API Endpoint</td><td>https://s3.example.com</td><td>Endpoint dùng cho S3 API. Nên bật HTTPS.</td></tr><tr><td>Console Endpoint</td><td>https://console-s3.example.com</td><td>Trang quản trị MinIO, chỉ cấp cho người có quyền vận hành.</td></tr><tr><td>Region</td><td>us-east-1</td><td>MinIO thường dùng giá trị này nếu không cấu hình region riêng.</td></tr><tr><td>Access Key</td><td>Cung cấp riêng qua kênh bảo mật</td><td>Không gửi chung trong tài liệu công khai.</td></tr><tr><td>Secret Key</td><td>Cung cấp riêng qua kênh bảo mật</td><td>Không commit vào source code, không gửi qua chat công khai.</td></tr><tr><td>Bucket mặc định</td><td>customer-files</td><td>Có thể tách bucket theo môi trường: dev, staging, production.</td></tr><tr><td>Path style</td><td>true</td><td>Nên bật forcePathStyle khi dùng MinIO với SDK AWS.</td></tr>
</tbody>
</table>
</div>
Cấu trúc object key khuyến nghị
{module}/{yyyy}/{mm}/{uuid}-{safe-file-name}Ví dụ:
orders/2026/06/8f3c9d7a-invoice-001.pdf
avatars/2026/06/user-1024.png
backups/2026/06/database-2026-06-20.sql.gz
Thông số bàn giao cần chốt trước khi tích hợp
<div class="vps-table-block">
<table>
<thead><tr><th>Nhóm</th><th>Giá trị cần cung cấp</th><th>Ví dụ điền thực tế</th></tr></thead>
<tbody>
<tr><td>Môi trường production</td><td>Endpoint, bucket, region, giới hạn dung lượng file</td><td>https://s3.company.vn, crm-prod, tối đa 50MB/file</td></tr><tr><td>Môi trường staging</td><td>Endpoint, bucket test, access key riêng</td><td>crm-staging, không dùng chung key production</td></tr><tr><td>Loại file cho phép</td><td>Danh sách MIME type</td><td>image/jpeg, image/png, application/pdf</td></tr><tr><td>Thời hạn link</td><td>Thời gian sống của presigned URL</td><td>Upload: 10 phút, Download: 15-60 phút</td></tr><tr><td>Retention/backup</td><td>Thời gian giữ file và chính sách xóa</td><td>Giữ file hợp đồng tối thiểu 5 năm, file tạm xóa sau 7 ngày</td></tr>
</tbody>
</table>
</div>
Ma trận quyền đề xuất
<div class="vps-table-block">
<table>
<thead><tr><th>Tài khoản/key</th><th>Quyền</th><th>Dùng cho</th><th>Không nên cấp</th></tr></thead>
<tbody>
<tr><td>app-uploader</td><td>s3:PutObject, s3:GetObject, s3:ListBucket</td><td>Ứng dụng upload và tạo link tải file</td><td>Không cấp quyền quản trị user, không cấp toàn bộ bucket khác</td></tr><tr><td>app-readonly</td><td>s3:GetObject, s3:ListBucket</td><td>Dịch vụ chỉ đọc file, đồng bộ dữ liệu</td><td>Không cấp s3:DeleteObject</td></tr><tr><td>backup-service</td><td>s3:PutObject, s3:GetObject, s3:DeleteObject</td><td>Backup định kỳ, dọn file cũ theo lịch</td><td>Không dùng chung với ứng dụng web</td></tr>
</tbody>
</table>
</div>
Ví dụ request S3 dạng REST
Ví dụ dưới đây minh họa đường dẫn và header. Giá trị Authorization phải do SDK hoặc thư viện ký
request tạo ra, không copy nguyên mẫu này để chạy thật.
PUT /customer-files/orders/2026/06/invoice.pdf HTTP/1.1
Host: s3.example.com
Content-Type: application/pdf
x-amz-date: 20260620T030000Z
x-amz-content-sha256: <sha256-payload>
Authorization: AWS4-HMAC-SHA256 Credential=<access-key>/20260620/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=<signature>
Body:
Nội dung file PDF dạng binary được gửi ở đâyPhần Body trong ví dụ là dữ liệu file thật. Khi dùng SDK hoặc fetch upload file, phần này chính là biến file/blob được gửi lên MinIO.
Ví dụ response khi upload thành công
HTTP/1.1 200 OK
ETag: "9b2cf535f27731c974343645a3985328"
x-amz-request-id: 183A6F4A9D1C4B2E
Date: Sat, 20 Jun 2026 03:00:00 GMT3. API backend đề xuất
Đây là lớp API nên đặt trong hệ thống backend của khách. Backend chịu trách nhiệm xác thực người dùng,
kiểm tra quyền nghiệp vụ, ghi database và tạo presigned URL. Frontend không gọi MinIO bằng secret key.
1. Tạo link upload
<div class="vps-table-block">
<table>
<thead><tr><th>Thông tin</th><th>Chi tiết</th></tr></thead>
<tbody>
<tr><td>Endpoint</td><td>POST /api/files/presign-upload</td></tr><tr><td>Mục đích</td><td>Tạo URL tạm thời để frontend upload file trực tiếp lên MinIO.</td></tr><tr><td>Thời hạn đề xuất</td><td>5-10 phút.</td></tr>
</tbody>
</table>
</div>
Request
{
"module": "orders",
"fileName": "invoice-001.pdf",
"contentType": "application/pdf",
"size": 245760,
"ownerType": "order",
"ownerId": "ORD-1001"
}Response
{
"success": true,
"data": {
"bucket": "customer-files",
"objectKey": "orders/2026/06/8f3c9d7a-invoice-001.pdf",
"uploadUrl": "https://s3.example.com/customer-files/orders/2026/06/8f3c9d7a-invoice-001.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256...",
"method": "PUT",
"expiresIn": 600,
"requiredHeaders": {
"Content-Type": "application/pdf"
}
}
}Frontend upload file bằng URL đã ký
await fetch(uploadUrl, {
method: "PUT",
headers: {
"Content-Type": "application/pdf"
},
body: file
});2. Xác nhận upload và lưu metadata
<div class="vps-table-block">
<table>
<thead><tr><th>Thông tin</th><th>Chi tiết</th></tr></thead>
<tbody>
<tr><td>Endpoint</td><td>POST /api/files/complete</td></tr><tr><td>Mục đích</td><td>Backend kiểm tra object tồn tại trên MinIO, sau đó lưu bản ghi file vào database.</td></tr>
</tbody>
</table>
</div>
Request
{
"bucket": "customer-files",
"objectKey": "orders/2026/06/8f3c9d7a-invoice-001.pdf",
"originalName": "invoice-001.pdf",
"contentType": "application/pdf",
"ownerType": "order",
"ownerId": "ORD-1001"
}Response
{
"success": true,
"data": {
"id": "file_01J0YQACV8K7Z1P6D4X9",
"bucket": "customer-files",
"objectKey": "orders/2026/06/8f3c9d7a-invoice-001.pdf",
"size": 245760,
"etag": "9b2cf535f27731c974343645a3985328",
"createdAt": "2026-06-20T10:00:00+07:00"
}
}3. Tạo link download
<div class="vps-table-block">
<table>
<thead><tr><th>Thông tin</th><th>Chi tiết</th></tr></thead>
<tbody>
<tr><td>Endpoint</td><td>GET /api/files/{id}/download</td></tr><tr><td>Mục đích</td><td>Trả về link tải file có thời hạn sau khi kiểm tra quyền người dùng.</td></tr>
</tbody>
</table>
</div>
Response
{
"success": true,
"data": {
"downloadUrl": "https://s3.example.com/customer-files/orders/2026/06/8f3c9d7a-invoice-001.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256...",
"expiresIn": 900,
"fileName": "invoice-001.pdf",
"contentType": "application/pdf"
}
}4. Xóa file
<div class="vps-table-block">
<table>
<thead><tr><th>Thông tin</th><th>Chi tiết</th></tr></thead>
<tbody>
<tr><td>Endpoint</td><td>DELETE /api/files/{id}</td></tr><tr><td>Mục đích</td><td>Xóa object trên MinIO hoặc đánh dấu đã xóa trong database tùy chính sách dữ liệu.</td></tr>
</tbody>
</table>
</div>
Khuyến nghị vận hành: nếu file liên quan hóa đơn, hợp đồng hoặc dữ liệu pháp lý, nên dùng
soft delete trong database trước. Chỉ xóa object thật sau khi hết thời gian lưu trữ bắt buộc.
Bảng metadata nên lưu trong database
files
- id UUID/string primary key
- bucket varchar(100)
- object_key varchar(1024)
- original_name varchar(255)
- content_type varchar(120)
- size bigint
- etag varchar(100)
- owner_type varchar(50)
- owner_id varchar(100)
- uploaded_by user id
- status uploaded | deleted | failed
- created_at datetime
- deleted_at datetime nullable4. Xác thực
Hệ thống sử dụng cặp accessKeyId và secretAccessKey. Mỗi request S3 phải được ký bằng
AWS Signature V4. Thực tế nên dùng AWS SDK hoặc MinIO SDK để SDK tự ký request.
Không nên gọi S3 API trực tiếp từ trình duyệt bằng secret key.
Nếu frontend cần upload/download, backend nên tạo presigned URL có thời hạn ngắn rồi trả về cho frontend.
Biến môi trường khuyến nghị
S3_ENDPOINT=https://s3.example.com
S3_REGION=us-east-1
S3_BUCKET=customer-files
S3_ACCESS_KEY=your-access-key
S3_SECRET_KEY=your-secret-key
S3_FORCE_PATH_STYLE=trueHeader xác thực khi gọi trực tiếp S3 API
Nếu khách không dùng SDK mà tự gọi HTTP, request cần có các header theo chuẩn AWS Signature V4.
Cách này dễ sai hơn SDK, chỉ nên dùng khi hệ thống của khách đã có thư viện ký request ổn định.
<div class="vps-table-block">
<table>
<thead><tr><th>Header/Tham số</th><th>Ví dụ</th><th>Ý nghĩa</th></tr></thead>
<tbody>
<tr><td>Host</td><td>s3.example.com</td><td>Domain endpoint S3/MinIO.</td></tr><tr><td>x-amz-date</td><td>20260620T030000Z</td><td>Thời gian UTC dùng để ký request.</td></tr><tr><td>x-amz-content-sha256</td><td>UNSIGNED-PAYLOAD hoặc SHA256 payload</td><td>Hash nội dung request. Một số SDK dùng UNSIGNED-PAYLOAD cho presigned URL.</td></tr><tr><td>Authorization</td><td>AWS4-HMAC-SHA256 Credential=...</td><td>Chữ ký được tính từ method, path, query, header và secret key.</td></tr>
</tbody>
</table>
</div>
Presigned URL hoạt động như thế nào
- Frontend yêu cầu backend tạo link upload/download cho một file cụ thể.
- Backend kiểm tra quyền người dùng trong hệ thống của khách.
- Backend dùng secret key tạo presigned URL có thời hạn ngắn.
- Frontend dùng URL đó để upload/download trực tiếp với MinIO.
- Khi URL hết hạn, client phải xin URL mới, không dùng lại URL cũ.
Khuyến nghị: secret key chỉ nằm ở backend. Frontend/mobile app không được lưu secret key,
kể cả khi ứng dụng đã đóng gói hoặc obfuscate.
5. API S3 thường dùng
Các endpoint bên dưới mô tả theo chuẩn S3. Khi dùng SDK, bạn chỉ cần gọi command tương ứng, SDK sẽ tự tạo
method, path, header và chữ ký hợp lệ.
<div class="vps-table-block">
<table>
<thead><tr><th>Nghiệp vụ</th><th>HTTP</th><th>Đường dẫn</th><th>Ghi chú</th></tr></thead>
<tbody>
<tr><td>Upload file</td><td>PUT</td><td>/{bucket}/{objectKey}</td><td>Gửi nội dung file trong body. Nên set Content-Type.</td></tr><tr><td>Tải file</td><td>GET</td><td>/{bucket}/{objectKey}</td><td>Bucket private cần request đã ký hoặc presigned URL.</td></tr><tr><td>Kiểm tra metadata</td><td>HEAD</td><td>/{bucket}/{objectKey}</td><td>Dùng để kiểm tra file tồn tại, dung lượng, mime type.</td></tr><tr><td>Xóa file</td><td>DELETE</td><td>/{bucket}/{objectKey}</td><td>Nên kiểm tra quyền trước khi xóa.</td></tr><tr><td>Liệt kê file</td><td>GET</td><td>/{bucket}?list-type=2&prefix={prefix}</td><td>Dùng prefix để lọc theo thư mục logic.</td></tr>
</tbody>
</table>
</div>
Header thường dùng khi upload
<div class="vps-table-block">
<table>
<thead><tr><th>Header</th><th>Ví dụ</th><th>Mục đích</th></tr></thead>
<tbody>
<tr><td>Content-Type</td><td>application/pdf</td><td>Giúp trình duyệt hiển thị hoặc tải file đúng kiểu.</td></tr><tr><td>Content-Disposition</td><td>attachment; filename="invoice.pdf"</td><td>Gợi ý tên file khi download.</td></tr><tr><td>x-amz-meta-*</td><td>x-amz-meta-owner-id: 1024</td><td>Lưu metadata tùy chỉnh nếu cần.</td></tr>
</tbody>
</table>
</div>
6. Ví dụ AWS CLI
Có thể dùng AWS CLI để kiểm tra nhanh kết nối. Tất cả lệnh cần thêm --endpoint-url khi làm việc
với MinIO.
Cấu hình profile
aws configure --profile minio
# Nhập:
# AWS Access Key ID: your-access-key
# AWS Secret Access Key: your-secret-key
# Default region name: us-east-1
# Default output format: jsonLiệt kê bucket
aws --profile minio \
--endpoint-url https://s3.example.com \
s3 lsUpload file
aws --profile minio \
--endpoint-url https://s3.example.com \
s3 cp ./invoice.pdf s3://customer-files/orders/2026/06/invoice.pdf \
--content-type application/pdfDownload file
aws --profile minio \
--endpoint-url https://s3.example.com \
s3 cp s3://customer-files/orders/2026/06/invoice.pdf ./invoice.pdfTạo presigned URL để tải file
aws --profile minio \
--endpoint-url https://s3.example.com \
s3 presign s3://customer-files/orders/2026/06/invoice.pdf \
--expires-in 36007. MinIO Client
mc là công cụ dòng lệnh chính thức của MinIO, phù hợp cho thao tác quản trị, kiểm tra bucket,
phân quyền user và xem dung lượng.
Kết nối MinIO bằng alias
mc alias set company-s3 https://s3.example.com your-access-key your-secret-key
# Kiểm tra kết nối
mc admin info company-s3
mc ls company-s3Tạo bucket và kiểm tra file
mc mb company-s3/customer-files
mc ls company-s3/customer-files
mc cp ./invoice.pdf company-s3/customer-files/orders/2026/06/invoice.pdf
mc stat company-s3/customer-files/orders/2026/06/invoice.pdfTạo user cho ứng dụng
mc admin user add company-s3 app-uploader APP_UPLOADER_SECRET_CHANGE_MEPolicy mẫu cho ứng dụng upload/read
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::customer-files"
]
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::customer-files/*"
]
}
]
}Gắn policy cho user
# Lưu policy vào file app-uploader-policy.json
mc admin policy create company-s3 app-uploader-policy ./app-uploader-policy.json
mc admin policy attach company-s3 app-uploader-policy --user app-uploaderBật versioning cho bucket quan trọng
mc version enable company-s3/customer-files
mc version info company-s3/customer-filesLifecycle xóa file tạm sau 7 ngày
{
"Rules": [
{
"ID": "delete-temp-files-after-7-days",
"Status": "Enabled",
"Filter": {
"Prefix": "tmp/"
},
"Expiration": {
"Days": 7
}
}
]
}# Lưu nội dung lifecycle vào lifecycle.json
mc ilm import company-s3/customer-files < lifecycle.json
mc ilm ls company-s3/customer-filesKiểm tra dung lượng bucket
mc du company-s3/customer-files
mc find company-s3/customer-files --name "*.pdf" --older-than 30d8. Ví dụ SDK
Node.js với AWS SDK v3
import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import fs from "node:fs";
const s3 = new S3Client({
endpoint: process.env.S3_ENDPOINT,
region: process.env.S3_REGION || "us-east-1",
forcePathStyle: true,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_KEY,
},
});
const bucket = process.env.S3_BUCKET;
const key = "orders/2026/06/invoice.pdf";
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: fs.createReadStream("./invoice.pdf"),
ContentType: "application/pdf",
}));
const downloadUrl = await getSignedUrl(
s3,
new GetObjectCommand({ Bucket: bucket, Key: key }),
{ expiresIn: 3600 }
);
console.log(downloadUrl);Node.js tạo presigned URL cho frontend upload
import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const s3 = new S3Client({
endpoint: process.env.S3_ENDPOINT,
region: process.env.S3_REGION || "us-east-1",
forcePathStyle: true,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_KEY,
},
});
export async function createUploadUrl({ bucket, key, contentType }) {
const command = new PutObjectCommand({
Bucket: bucket,
Key: key,
ContentType: contentType,
});
return getSignedUrl(s3, command, { expiresIn: 600 });
}
export async function createDownloadUrl({ bucket, key, fileName }) {
const command = new GetObjectCommand({
Bucket: bucket,
Key: key,
ResponseContentDisposition: `attachment; filename="${fileName}"`,
});
return getSignedUrl(s3, command, { expiresIn: 900 });
}PHP với aws/aws-sdk-php
<?php
require __DIR__ . "/vendor/autoload.php";
use Aws\S3\S3Client;
use Aws\S3\Exception\S3Exception;
$s3 = new S3Client([
"version" => "latest",
"region" => getenv("S3_REGION") ?: "us-east-1",
"endpoint" => getenv("S3_ENDPOINT"),
"use_path_style_endpoint" => true,
"credentials" => [
"key" => getenv("S3_ACCESS_KEY"),
"secret" => getenv("S3_SECRET_KEY"),
],
]);
$bucket = getenv("S3_BUCKET");
$key = "orders/2026/06/invoice.pdf";
try {
$s3->putObject([
"Bucket" => $bucket,
"Key" => $key,
"SourceFile" => __DIR__ . "/invoice.pdf",
"ContentType" => "application/pdf",
]);
$cmd = $s3->getCommand("GetObject", [
"Bucket" => $bucket,
"Key" => $key,
]);
$request = $s3->createPresignedRequest($cmd, "+60 minutes");
echo (string) $request->getUri();
} catch (S3Exception $e) {
error_log($e->getAwsErrorCode() . ": " . $e->getMessage());
}9. CORS & proxy
Nếu frontend upload trực tiếp bằng presigned URL, bucket cần cấu hình CORS cho domain ứng dụng.
Nếu không cấu hình CORS, trình duyệt sẽ chặn request dù URL đã ký đúng.
CORS mẫu cho bucket private
{
"CORSRules": [
{
"AllowedOrigins": [
"https://app.example.com",
"https://admin.example.com"
],
"AllowedMethods": [
"GET",
"PUT",
"POST",
"HEAD"
],
"AllowedHeaders": [
"Authorization",
"Content-Type",
"x-amz-date",
"x-amz-content-sha256",
"x-amz-security-token",
"x-amz-meta-*"
],
"ExposeHeaders": [
"ETag",
"Content-Length",
"Content-Type"
],
"MaxAgeSeconds": 3600
}
]
}Áp dụng CORS bằng AWS CLI
aws --profile minio \
--endpoint-url https://s3.example.com \
s3api put-bucket-cors \
--bucket customer-files \
--cors-configuration file://cors.jsonÁp dụng CORS bằng MinIO Client
mc cors set company-s3/customer-files cors.json
mc cors info company-s3/customer-filesLưu ý: không nên đặt AllowedOrigins là * cho hệ thống production
có dữ liệu riêng tư. Hãy khai báo đúng domain frontend của khách.
Nginx reverse proxy mẫu cho MinIO API
server {
listen 443 ssl http2;
server_name s3.example.com;
client_max_body_size 200M;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 300;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://127.0.0.1:9000;
}
}Nginx reverse proxy mẫu cho MinIO Console
server {
listen 443 ssl http2;
server_name console-s3.example.com;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:9001;
}
}10. Quy ước & bảo mật
Quy ước bucket
- Không để bucket production ở chế độ public nếu chứa dữ liệu khách hàng, hợp đồng, hóa đơn hoặc thông tin cá nhân.
- Tách bucket theo môi trường hoặc theo nhóm dữ liệu nếu cần phân quyền rõ ràng.
- Không dùng tên file gốc làm object key duy nhất vì có thể trùng. Nên thêm UUID hoặc mã định danh.
Quy ước quyền truy cập
- Access key dùng cho ứng dụng chỉ nên có quyền trên bucket cần thiết.
- Tài khoản upload không nhất thiết có quyền xóa nếu nghiệp vụ không cần xóa.
- Secret key phải lưu trong biến môi trường hoặc secret manager, không ghi trực tiếp vào source code.
- Presigned URL nên có thời hạn ngắn, ví dụ 5 phút đến 60 phút tùy nghiệp vụ.
Lưu ý bảo mật: nếu secret key bị lộ, cần khóa hoặc xoay key ngay, sau đó kiểm tra log truy cập
để phát hiện upload/download bất thường.
11. Lỗi thường gặp
<div class="vps-table-block">
<table>
<thead><tr><th>Mã lỗi</th><th>Nguyên nhân phổ biến</th><th>Cách xử lý</th></tr></thead>
<tbody>
<tr><td>AccessDenied</td><td>Access key không có quyền với bucket hoặc object.</td><td>Kiểm tra policy, bucket name, object key và quyền read/write/delete.</td></tr><tr><td>NoSuchBucket</td><td>Bucket chưa tồn tại hoặc sai tên bucket.</td><td>Tạo bucket hoặc sửa lại biến S3_BUCKET.</td></tr><tr><td>NoSuchKey</td><td>Object key không tồn tại.</td><td>Kiểm tra lại đường dẫn object, phân biệt chữ hoa/chữ thường.</td></tr><tr><td>SignatureDoesNotMatch</td><td>Sai secret key, sai endpoint, sai region, hoặc request bị thay đổi sau khi ký.</td><td>Kiểm tra credentials, region, đồng bộ thời gian server và bật path style cho MinIO.</td></tr><tr><td>RequestTimeTooSkewed</td><td>Thời gian máy client/server lệch quá nhiều.</td><td>Đồng bộ NTP trên server ứng dụng.</td></tr><tr><td>EntityTooLarge</td><td>File vượt giới hạn upload của proxy, ứng dụng hoặc cấu hình hạ tầng.</td><td>Kiểm tra Nginx/Apache, backend upload limit và cân nhắc multipart upload.</td></tr><tr><td>CORS error</td><td>Browser bị chặn vì bucket chưa cho phép domain frontend.</td><td>Kiểm tra AllowedOrigins, AllowedMethods, AllowedHeaders trong CORS.</td></tr><tr><td>403 khi dùng presigned URL</td><td>URL hết hạn, method không khớp, hoặc header upload khác với lúc ký.</td><td>Nếu ký với Content-Type: application/pdf, frontend phải gửi đúng header đó khi upload.</td></tr><tr><td>InvalidAccessKeyId</td><td>Access key sai, bị khóa hoặc không tồn tại trên MinIO.</td><td>Kiểm tra user bằng mc admin user info và tạo key mới nếu cần.</td></tr><tr><td>Connection refused</td><td>MinIO service chưa chạy hoặc proxy trỏ sai port.</td><td>Kiểm tra port API 9000, console 9001, firewall và upstream Nginx.</td></tr>
</tbody>
</table>
</div>
Checklist debug nhanh
- Kiểm tra endpoint có mở được HTTPS không: curl -I https://s3.example.com.
- Kiểm tra credentials bằng aws s3 ls --endpoint-url ... hoặc mc ls.
- Kiểm tra bucket name và object key có đúng chữ hoa/chữ thường không.
- Kiểm tra đồng hồ server ứng dụng có lệch giờ UTC không.
- Kiểm tra presigned URL còn hạn và frontend dùng đúng method PUT/GET.
- Kiểm tra CORS nếu lỗi chỉ xảy ra trên trình duyệt nhưng chạy bằng CLI vẫn thành công.
12. Checklist bàn giao
<div class="vps-table-block">
<table>
<thead><tr><th>Hạng mục</th><th>Trạng thái</th><th>Ghi chú</th></tr></thead>
<tbody>
<tr><td>API endpoint HTTPS hoạt động</td><td>☐</td><td>Kiểm tra chứng chỉ SSL còn hạn.</td></tr><tr><td>Bucket production đã tạo</td><td>☐</td><td>Ghi rõ tên bucket bàn giao.</td></tr><tr><td>Access key/secret key đã cấp</td><td>☐</td><td>Gửi secret qua kênh bảo mật riêng.</td></tr><tr><td>Policy quyền đã giới hạn</td><td>☐</td><td>Chỉ cấp quyền cần thiết cho ứng dụng.</td></tr><tr><td>Đã test upload/download/delete</td><td>☐</td><td>Lưu log hoặc ảnh chụp kết quả test nếu cần.</td></tr><tr><td>Cơ chế backup/replication</td><td>☐</td><td>Ghi rõ lịch backup và thời gian lưu trữ.</td></tr><tr><td>Người phụ trách hỗ trợ</td><td>☐</td><td>Tên, email, số điện thoại hoặc kênh ticket.</td></tr>
</tbody>
</table>
</div>
Mẫu thông tin bàn giao cho khách
API Endpoint: https://s3.example.com
Console: https://console-s3.example.com
Region: us-east-1
Bucket: customer-files
Access Key: gửi riêng
Secret Key: gửi riêng
Path Style: true
Presigned URL: khuyến nghị dùng cho frontend, thời hạn 5-60 phútTest case nghiệm thu tích hợp
<div class="vps-table-block">
<table>
<thead><tr><th>STT</th><th>Kịch bản</th><th>Cách test</th><th>Kết quả đạt</th></tr></thead>
<tbody>
<tr><td>1</td><td>Backend tạo presigned upload URL</td><td>Gọi POST /api/files/presign-upload với file PDF hợp lệ.</td><td>Nhận được uploadUrl, objectKey, expiresIn.</td></tr><tr><td>2</td><td>Frontend upload file</td><td>Gửi PUT file lên uploadUrl.</td><td>MinIO trả HTTP 200 hoặc 204, có ETag.</td></tr><tr><td>3</td><td>Backend xác nhận file</td><td>Gọi POST /api/files/complete.</td><td>Database có bản ghi file, size và content type đúng.</td></tr><tr><td>4</td><td>Download file private</td><td>Gọi GET /api/files/{id}/download rồi mở downloadUrl.</td><td>File tải về đúng nội dung, URL hết hạn sau thời gian cấu hình.</td></tr><tr><td>5</td><td>Chặn file sai định dạng</td><td>Upload file .exe hoặc MIME type không cho phép.</td><td>Backend từ chối trước khi tạo presigned URL.</td></tr><tr><td>6</td><td>Chặn file quá dung lượng</td><td>Upload file vượt giới hạn đã thống nhất.</td><td>Backend trả lỗi rõ ràng, không tạo object trên MinIO.</td></tr><tr><td>7</td><td>Kiểm tra quyền user</td><td>User A thử tải file thuộc User B.</td><td>Backend trả 403 Forbidden, không trả presigned URL.</td></tr><tr><td>8</td><td>Xóa hoặc soft delete</td><td>Gọi DELETE /api/files/{id}.</td><td>Trạng thái file đúng theo chính sách: xóa object hoặc đánh dấu deleted.</td></tr>
</tbody>
</table>
</div>
Mẫu phản hồi lỗi API nội bộ
{
"success": false,
"error": {
"code": "FILE_TYPE_NOT_ALLOWED",
"message": "Định dạng file không được hỗ trợ.",
"details": {
"allowedTypes": ["image/jpeg", "image/png", "application/pdf"]
}
}
}Thông tin liên hệ đội ngũ VPSTTT
Hotline: 0328 812 674
Website: https://vpsttt.com
Facebook: https://facebook.com/VPSTTT
Zalo OA: https://zalo.me/vpstttgroup