Bài này hướng dẫn gọi Object Storage từ mã nguồn ứng dụng: PHP và Laravel, Python, Node.js. Mọi ví dụ đều dùng bộ thư viện S3 chính thức, không cần thư viện riêng của Cloudzone.
1. Bốn thông tin dùng chung cho mọi ngôn ngữ
| Thông tin | Giá trị |
|---|---|
| Endpoint | https://object.cloudzone.vn |
| Access Key / Secret Key | lấy trong Portal, mục dịch vụ, nút Khoá kết nối |
| Region | us-east-1 – chỉ là giá trị hình thức, nhiều thư viện bắt buộc phải khai |
| Kiểu địa chỉ | nhận cả hai: đường dẫn (endpoint/tên-bucket) và tên miền con (tên-bucket.object.cloudzone.vn) |
Chưa có khoá thì xem bài Bắt đầu với Object Storage.
2. 🔴 Khoá không được nằm trong mã nguồn
Đây là lỗi tốn kém nhất trong cả bài. Cặp khoá cho toàn quyền đọc, ghi và xoá mọi thứ trong kho. Viết thẳng vào mã rồi đẩy lên git là khoá nằm vĩnh viễn trong lịch sử kho mã, kể cả khi xoá dòng đó ở lần sửa sau.
Đặt khoá vào biến môi trường, và tệp giữ biến môi trường phải nằm ngoài git:
# .env
S3_ENDPOINT=https://object.cloudzone.vn
S3_KEY=khoa_lay_trong_portal
S3_SECRET=khoa_bi_mat_lay_trong_portal
S3_BUCKET=abc-tep-ung-dung
# .gitignore
.env
Lỡ đẩy khoá lên git rồi thì vào Portal bấm Cấp khoá mới ngay, đừng chỉ xoá dòng đó đi. Khoá cũ mất hiệu lực lập tức, dữ liệu không ảnh hưởng, nhưng mọi nơi đang dùng khoá cũ phải khai lại.
3. PHP thuần, với AWS SDK
composer require aws/aws-sdk-php
<?php
require 'vendor/autoload.php';
use Aws\S3\S3Client;
use Aws\Exception\AwsException;
$s3 = new S3Client([
'version' => 'latest',
'region' => 'us-east-1',
'endpoint' => getenv('S3_ENDPOINT'),
'use_path_style_endpoint' => true,
'credentials' => [
'key' => getenv('S3_KEY'),
'secret' => getenv('S3_SECRET'),
],
]);
$bucket = getenv('S3_BUCKET');
// Tai mot tep len
$s3->putObject([
'Bucket' => $bucket,
'Key' => 'anh/san-pham-001.jpg',
'SourceFile' => '/duong/dan/san-pham-001.jpg',
'ACL' => 'private',
]);
// Tao duong dan tai ve co han, 15 phut
$lenh = $s3->getCommand('GetObject', [
'Bucket' => $bucket,
'Key' => 'anh/san-pham-001.jpg',
]);
$duongDan = (string) $s3->createPresignedRequest($lenh, '+15 minutes')->getUri();
echo $duongDan;
use_path_style_endpoint đặt true là cách chắc ăn nhất, vì nó không phụ thuộc vào việc phân giải tên miền con. Bỏ dòng đó cũng chạy được với Cloudzone, nhưng để lại thì bớt một thứ phải nghĩ.
4. Laravel
Laravel có sẵn ổ đĩa s3, chỉ cần khai cấu hình. Cài gói adapter:
composer require league/flysystem-aws-s3-v3
Trong .env:
AWS_ACCESS_KEY_ID=khoa_lay_trong_portal
AWS_SECRET_ACCESS_KEY=khoa_bi_mat_lay_trong_portal
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=abc-tep-ung-dung
AWS_ENDPOINT=https://object.cloudzone.vn
AWS_USE_PATH_STYLE_ENDPOINT=true
Khối s3 trong config/filesystems.php của Laravel đọc sẵn đúng các biến này, thường không phải sửa gì. Dùng:
use Illuminate\Support\Facades\Storage;
// Luu tep nguoi dung tai len
$duongDan = $request->file('anh')->store('anh-san-pham', 's3');
// Doc ra
$noiDung = Storage::disk('s3')->get($duongDan);
// Duong dan co han 15 phut
$tam = Storage::disk('s3')->temporaryUrl($duongDan, now()->addMinutes(15));
// Xoa
Storage::disk('s3')->delete($duongDan);
Về Storage::url(). Hàm này dựng địa chỉ công khai, chỉ dùng được khi đối tượng thật sự cho đọc công khai. Mặc định mọi thứ tải lên đều riêng tư, nên với tệp riêng tư hãy dùng temporaryUrl() như trên – nó ký sẵn vào địa chỉ và tự hết hạn.
5. Python, với boto3
pip install boto3
import os
import boto3
from botocore.client import Config
s3 = boto3.client(
"s3",
endpoint_url=os.environ["S3_ENDPOINT"],
aws_access_key_id=os.environ["S3_KEY"],
aws_secret_access_key=os.environ["S3_SECRET"],
region_name="us-east-1",
config=Config(signature_version="s3v4", s3={"addressing_style": "path"}),
)
bucket = os.environ["S3_BUCKET"]
# Tai len
s3.upload_file("/duong/dan/bao-cao.pdf", bucket, "bao-cao/2026-10.pdf")
# Liet ke, co phan trang san cho bucket nhieu tep
trang = s3.get_paginator("list_objects_v2")
for lo in trang.paginate(Bucket=bucket, Prefix="bao-cao/"):
for o in lo.get("Contents", []):
print(o["Key"], o["Size"])
# Duong dan co han 15 phut
print(s3.generate_presigned_url(
"get_object",
Params={"Bucket": bucket, "Key": "bao-cao/2026-10.pdf"},
ExpiresIn=900,
))
Dùng get_paginator chứ đừng dùng thẳng list_objects_v2. Lệnh liệt kê chỉ trả tối đa 1.000 đối tượng mỗi lượt; viết vòng lặp đơn giản thì chạy thử với bucket nhỏ thấy đúng, tới lúc thật thì im lặng bỏ sót từ đối tượng thứ 1.001.
6. Node.js, với AWS SDK v3
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
import { S3Client, PutObjectCommand, GetObjectCommand, ListObjectsV2Command }
from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { readFileSync } from "node:fs";
const s3 = new S3Client({
region: "us-east-1",
endpoint: process.env.S3_ENDPOINT,
forcePathStyle: true,
credentials: {
accessKeyId: process.env.S3_KEY,
secretAccessKey: process.env.S3_SECRET,
},
});
const Bucket = process.env.S3_BUCKET;
// Tai len
await s3.send(new PutObjectCommand({
Bucket,
Key: "hoa-don/2026-10-001.pdf",
Body: readFileSync("/duong/dan/hoa-don.pdf"),
ContentType: "application/pdf",
}));
// Duong dan co han 15 phut
const duongDan = await getSignedUrl(
s3,
new GetObjectCommand({ Bucket, Key: "hoa-don/2026-10-001.pdf" }),
{ expiresIn: 900 }
);
// Liet ke, co di tiep trang
let token;
do {
const kq = await s3.send(new ListObjectsV2Command({
Bucket, Prefix: "hoa-don/", ContinuationToken: token,
}));
(kq.Contents ?? []).forEach(o => console.log(o.Key, o.Size));
token = kq.NextContinuationToken;
} while (token);
Nhớ khai ContentType khi tải lên. Không khai thì đối tượng mang kiểu mặc định, và trình duyệt sẽ tải tệp về thay vì mở ảnh hay PDF ngay trên trang.
7. Bốn chỗ hay vướng khi chuyển ứng dụng từ AWS sang
| Triệu chứng | Nguyên nhân thường gặp |
|---|---|
| Thư viện báo thiếu region | Khai us-east-1, giá trị hình thức |
| Tải lên được nhưng liệt kê không ra | Kiểu địa chỉ. Bật tuỳ chọn kiểu đường dẫn như các ví dụ trên |
SignatureDoesNotMatch |
Khoá sai một ký tự, hoặc thư viện ký bằng phiên bản cũ. Ép dùng chữ ký v4 |
| Tệp tải lên mở ra bị tải về thay vì hiện | Thiếu ContentType lúc tải lên |
Mã lỗi cụ thể thì tra ở bài Lỗi thường gặp khi kết nối Object Storage và cách xử lý, có đủ mười hai mã hay gặp.
8. Vài nếp nên có ngay từ đầu
- Mỗi môi trường một bucket.
abc-tep-ung-dungvàabc-tep-ung-dung-thutách hẳn. Chạy thử mà ghi đè lên dữ liệu thật là chuyện xảy ra nhiều hơn người ta tưởng. - Đặt tên đối tượng có cấu trúc, ví dụ
hoa-don/2026/10/001.pdf. Về sau dọn theo tháng hay liệt kê theo năm đều dựa vào phần đầu của tên. - Đừng đặt tên đối tượng theo tên tệp người dùng tải lên. Sinh một tên riêng và lưu tên gốc trong cơ sở dữ liệu. Tên người dùng đặt có dấu, có dấu cách, có ký tự lạ, và hai người tải lên cùng tên là ghi đè nhau.
- Giới hạn thời hạn của đường dẫn ký sẵn: 15 phút là đủ cho phần lớn việc. Đặt bảy ngày cho tiện thì địa chỉ đó lọt ra ngoài là ai cũng tải được suốt bảy ngày.
- Dọn tệp đã bỏ. Xoá bản ghi trong cơ sở dữ liệu mà không xoá đối tượng trên kho thì dung lượng chỉ có tăng. Cách theo dõi và dọn ở bài Dọn dữ liệu và giữ dung lượng Object Storage trong tầm kiểm soát.
Quý Khách hàng cần Cloudzone hỗ trợ nối ứng dụng vào kho, vui lòng nhắn qua ô chat trên trang Cloudzone.
Bài liên quan trong chuyên mục Object Storage
- Bắt đầu với Object Storage: lấy khoá kết nối và tạo bucket đầu tiên
- Tải lên, tải về và quản lý dữ liệu trên Object Storage
- Lỗi thường gặp khi kết nối Object Storage và cách xử lý
- Dọn dữ liệu và giữ dung lượng Object Storage trong tầm kiểm soát
- Sao lưu máy chủ Linux lên Object Storage bằng rclone
- Đưa bản sao lưu cPanel hoặc DirectAdmin lên Object Storage
- Nối NAS Synology hoặc QNAP vào Object Storage
- Chuyển dữ liệu từ AWS S3, Wasabi hoặc kho S3 khác về Object Storage Cloudzone