Skip to content

Asset Service

Most admin frameworks ship “upload to a local directory and return a URL.” That works in development and breaks in production: files vanish when a container restarts or scales out, private files have no access check, URLs leak, every project re-writes its own OSS/S3 wiring, and large uploads saturate the application server.

StackRivet treats files as a first-class Asset Service: it manages asset metadata, storage adapters, upload/download security and business references in one place.

Business tables store an assetId, never a raw file URL. A URL is fetched on demand through the Asset API — so it can be short-lived, permission-checked, and independent of which storage backend you use.

business row → assetId → Asset API → (permission check) → short-lived signed URL

This is what keeps private files private and lets you switch storage backends without rewriting business code.

A single StorageAdapter interface covers Local Dev, S3-compatible (e.g. MinIO, AWS S3) and Aliyun OSS. The adapter:

  • Never leaks a cloud-vendor SDK object into the business layer.
  • Converts errors into one unified exception type.
  • Takes endpoint / bucket / region / credentials from configuration (env vars, never committed).
  • Issues short-lived signed URLs by default.

Switching from local storage to S3 or OSS is a configuration change (STACKRIVET_STORAGE_TYPE), not a code change. See the Configure object storage guide.

The storage object key is generated by the system — the user never controls it, and the original filename is kept only in metadata:

{env}/{tenantId}/{yyyy}/{MM}/{dd}/{assetId}.{extension}
# e.g. prod/default/2026/05/27/01JABC.pdf

Private files never get a permanent URL.

An asset moves through a small state machine:

stateDiagram-v2
  [*] --> pending
  pending --> active: upload completed
  pending --> deleted: aborted / expired
  active --> deleted: soft delete
  deleted --> [*]

Community implements pending, active and deleted. Enterprise environments can extend the lifecycle with compliance and security states when required.

FilePath
Small (≤ 8 MB)Through the backend, which writes to the storage adapter
LargeA presigned direct upload — the file goes straight to object storage, so the app server never carries the full file traffic
Very largeMultipart upload sessions (initiate / complete / abort)

Upload responses return a stable assetId, so clients can retry around the returned asset record instead of storing raw URLs or vendor-specific object keys.

A private file’s download URL is only minted after a permission check, via the AssetAccessPolicy SPI:

public interface AssetAccessPolicy {
boolean canRead(CurrentUser user, Asset asset);
boolean canDelete(CurrentUser user, Asset asset);
}

By default the uploader can read, a system administrator can read and delete, and a business module can register its own policy. An unauthorized download returns 403; an unknown/oversized/forbidden upload returns 415 or 413.

Community includes Local / S3-compatible / Aliyun OSS, private signed URLs, small + large + multipart upload, security validation and business references. A recycle bin, CDN domains, image resize/watermark and dedicated asset access logs are Pro; virus scanning, Object Lock and GCS/Azure/COS/OBS are Enterprise — see the pricing page.