post-image

Giới thiệu về Firebase Cloud Storage

Tổng quan

Hôm nay chúng ta tiếp tục tìm hiểu khái quát về Cloud Storage, tức là dịch vụ lưu trữ tĩnh. Với Firebase Cloud Storage thì bạn có thể giảm bớt gánh nặng cho Realtime Database vốn có giới hạn thấp hơn, để lưu trữ các nội dung mang tính tĩnh hơn. Chẳng hạn, profile pictures cho các users hay một tập hợp các tập tin ZIP.

1. Firebase Cloud Storage là gì?

Cloud Storage for Firebase is a powerful, simple, and cost-effective object storage service built for Google scale. The Firebase SDKs for Cloud Storage add Google security to file uploads and downloads for your Firebase apps, regardless of network quality. You can use our SDKs to store images, audio, video, or other user-generated content.

Nói ngắn gọn, Firebase Cloud Storage chính là không gian lưu trữ dữ liệu. Dữ liệu ở đây không có giới hạn nào cả. Bạn có thể chứa bất kì các (loại) tập tin nào mà bạn muốn, như ảnh, nhạc, video hoặc các tập tin text, zip hay thậm chí là một tập tin với kiểu dữ liệu của riêng bạn thiết kế. Có lẽ bạn sẽ cho rằng “Ơ nghe sao giống Google Drive vậy?” – Giống thôi, nhưng mục đích của Firebase Cloud Storage phục vụ cho hoạt động lập trình, làm nền tảng cho bạn xây dựng sản phẩm phần mềm trên đó, không phải phục vụ cho mục đích cá nhân. Trên thực tế, nó giống như không gian lưu trữ trên Web host của bạn vậy.

Đã có Realtime Database thì tại sao có Cloud Storage? Câu trả lời rất đơn giản. Realtime Database mang tính “là cơ sở dữ liệu” hơn so với Cloud Storage – vốn là nơi lưu trữ các tập tin riêng biệt. Về góc độ linh động, thì Realtime Database mang tính “động” (dynamic), tức là khi có thay đổi trên CSDL thì lập tức Firebase server sẽ gửi các Events xuống các thiết bị, còn Cloud Storage thì không. Dưới góc độ dung lượng, với gói Spark thì bạn chỉ có 1GB cho Realtime Database, trong khi có tới 5GB cho Cloud Storage.

2. Cái hay của Firebase Storage.

Cũng như Realtime Database, Authentication và các sản phẩm khác của “Ngọn lửa”, Cloud Storage có hẳn (sẵn) một bộ thư viện để bạn chỉ việc vận dụng vào các projects của mình. Bạn không cần viết bất kì một dòng code nào để làm APIs, để handle các event khi người dùng send request, upload hoặc download các tập tin tại server trừ khi điều đó là cần thiết. Và nếu bạn đang (có dự định) thao tác với Google Cloud Platform, cụ thể là Google Cloud Storage thì Firebase Cloud Storage là lựa chọn hàng đầu trong danh sách các dịch vụ mà bạn (cần) tham khảo.

3. Tích hợp Firebase Storage vào các Projects của bạn và cho upload, download, delete các files.

Như thường lệ, chúng tôi sẽ hướng dẫn các bạn tích hợp FCS vào các Android, iOS và Web projects. Và nếu bạn đã thao tác với Authentication và Realtime Database, bạn sẽ thấy việc tích hợp FCS SDK hoàn toàn tương tự. Trong bài hướng dẫn này, chúng tôi sẽ đi vào trường hợp đơn giản nhất là bạn sẽ sử dụng Bucket (chỉ nên dịch là “xô chứa” để cho vui, vui lòng đừng dùng từ đó làm thuật ngữ) mặc định. Lưu ý là chúng tôi khuyến nghị các bạn nên tích hợp Authentication trước khi dùng Cloud Storage

######################################

3.1. Đối với Android:

3.1.1. Thêm thư viện và khởi tạo các instances cơ bản:

Bạn đã quá quen thuộc với yêu cầu Project của bạn đã phải được tích hợp firebase-core nên tôi sẽ không nói gì thêm, tuy nhiên nếu bạn chưa làm thì vui lòng xem lại Bài mở đầu của chúng tôi. Bạn không còn lạ lùng gì với việc yêu cầu Gradle compile thêm thư viện firebase-storage như bên dưới:

1compile ‘com.google.firebase:firebase-storage:10.2.4’

Sau khi mọi thứ đã được sẵn sàng thì việc khởi tạo instance cho FirebaseStorage là điều quá hiển nhiên.

1FirebaseStorage mStorage = FirebaseStorage.getInstance();

Để tiến hành upload, download và delete các files thì bạn sẽ thao tác qua một instance của StorageReference. Nếu bạn đã làm qua DatabaseReference trong Realtime Database thì bạn sẽ không hề có cảm giác bỡ ngỡ vì chúng hoàn toàn tương tự nhau, cũng có hàm child(String child) để trỏ vào thư mục con. Cụ thể:

12345678910111213141516171819202122232425// Tạo instance cho StorageReferenceStorageReference mStorageRef = mStorage.getReference(); // Trỏ vào thư mục con, ví dụ thư mục có tên “images”StorageReference imagesFolder = mStorageRef.child(“images”); // Trỏ vào tập tin có tên là “Firebase_Cloud_Storage_logo.png” trong thư mục “images”// Trỏ từ imagesFolderStorageReference fcs_logo = imagesFolder.child(“Firebase_Cloud_Storage_logo.png”);// Trỏ trực tiếp qua mStorageRefStorageReference fcs_logo = mStorageRef.child(“images/Firebase_Cloud_Storage_logo.png”); // Trỏ về thư mục mẹ, tức là nagivate up lên 1 tầng// Trỏ từ “Firebase_Cloud_Storage_logo.png”, kết quả sẽ là thư mục “images”StorageReference imagesFolder = fcs_logo.getParent(); // Trỏ về thư mục gốcStorageReference root = fcs_logo.getRoot();// Lưu ý là root.getParent() sẽ cho ra null, vì bản thân root đương nhiên không có parent. // Để lấy đường dẫn, ta dùng getPath(), hàm này return String:String path_to_fcs_logo = fcs_logo.getPath(); // Để lấy tên, ta gọi getName() và cũng return StringString name = fcs_logo.getName();

Vui lòng không đặt tên thư mục và tên tập tin có các kí tự (character) bị cấm, chẳng hạn #, [, ], *, hoặc ?. Lí do thì bạn hoặc biết rồi, hoặc có thể tự hiểu và tôi sẽ không giải thích gì ở đây. Và cũng không nên đặt tên thư mục và tên tập tin với các kí tự tiếng Việt trừ khi điều đó là vô cùng cần thiết. Tốt nhất, nếu tiếng Anh của bạn tốt thì nên sử dụng tiếng Anh.

3.1.2. Upload files:

Để upload các tập tin lên FCS thì trước tiên bạn sẽ phải chuyển chúng thành byte[ ] hoặc InputStream, hoặc trực tiếp hơn là từ chính tập tin đó qua Uri, và công việc chuyển đổi hoặc lấy Uri này thì tôi sẽ không trình bày chi tiết. Sau khi đã có các dữ liệu cần thiết thì ta sẽ gọi các hàm putBytes(byte[ ]), putStream(InputStream) hoặc putFile(Uri) thông qua mStorageRef, và các hàm này sẽ return một đối tượng UploadTask. Bạn có thể thêm các thông tin Metadata như chiều dài tập tin, ngày tạo, kiểu thông qua các lớp StorageMetadata và StorageMetadata.Builder và truyền StorageMetadata instance đó làm tham số (param) thứ hai trong các hàm putBytes, putStream hoặc putFile tôi đã liệt kê. Cụ thể như sau:

12345678910111213141516171819202122File file; // tập tin mà bạn cần uploadUploadTask uploadTask; // instance của UploadTaskStorageReference storageRef = …; // trỏ tới vị trí mà bạn muốn. // Nếu bạn muốn kèm StorageMetadata thì làm như sau:StorageMetadata.Builder metaB = new StorageMetadata.Builder();// Bạn muốn thêm các meta data nào thì gọi qua instance meta đó.meta.set…(…);// Cuối cùng gọi build()StorageMetadata meta = metaB.build(); // Upload qua putBytes(byte[])byte[] bytes = convertFileToByteArray(file) // hàm này bạn tự viết, ở đây tôi chỉ đưa ra làm minh họauploadTask = storageRef.putBytes(bytes, meta); // có kèm thông tin metadata, nếu không muốn kèm thì chỉ cần bỏ qua param đó hoặc truyền vào null // Upload qua putStream(InputStream)InputStream inputStream = new FileInputStream(file);uploadTask = storageRef.putStream(inputStream); // Upload trực tiếp qua File UriUri uriToFile = getUriFromFile(file); // hàm này bạn tự viết, ở đây tôi chỉ đưa ra để minh họa, và bạn cần tránh FileUriExposedException trong Android 7 về sauuploadTask = storageRef.putFile(uriToFile);

Để theo dõi và nhận kết quả của quá trình upload thì ta chỉ việc thêm mấy cái Listener interfaces, điều này quá đỗi quen thuộc với các bạn rồi.

123456789101112131415161718192021uploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {    @Override    public void onProgress(UploadTask.TaskSnapshot snapshot) {        // Lấy progress        double progress = (100.0 * taskSnapshot.getBytesTransferred()) / taskSnapshot.getTotalByteCount();    }}); uploadTask.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {    @Override    public void onSuccess(UploadTask.TaskSnapshot snapshot) {        // Code xử lí    }}); uploadTask.addOnFailureListener(new OnFailureListener() {    @Override    public void onFailure(@NonNull Exception exception) {        // Code xử lí    }});

3.1.3. Download files:

Để download các files thì ta sẽ gọi các hàm getBytes() hoặc getFile(), ngược lại với upload. Bạn cũng có thể chỉ lấy URL trỏ tới file, chẳng hạn để chia sẻ. Cụ thể như sau:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849StorageReference storageRef = …; // trỏ tới vị trí tập tinFileDownloadTask downloadTask; // Với hàm getBytes, ban lấy dữ liệu về dưới dạng byte[ ]final long ONE_MEGABYTE = 1024 * 1024;downloadTask = storageRef.getBytes(ONE_MEGABYTE);downloadTask.addOnSuccessListener(new OnSuccessListener<byte[]>() {    @Override    public void onSuccess(byte[] bytes) {        // Code xử lí    }});downloadTask.addOnFailureListener(new OnFailureListener() {    @Override    public void onFailure(@NonNull Exception exception) {        // Code xử lí    }}); // Với hàm getFile, bạn lưu trực tiếp vào file có sẵn, Bạn tự xử lí với file có sẵn này.File myFile;downloadTask = storageRef.getFile(myFile);downloadTask.addOnSuccessListener(new OnSuccessListener<FileDownloadTask.TaskSnapshot>() {    @Override    public void onSuccess(FileDownloadTask.TaskSnapshot snapshot) {        // Code xử lí    }});downloadTask.addOnFailureListener(new OnFailureListener() {    @Override    public void onFailure(@NonNull Exception exception) {        // Code xử lí    }}); // Chỉ lấy URL tới filestorageRef.getDownloadUrl();downloadTask.addOnSuccessListener(new OnSuccessListener<Uri>() {    @Override    public void onSuccess(Uri url) {       // Code xử lí    }});downloadTask.addOnFailureListener(new OnFailureListener() {    @Override    public void onFailure(@NonNull Exception exception) {      // Code xử lí    }});

3.1.4. Delete files:

Để xóa tập tin thì đơn giản là bạn chỉ cần gọi StorageReference#delete(). Lưu ý là hàm này không return ra kiểu boolean (với ý nghĩa là return true nếu delete được và false nếu không được), nên bạn sẽ phải tự thêm các listeners như bên trên để kiểm tra xem là việc xóa tập tin đó có thành công hay không.

12345678910111213storageRef.delete()  addOnSuccessListener(new OnSuccessListener<Object>() {    @Override    public void onSuccess(Object) {       // Thành công    }  })  .addOnFailureListener(new OnFailureListener() {    @Override    public void onFailure(@NonNull Exception exception) {      // Có lỗi, không xóa được    }  });

######################################

3.2. Đối với iOS:

3.2.1. Thêm thư viện và khởi tạo các instances cơ bản:

Bạn đã quá quen thuộc với yêu cầu Project của bạn đã phải được tích hợp Firebase/Core nên tôi sẽ không nói gì thêm, tuy nhiên nếu bạn chưa làm thì vui lòng xem lại Bài mở đầu của chúng tôi. Bạn không còn lạ lùng gì với việc yêu cầu CocoaPods add thêm thư viện Firebase/Storage như bên dưới:

12pod ‘Firebase/Core’pod ‘Firebase/Storage’

Sau khi đã đảm bảo đã có FIRApp.configure() thì việc khởi tạo FIRStorage instance là điều quá hiển nhiên:

1let storage = FIRStorage.storage()

Để tiến hành upload, download và delete các files thì bạn sẽ thao tác qua một instance của FIRStorageReference. Nếu bạn đã làm qua DatabaseReference trong Realtime Database thì bạn sẽ không hề có cảm giác bỡ ngỡ vì chúng hoàn toàn tương tự nhau, cũng có hàm child(String child) để trỏ vào thư mục con. Cụ thể:

12345678910111213141516171819202122232425// Tạo instance cho StorageReferencelet storageRef = storage.reference() // Trỏ vào thư mục con, ví dụ thư mục có tên “images”let imagesFolder = storageRef.child(“images”); // Trỏ vào tập tin có tên là “Firebase_Cloud_Storage_logo.png” trong thư mục “images”// Trỏ từ imagesFolderlet fcs_logo = imagesFolder.child(“Firebase_Cloud_Storage_logo.png”);// Trỏ trực tiếp qua mStorageReflet fcs_logo = storageRef.child(“images/Firebase_Cloud_Storage_logo.png”); // Trỏ về thư mục mẹ, tức là navigate up lên 1 tầng// Trỏ từ “Firebase_Cloud_Storage_logo.png”, kết quả sẽ là thư mục “images”let imagesFolder = fcs_logo.parent; // Trỏ về thư mục gốclet root = fcs_logo.root;// Lưu ý là root.parent sẽ cho ra nil, vì bản thân root đương nhiên không có parent. // Để lấy đường dẫn, ta dùng path, hàm này return String:let path_to_fcs_logo = fcs_logo.path; // Để lấy tên, ta gọi name và cũng return Stringlet name = fcs_logo.name;

Vui lòng không đặt tên thư mục và tên tập tin có các kí tự (character) bị cấm, chẳng hạn #, [, ], *, hoặc ?. Lí do thì bạn hoặc biết rồi, hoặc có thể tự hiểu và tôi sẽ không giải thích gì ở đây. Và cũng không nên đặt tên thư mục và tên tập tin với các kí tự tiếng Việt trừ khi điều đó là vô cùng cần thiết. Tốt nhất, nếu tiếng Anh của bạn tốt thì nên sử dụng tiếng Anh.

3.2.2. Upload files:

Để upload các tập tin lên FCS thì bạn có thể chọn trực tiếp dữ liệu trong bộ nhớ với hàm put() hoặc URL đại diện cho vị trí của tập tin đó với hàm putFile(), thông qua storageRef, và các hàm này sẽ return một đối tượng FIRUploadTask. Bạn có thể thêm các thông tin Metadata như chiều dài tập tin, ngày tạo, kiểu thông qua lớp FIRStorageMetadata và truyền FIRStorageMetadata instance đó làm tham số (param) thứ hai trong các hàm put() hoặc putFile() tôi đã liệt kê. Cụ thể như sau:

12345678910111213var uploadTask: FIRUploadTasklet storageRef = … // trỏ tới vị trí bạn mong muốn // Nếu muốn bao gồm metadata thì bạn khởi tạo một instance và thêm các thông tin:let metadata = FIRStorageMetadata() // Upload với hàm put() và có metadatalet data = Data()uploadTask = storageRef.put(data, metadata: metadata) // Upload với hàm putFile() và không có metadatalet file = URL(string: “path/to/file”)!uploadTask = storageRef.putFile(file, metadata: nil)

Để theo dõi và nhận kết quả của quá trình upload thì ta chỉ việc thêm mấy cái Observers interfaces, điều này quá đỗi quen thuộc với các bạn rồi.

1234567891011121314uploadTask.observe(.progress) { snapshot in   // Để lấy progress   let progress = 100.0 * Double(snapshot.progress!.completedUnitCount) / Double(snapshot.progress!.totalUnitCount)} uploadTask.observe(.success) { snapshot in   // Code xử lí} uploadTask.observe(.failure) { snapshot in   if let error = snapshot.error as? NSError {      // Code xử lí   }}

3.2.3. Download files:

Để download các files thì ta sẽ gọi các hàm data() để lưu dữ liệu dưới dạng NSData hoặc write() để lưu trực tiếp vào một tập tin. Bạn cũng có thể chỉ lấy URL trỏ tới file, chẳng hạn để chia sẻ. Cụ thể như sau:

1234567891011121314151617181920212223242526272829let storageRef = … // trỏ tới vị trí bạn mong muốn // Download với data: NSDatastorageRef.data(withMaxSize: 1 * 1024 * 1024) { data, error in  if let error = error {    // Có lỗi xảy ra, code xử lí ở đây  } else {    // Thành công, code xử lí ở đây  }} // Download với writeToFIlelet localURL = URL(string: “path/to/file”)!storageRef.write(toFile: localURL) { url, error in  if let error = error {    // Có lỗi xảy ra, code xử lí ở đây  } else {    // Thành công, code xử lí ở đây  }} // Chỉ lấy URLstorageRef.downloadURL { url, error in  if let error = error {    // Có lỗi xảy ra  } else {    let url = url // Lấy URL  }}

3.2.4. Delete files:

Để xóa tập tin thì đơn giản là bạn chỉ cần gọi FIRStorageReference#delete. Lưu ý là hàm này không return ra kiểu Bool (với ý nghĩa là return true nếu delete được và false nếu không được), nên bạn sẽ phải tự quan sát xem có error phát sinh hay không.

1234567storageRef.delete { error in   if let error = error {     // Có lỗi, không xóa được   } else {     // Thành công   }}

######################################

3.3. Đối với Web:

3.3.1. Khởi tạo các instances cơ bản:

Bạn đã quá quen thuộc với yêu cầu Project của bạn đã phải được tích hợp Firebase nên tôi sẽ không nói gì thêm, tuy nhiên nếu bạn chưa làm thì vui lòng xem lại Bài mở đầu của chúng tôi. Sau khi đã cho initialize thì bạn sẽ làm động tác quen thuộc:

1let storage = fỉrebase.storage()

Để tiến hành upload, download và delete các files thì bạn sẽ thao tác qua một instance của Storage Reference. Nếu bạn đã làm qua Database Reference trong Realtime Database thì bạn sẽ không hề có cảm giác bỡ ngỡ vì chúng hoàn toàn tương tự nhau, cũng có hàm child(String child) để trỏ vào thư mục con. Cụ thể:

12345678910111213141516171819202122232425// Tạo instance cho StorageReferencelet storageRef = storage.ref() // Trỏ vào thư mục con, ví dụ thư mục có tên “images”let imagesFolder = storageRef.child(“images”); // Trỏ vào tập tin có tên là “Firebase_Cloud_Storage_logo.png” trong thư mục “images”// Trỏ từ imagesFolderlet fcs_logo = imagesFolder.child(“Firebase_Cloud_Storage_logo.png”);// Trỏ trực tiếp qua storageReflet fcs_logo = storageRef.child(“images/Firebase_Cloud_Storage_logo.png”); // Trỏ về thư mục mẹ, tức là navigate up lên 1 tầng// Trỏ từ “Firebase_Cloud_Storage_logo.png”, kết quả sẽ là thư mục “images”let imagesFolder = fcs_logo.parent(); // Trỏ về thư mục gốclet root = fcs_logo.root();// Lưu ý là root.parent() sẽ cho ra null, vì bản thân root đương nhiên không có parent. // Để lấy đường dẫn, ta dùng path, hàm này return String:let path_to_fcs_logo = fcs_logo.path; // Để lấy tên, ta gọi name và cũng return Stringlet name = fcs_logo.name;

Vui lòng không đặt tên thư mục và tên tập tin có các kí tự (character) bị cấm, chẳng hạn #, [, ], *, hoặc ?. Lí do thì bạn hoặc biết rồi, hoặc có thể tự hiểu và tôi sẽ không giải thích gì ở đây. Và cũng không nên đặt tên thư mục và tên tập tin với các kí tự tiếng Việt trừ khi điều đó là vô cùng cần thiết. Tốt nhất, nếu tiếng Anh của bạn tốt thì nên sử dụng tiếng Anh.

3.3.2. Upload files:

Để upload các tập tin lên FCS thì bạn có thể chọn trực tiếp dữ liệu trong bộ nhớ với hàm put(file) – với kiểu là File hoặc Blob, hoặc put(bytes), hoặc putString(string) với string chứa các thông tin thô hay được mã hóa sẵn, thông qua storageRef, và các hàm này sẽ return một đối tượng UploadTask. Bạn có thể thêm các thông tin Metadata như chiều dài tập tin, ngày tạo, kiểu thông qua lớp FullMetadata và truyền FullMetadata instance đó làm tham số (param) thứ hai trong các hàm put(file), put(bytes) hoặc putString(string) tôi đã liệt kê. Cụ thể như sau:

12345678910111213var uploadTask;let storageRef = …; // trỏ tới vị trí bạn mong muốn // Nếu muốn bao gồm metadata thì bạn khởi tạo một instance và thêm các thông tin:let metadata = { … } // metadata là Object // Upload với hàm put(file) và có metadatalet file = …;uploadTask = storageRef.put(file, metadata); // Upload với hàm putString() và không có metadatavar string = ‘5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB’;uploadTask = storageRef.putString(string, “base64”);

Để theo dõi và nhận kết quả của quá trình upload thì ta chỉ việc thêm mấy cái Listener interfaces, điều này quá đỗi quen thuộc với các bạn rồi.

1234567891011uploadTask.on(“state_changed”,   function(snapshot) {   // Lấy progress      var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;   },   function(error) {     // Có lỗi xảy ra, code xử lí ở đây   },   function() {    // Thành công, code xử lí ở đây   });

3.3.3. Download files:

Để download các files thì ta sẽ gọi các hàm getDownloadURL. Bạn cũng có thể chỉ lấy URL trỏ tới file, chẳng hạn để chia sẻ. Cụ thể như sau:

12345678910111213141516171819let storageRef = …; // trỏ tới vị trí bạn mong muốn storageRef.getDownloadURL()   .then(function(url) {     // `url` là URL trỏ tới file, nếu bạn chỉ muốn lấy URL thì chỉ cần thao tác tới đây   String urlToFIle = url;    // Tải về trực tiếp:   var xhr = new XMLHttpRequest();   xhr.responseType = ‘blob’;   xhr.onload = function(event) {     var blob = xhr.response;   };   xhr.open(‘GET’, url);   xhr.send();   })  .catch(function(error) {    // Có lỗi xảy ra  });

3.3.4. Delete files

Để xóa tập tin thì đơn giản là bạn chỉ cần gọi Storage Reference#delete(). Lưu ý là hàm này không return ra kiểu Boolean (với ý nghĩa là return true nếu delete được và false nếu không được), nên bạn sẽ phải tự quan sát xem có error phát sinh hay không.

123456storageRef.delete()  .then(function() {   // Thành công  }).catch(function(error) {   // Cõ lỗi, không xóa được  });

######################################

`Hi vọng các bạn đã nắm bắt được các thao tác cơ bản với Firebase Storage để vận dụng vào công việc viết code của mình. Sẽ còn nhiều vấn đề khác mà chúng tôi sẽ không trình bày thêm, chẳng hạn như các “xô chứa” (Bucket) cũng như các thao tác nâng cao bảo mật mà các bạn sẽ cần nghiên cứu khi cần để tối ưu hóa hạ tầng dữ liệu của mình. Chúc các bạn thành công.

Nguồn: https://eitguide.net/firebase-bai-3-gioi-thieu-ve-cloud-storage/#:~:text=N%C3%B3i%20ng%E1%BA%AFn%20g%E1%BB%8Dn%2C%20Firebase%20Cloud,c%E1%BB%A7a%20ri%C3%AAng%20b%E1%BA%A1n%20thi%E1%BA%BFt%20k%E1%BA%BF.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *