PRD: Quản lý asset & media
| Module | Nền tảng (CORE-16) | PRD ID | PRD-AST-001 |
| Status | Shipped | Owner | Platform squad |
| Date | 2026-06-15 | Version | v1.0 |
| Packages | @nx/asset · @nx/core | URD | AST · MTL · BNK |
TL;DR
Cho mọi service của KICKO một cách dùng chung để lưu, định địa chỉ, và phục vụ media nhị phân - ảnh sản phẩm, logo tổ chức, tài liệu - trên object storage tương thích S3. Một upload multipart được xác thực đặt mỗi tệp dưới một object name duy nhất, an toàn URL, và ghi một meta-link bền vững: tọa độ lưu trữ của object cộng mimetype, size, etag của nó, và một ràng buộc tùy chọn tới thực thể nghiệp vụ nó thuộc về (một product, một variant, một organizer, một ledger…). Object stream lại inline hoặc tải về dạng attachment theo tên; meta-link truy vấn được như một tài nguyên CRUD nên mọi object tìm được theo chủ của nó. Cùng backbone đó phục vụ một bundle bản địa hóa dùng chung và một registry ngân hàng Việt Nam chỉ-đọc kèm logo ngân hàng, để storefront và back office render lựa chọn ngân hàng và bản dịch từ một nguồn.
1. Bối cảnh & Vấn đề
Mỗi module sản phẩm cần đính kèm media. Product và variant cần ảnh, organizer cần logo, ledger và ticket cần tài liệu hỗ trợ, và storefront cần logo ngân hàng cùng một bundle bản dịch dùng chung. Không có một backbone dùng chung, mỗi service sẽ tự phát minh đường upload riêng, sơ đồ đặt tên object riêng, và cách riêng để nhớ tệp nào thuộc bản ghi nào - và dữ liệu tham chiếu ngân hàng sẽ bị copy-paste và trôi dạt.
Khoảng trống có hai phần. Thứ nhất, một primitive lưu-và-nhớ: một nơi để đặt một nhị phân, lấy lại một link ổn định, và giữ một bản ghi bền vững gắn object với thực thể nó minh họa, để catalogue tìm được "ảnh cho variant này" mà không phải quét bucket. Thứ hai, media tham chiếu dùng chung: một bundle bản địa hóa và một registry ngân hàng Việt Nam (kèm logo) mà cả nền tảng đọc thay vì mỗi app mang bản sao riêng.
PRD này đặc tả backbone media dùng chung đó: upload được xác thực lên object storage với một bản ghi meta-link cho mỗi object, ràng buộc principal và một API truy vấn meta-link, phục vụ và liệt kê công khai, bundle bản địa hóa, và registry ngân hàng cùng các endpoint logo.
2. Mục tiêu & Không-mục-tiêu
Mục tiêu
- Một upload multipart được xác thực lên một bucket tương thích S3 gán cho mỗi tệp một object name duy nhất, an toàn URL, và trả về một link định địa chỉ được (
AST). - Một bản ghi meta-link cho mỗi object đã lưu ghi tọa độ lưu trữ, mimetype, size, etag, metadata, và storage type của nó (
MTL). - Ràng buộc principal tại lúc upload - một object có thể mang
principalType/principalId/variantđể tìm được theo thực thể nó thuộc về (MTL). - Phục vụ công khai: stream một object inline theo tên, tải về dạng attachment, và liệt kê một bucket theo prefix; cộng delete xóa cả object và meta-link cùng nhau (
AST). - Một bundle bản địa hóa dùng chung phục vụ inline và tải về được từ bucket cấu hình (
AST). - Một registry ngân hàng Việt Nam chỉ-đọc dạng JSON - mỗi entry kèm tên, cờ năng lực, và một logo URL tuyệt đối - cộng một endpoint logo PNG theo từng ngân hàng (
BNK). - Định địa chỉ an toàn: object name / độ sâu thư mục được kiểm tra, tên tệp logo nghiêm ngặt, header phản hồi trong danh sách trắng, và
nosnifftrên mọi stream (AST,BNK).
Không-mục-tiêu
- Biến đổi ảnh, tạo thumbnail, hoặc resize động - object được phục vụ như đã lưu.
- Cô lập bucket theo từng merchant hoặc lưu trữ giới hạn theo tenant - một bucket cấu hình duy nhất hậu thuẫn service chủ.
- Một UI duyệt thư viện media được quản lý - PRD này cung cấp bề mặt API, không phải màn hình gallery.
- Tác giả hoặc sửa registry ngân hàng - đây là dữ liệu tham chiếu chỉ-đọc đóng gói cùng service.
- Pipeline thông báo real-time (ACT, WSS) - đặc tả trong PRD-ACT-001.
3. Chỉ số thành công
| Chỉ số | Mục tiêu / tín hiệu |
|---|---|
| Toàn vẹn upload | Mọi object lưu thành công có một meta-link khớp với size, mimetype, và etag của nó |
| Khả năng tìm | Một object upload kèm ràng buộc principal lấy được theo type + id của principal đó |
| An toàn đặt tên | Không object name nào trùng hoặc mang đường dẫn không an toàn; mọi stream đặt nosniff |
| Tái dùng tham chiếu | Storefront và back office render lựa chọn và logo ngân hàng từ một registry, không phải bản sao cục bộ |
| Toàn vẹn dọn dẹp | Xóa một object không để lại meta-link mồ côi cho bucket + object đó |
4. Nhân vật & Tình huống
| Nhân vật | Mục tiêu trong tính năng |
|---|---|
| Owner / Manager | Đính một ảnh vào product hoặc một logo vào tổ chức và hiện nó ở mọi nơi thực thể đó xuất hiện |
| Service tạo sự kiện | Upload một object, ràng buộc nó với một bản ghi, và sau đó phân giải media của bản ghi theo meta-link |
| Storefront / client | Stream một ảnh theo tên, render lựa chọn và logo ngân hàng, và tải bundle bản dịch dùng chung |
| Người vận hành nền tảng | Tin rằng object name an toàn và delete dọn dẹp cả lưu trữ lẫn metadata |
Tình huống cốt lõi: một manager thêm một ảnh khi sửa một variant. Client upload tệp kèm principalType và principalId của variant; service lưu nó dưới một object name duy nhất, trả về link, và ghi một meta-link ghi object và ràng buộc của nó. Bất cứ đâu variant đó xuất hiện sau này, media của nó được phân giải theo meta-link. Riêng biệt, màn hình checkout lấy registry ngân hàng một lần và render mỗi nhà cung cấp kèm logo URL tuyệt đối của nó.
5. User Stories
- Là một manager, tôi đính một ảnh vào một product và nó xuất hiện ở mọi nơi product hiện, để catalogue trông hoàn chỉnh.
- Là một service tạo sự kiện, tôi ràng buộc một object đã upload với bản ghi nó minh họa, để sau này tìm được media của bản ghi mà không phải quét bucket.
- Là một client, tôi stream một ảnh theo object name và tải một tài liệu dạng attachment, để media chỉ việc hoạt động trong UI.
- Là một client, tôi đọc một registry ngân hàng kèm logo URL tuyệt đối, để render lựa chọn thanh toán mà không tự nối đường dẫn.
- Là một người vận hành, tôi xóa một object và tin rằng meta-link của nó cũng được dọn, để không gì lủng lẳng.
- Là một client, tôi tải một bundle bản địa hóa dùng chung, để mọi bề mặt dịch từ cùng một nguồn.
6. Yêu cầu chức năng
| # | Yêu cầu | URD ref |
|---|---|---|
| FR-1 | Upload multipart được xác thực lưu một hoặc nhiều tệp trong bucket cấu hình, mỗi cái dưới một object name duy nhất an toàn URL, trả về object name và link định địa chỉ của nó | URD-AST-001 |
| FR-2 | Một upload có thể mang một folder path (được kiểm tra, tối đa hai cấp) và một ràng buộc principal (principalType / principalId / variant) | URD-AST-002 · URD-MTL-002 |
| FR-3 | Sau khi lưu thành công, một meta-link được tạo cho mỗi object ghi bucket, object name, link, mimetype, size, etag, metadata, storage type, và cờ sync | URD-MTL-001 |
| FR-4 | Một object đã lưu stream inline theo object name; đường dẫn object lồng tới hai cấp thư mục được hỗ trợ | URD-AST-003 |
| FR-5 | Một object đã lưu tải về dạng attachment (content-disposition) theo object name | URD-AST-004 |
| FR-6 | Một delete được xác thực xóa object khỏi lưu trữ và dọn các bản ghi meta-link của nó cho bucket + object đó | URD-AST-005 · URD-MTL-004 |
| FR-7 | Một list được xác thực trả về các object của một bucket lọc theo prefix, recursion, và max-keys | URD-AST-006 |
| FR-8 | Một bundle bản địa hóa dùng chung phục vụ inline và tải về được dạng attachment từ bucket cấu hình | URD-AST-007 |
| FR-9 | Meta-link được phơi bày như một tài nguyên CRUD (find, find-by-id, find-one, count, create, update, delete) qua xác thực JWT / Basic | URD-MTL-003 |
| FR-10 | Registry ngân hàng Việt Nam phục vụ dạng JSON - mỗi entry với tên ngắn, tên đầy đủ, cờ năng lực (VietQR, disburse, NAPAS), và một logo URL tuyệt đối | URD-BNK-001..002 |
| FR-11 | Mỗi logo ngân hàng phục vụ dạng PNG theo tên tệp <code>.png, kiểm tra theo một mẫu nghiêm ngặt, với caching immutable lâu dài | URD-BNK-003 |
| FR-12 | Object name, folder path, và tên tệp logo được kiểm tra; chỉ các header metadata trong danh sách trắng được phản chiếu và nosniff được đặt trên mọi stream | URD-AST-008 · URD-BNK-004 |
Toàn văn yêu cầu và tiêu chí chấp nhận sống trong Platform URD - AST · MTL · BNK. PRD này tham chiếu chúng thay vì lặp lại.
7. Yêu cầu phi chức năng
| Lĩnh vực | Yêu cầu |
|---|---|
| An toàn đặt tên | Object name là duy nhất và an toàn URL; độ sâu thư mục bị giới hạn và mọi segment đường dẫn được kiểm tra trước khi lưu hoặc lấy |
| Vệ sinh header | Chỉ các header metadata trong danh sách trắng được phản chiếu lên phản hồi; nosniff luôn được đặt; giá trị header bị loại CR/LF |
| Trừu tượng lưu trữ | Lưu trữ được tiếp cận qua một helper tương thích S3 duy nhất cấu hình từ môi trường (endpoint, access key, secret key, bucket) |
| Khả năng phục hồi | Một meta-link ghi lỗi không làm mất object đã lưu - object được báo cáo kèm lỗi meta-link theo từng tệp; dọn meta-link khi delete là best-effort và được log |
| Caching | Registry ngân hàng cacheable (max-age); logo ngân hàng phục vụ immutable với max-age dài |
| Xác thực | Upload, delete, list, và API CRUD meta-link cần xác thực JWT / Basic; đọc object, bundle bản địa hóa, và registry / logo ngân hàng là đọc công khai |
| i18n | Tên hiển thị ngân hàng và bundle bản địa hóa dùng chung là nguồn bản địa hóa; entry ngân hàng mang cả tên ngắn lẫn tên đầy đủ |
8. UX & Luồng
Bề mặt upload nhận một hoặc nhiều tệp kèm một folder path tùy chọn và một ràng buộc principal, và trả về object name, link, và meta-link (hoặc lỗi meta-link) của mỗi object. Đọc stream theo object name inline hoặc dạng download; một bucket liệt kê được theo prefix. Registry ngân hàng và logo theo từng ngân hàng được lấy thẳng vào UI.
9. Dữ liệu & Miền
| Thực thể | Vai trò |
|---|---|
MetaLink | Bản ghi bền vững của một object đã lưu - bucket, object name, link, mimetype, size, etag, metadata, storage type, cờ sync - cộng một ràng buộc principal tùy chọn (principalType, principalId, variant) |
Stored object | Nhị phân trong bucket tương thích S3, định địa chỉ theo object name và tiếp cận qua link của nó |
Bank registry entry | Một bản ghi tham chiếu chỉ-đọc - tên ngắn, tên đầy đủ, cờ năng lực (VietQR, disburse, NAPAS), và một logo - đóng gói cùng service |
Chỉ khái niệm - toàn bộ schema và bất biến sống trong asset domain model. Quan hệ principal là soft reference phân giải theo type + id, không phải khóa ngoại database.
10. Phụ thuộc & Giả định
Phụ thuộc vào
- Object storage tương thích S3 - một endpoint, access key, secret key, và một bucket mặc định được cấu hình trong môi trường.
@nx/core- sở hữu schema / model / repositoryMetaLink, registry principal-type, và các environment key dùng chung.- Một explorer base URL cấu hình - dùng để chiếu các đường dẫn logo ngân hàng tương đối thành URL tuyệt đối cho client.
Giả định
- Service tạo sự kiện biết
principalType/principalIdcủa bản ghi nó đang đính media. - Bundle bản địa hóa và các asset logo banks-VN có mặt trong bucket cấu hình / đóng gói cùng service.
- Một bucket cấu hình duy nhất là đủ cho service chủ; cô lập đa-bucket / theo-từng-tenant ngoài phạm vi ở đây.
11. Rủi ro & Câu hỏi mở
| Rủi ro / câu hỏi | Giảm thiểu / trạng thái |
|---|---|
| Object đã lưu nhưng meta-link ghi lỗi | Object không bị mất - được trả về kèm lỗi meta-link theo từng tệp và được log để đối soát |
| Object name không an toàn hoặc trùng | Tên được tạo duy nhất và an toàn URL; độ sâu thư mục bị giới hạn và segment được kiểm tra; đọc từ chối đường dẫn không hợp lệ |
| Ủy quyền CRUD meta-link hiện đang dễ dãi | Follow-up đã biết - siết API meta-link về tập permission của nó được theo dõi (URD-CON-005) |
| Path traversal logo ngân hàng | Tên tệp logo phải khớp mẫu <code>.png nghiêm ngặt; bất cứ gì khác bị từ chối trước khi truy cập đĩa |
| Registry ngân hàng trôi dạt giữa các app | Một registry chỉ-đọc duy nhất phục vụ từ một nguồn; các app ngừng mang bản sao cục bộ |
12. Kế hoạch phát hành & Tiêu chí ra mắt
| Khía cạnh | Kế hoạch |
|---|---|
| Phase | P2 - AST, MTL, BNK trong URD feature catalog |
| Rollout | Được mount bởi các service chủ cần media; không feature flag |
| Migration | Không - store MetaLink và các asset ngân hàng đóng gói ship cùng service |
| Tiêu chí ra mắt | Upload lưu object kèm một meta-link và ràng buộc tùy chọn; object stream inline và tải theo tên; delete dọn cả lưu trữ lẫn meta-link; registry ngân hàng trả về entry kèm logo URL tuyệt đối và logo phục vụ dạng PNG |
| Giám sát | Tỷ lệ lỗi upload, tỷ lệ meta-link ghi lỗi, nhất quán object-vs-meta-link, hành vi cache hit registry / logo |
13. FAQ
Tệp upload đi đâu? Vào bucket tương thích S3 cấu hình, mỗi cái dưới một object name duy nhất, an toàn URL, với một link định địa chỉ trả về cho caller.
Làm sao tìm media cho một product hoặc variant cụ thể? Ràng buộc object tại lúc upload với principalType và principalId của nó; meta-link khi đó làm nó tìm được theo thực thể đó qua API truy vấn meta-link.
Điều gì xảy ra nếu meta-link không ghi được sau khi một tệp đã lưu? Object không bị mất - kết quả upload báo tệp đó kèm một lỗi meta-link để đối soát; các tệp khác trong cùng upload không bị ảnh hưởng.
Object upload có công khai không? Đọc object, bundle bản địa hóa, và registry / logo ngân hàng là đọc công khai; upload, delete, list, và API CRUD meta-link cần xác thực.
Tôi có sửa được registry ngân hàng không? Không - đây là dữ liệu tham chiếu chỉ-đọc đóng gói cùng service. Mỗi entry được chiếu kèm một logo URL tuyệt đối để client dùng trực tiếp.
Xóa một object có để lại bản ghi của nó không? Không - một delete xóa object khỏi lưu trữ và dọn các bản ghi meta-link của nó cho bucket + object đó.
Trang liên quan
- URD: Platform - AST · MTL · BNK
- PRD anh em: Activity notifications & websocket push
- Module: Nền tảng - tổng quan + traceability
- Developer: @nx/asset · storage · meta-links · @nx/core