Storage Configuration
All platform files (site assets, reconciliation files, etc.) are stored through the S3-compatible protocol. MinIO, Aliyun OSS, Tencent COS, Huawei OBS, AWS S3 and other S3-compatible object storage services are supported. Configuration entry: Admin panel → System Config → Platform Config → Storage.
How Upload Works
Files are uploaded via presigned direct upload, in three steps:
- The admin frontend asks the backend for a presigned upload URL;
- The browser uploads the file directly to the object storage (the file never passes through the backend server);
- The backend confirms the file exists and registers the file record.
Therefore one action is mandatory before anything else: configure CORS rules on the bucket, otherwise "Test Connection" passes but real uploads still fail. Example on Aliyun OSS (similar on other providers: bucket → Data Security → CORS):
| Setting | Value |
|---|---|
| Origin | Your admin panel origin, e.g. https://admin.example.com |
| Methods | PUT, GET, HEAD, POST |
| Allow Headers | * |
| Expose Headers | ETag |
Parameters
| Parameter | Required | Description |
|---|---|---|
| Endpoint | Yes | API address of the storage service. Must include https:// and must NOT include the bucket name |
| Region | Depends | See "Do I need the region" below |
| Access Key / Secret Key | Yes | Credentials of the storage service |
| Path Style Access | Yes | Address style switch, see below |
| Public Bucket / Private Bucket | Yes | Bucket names; the same bucket can be used for both, see "Single bucket" |
| Public Base URL | Recommended | External address for public bucket files. Do not include the bucket name (it is appended automatically) |
| Private Base URL | No | Usually leave empty; private files are served through presigned URLs |
| Base Storage Path | No | Common prefix of object keys, e.g. daxpay |
| Upload presign expiration | No | Validity of upload URLs in minutes, default 10 |
| Download presign expiration | No | Validity of private file URLs in hours, default 72 |
Do I need the region
The region only participates in request signing — it does not decide which data center is accessed; that is entirely determined by the endpoint. So in most cases it can be left empty:
| Storage service | Required | Example |
|---|---|---|
| MinIO / self-hosted S3-compatible | Not required, leave empty | — |
| Aliyun OSS | Recommended | cn-beijing, cn-hangzhou |
| Huawei OBS | Recommended | cn-north-4 |
| Tencent COS | Required (validates the signing region) | ap-shanghai, ap-guangzhou |
| AWS S3 | Required | us-east-1, ap-northeast-1 |
| Qiniu Kodo | Required | cn-east-1 |
| Cloudflare R2 | Fixed value | auto |
When left empty, us-east-1 is used for signing. MinIO and other services that do not validate the signing region are not affected. Refer to each provider's S3-compatibility documentation for exact region codes.
Path Style Access
One bucket has two address styles:
- Path style (on):
https://endpoint/bucket/object— required for MinIO and most self-hosted storage - Virtual-hosted style (off):
https://bucket.endpoint-host/object— the native style of cloud providers
How to choose
Always enable it for self-hosted MinIO. Cloud providers usually support both; follow the recommended values in the examples below.
About Public Base URL
The final public file URL = Public Base URL / public bucket / object key. The bucket name is appended automatically, so:
- Fill in the service address without the bucket name, e.g.
https://oss-cn-beijing.aliyuncs.com - If the bucket is bound to a CDN / custom domain, make sure files are reachable via
domain/bucket/object, otherwise images will be broken even though uploads succeeded
Single bucket
The public and private bucket fields accept the same bucket name. In that case set the bucket ACL to public-read: public files are directly accessible and private files still work through presigned URLs.
The trade-off: "private" becomes nominal — anyone holding the full URL can read the file. For production, create two separate buckets so the permission semantics actually hold.
Examples
Example 1: Self-hosted MinIO
| Parameter | Value |
|---|---|
| Endpoint | https://minio.example.com (the MinIO API port, not the console port) |
| Region | Leave empty |
| Path Style Access | On |
| Public Base URL | https://minio.example.com (or its public access domain) |
The keys are the MinIO AccessKey / SecretKey (created via mc admin).
Example 2: Aliyun OSS (Beijing)
| Parameter | Value |
|---|---|
| Endpoint | https://oss-cn-beijing.aliyuncs.com |
| Region | cn-beijing |
| Path Style Access | On |
| Public / Private Bucket | e.g. daxpay-pub / daxpay-priv (or the same bucket name) |
| Public Base URL | https://oss-cn-beijing.aliyuncs.com |
The keys are the AccessKey of a RAM user. Minimal permissions: PutObject, GetObject, DeleteObject, ListObjects, GetBucketInfo on both buckets.
Note: even if the backend runs on Aliyun ECS, do not switch the endpoint to the internal address (ending with -internal) — uploads go from the user's browser directly, and browsers cannot reach internal addresses.
Example 3: Tencent COS (Shanghai)
| Parameter | Value |
|---|---|
| Endpoint | https://cos.ap-shanghai.myqcloud.com |
| Region | ap-shanghai (required) |
| Path Style Access | Off |
| Public Base URL | https://cos.ap-shanghai.myqcloud.com |
The keys are the SecretId / SecretKey of a Tencent Cloud sub-account.
Testing and Troubleshooting
After saving, click "Test Connection". The system runs a full loop on both buckets: bucket exists → write probe file → read → delete. A pass means read/write permissions are ready.
Common errors:
| Symptom | Cause | Fix |
|---|---|---|
| Test passes, upload fails | Bucket has no CORS rules | Configure CORS as in "How Upload Works" |
| Write failed, check upload permission | Key lacks write permission / bucket policy denies writes / bucket enforces KMS encryption without KMS permission | Search 探测写入失败 (probe write failed) in backend logs for the real status code; temporarily grant full permissions to narrow it down |
| File uploaded but image not displayed | Public Base URL contains the bucket name, or the public bucket is private | Remove the bucket name from the URL; set the public bucket to public-read |
| Upload succeeds but the record stays pending | Key can write but cannot read | Add read permission (GetObject) |
WARNING
"A record exists in the file list" does not mean the upload succeeded — the record is created as soon as the upload URL is requested. Only a truly accessible file counts.