How to Configure File Storage with X File Storage for S3 Compatibility in ContiNew Admin

To configure S3-compatible storage in ContiNew Admin, create an OSS-type storage record with your endpoint and credentials, which the system loads into X File Storage via StorageServiceImpl.load() to generate an AWS SDK 2.x S3Client for multipart uploads.

ContiNew Admin leverages the X File Storage library from the Dromara ecosystem to abstract file operations across multiple backends. When you need to integrate Amazon S3, MinIO, or any S3-compatible service, you configure an OSS (Object Storage Service) entry that dynamically builds an AmazonS3Config and delegates operations to S3StorageHandler.

Creating an OSS Storage Record

ContiNew Admin stores storage configurations in the database and loads them at runtime. To enable S3 compatibility, you must create a record with type = OSS.

Required Configuration Fields

When creating the storage entry—whether through the admin interface or the REST API—populate these fields exactly as specified:

  • type: Fixed value OSS (case-sensitive, validated by StorageTypeEnum.OSS.validate)
  • code: Unique platform identifier used by X File Storage (e.g., my-s3)
  • endpoint: Base URL of the S3-compatible service without trailing slash (e.g., https://s3.amazonaws.com or https://minio.example.com)
  • accessKey: IAM access key ID
  • secretKey: Secret access key (must be RSA-encrypted before transmission; decrypted server-side in StorageServiceImpl.beforeCreate)
  • bucketName: Target bucket name, must end with a trailing slash / (e.g., my-bucket/)
  • domain (optional): Public CDN prefix for direct URLs, must end with /
  • recycleBinEnabled / recycleBinPath (optional): Enable soft-delete functionality with a path ending in /

Via the Admin UI

Navigate to System → Storage, click Create, select OSS, and complete the form. The frontend automatically encrypts the secretKey using the RSA public key bundled with the application before sending the payload to POST /api/system/storage.

Via REST API

curl -X POST "http://localhost:8080/api/system/storage" \
     -H "Content-Type: application/json" \
     -d '{
           "type": "OSS",
           "code": "my-s3",
           "endpoint": "https://s3.amazonaws.com",
           "accessKey": "AKIAIOSFODNN7EXAMPLE",
           "secretKey": "<RSA-ENCRYPTED-SECRET>",
           "bucketName": "production-uploads/",
           "domain": "https://cdn.example.com/",
           "recycleBinEnabled": true,
           "recycleBinPath": "trash/"
         }'

The server decrypts the secret key in StorageServiceImpl.beforeCreate (lines 66-73) before persisting to the database.

Loading the Storage into X File Storage

When the application context refreshes or a storage is enabled, StorageServiceImpl.load() instantiates the X File Storage platform:

public void load(StorageDO storage) {
    switch (storage.getType()) {
        case OSS -> {
            FileStorageProperties.AmazonS3Config config = new FileStorageProperties.AmazonS3Config();
            config.setPlatform(storage.getCode());
            config.setAccessKey(storage.getAccessKey());
            config.setSecretKey(storage.getSecretKey());
            config.setEndPoint(storage.getEndpoint());
            config.setBucketName(storage.getBucketName());
            fileStorageList.addAll(FileStorageServiceBuilder.buildAmazonS3FileStorage(
                Collections.singletonList(config), null));
        }
        // ...
    }
}

This method (located in continew-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java) bridges the ContiNew Admin domain model with X File Storage's builder API, registering the platform for subsequent file operations.

S3 Client Initialization and Caching

S3ClientFactory (in continew-system/src/main/java/top/continew/admin/system/factory/S3ClientFactory.java) manages AWS SDK 2.x client instances with a unique cache key combining endpoint and access key:

public S3Client getClient(StorageDO storage) {
    String key = storage.getEndpoint() + "|" + storage.getAccessKey();
    return CLIENT_CACHE.computeIfAbsent(key, k -> {
        StaticCredentialsProvider auth = StaticCredentialsProvider.create(
            AwsBasicCredentials.create(storage.getAccessKey(), storage.getSecretKey()));
        return S3Client.builder()
            .credentialsProvider(auth)
            .endpointOverride(URI.create(storage.getEndpoint()))
            .region(Region.US_EAST_1)
            .serviceConfiguration(S3Configuration.builder()
                .chunkedEncodingEnabled(false).build())
            .build();
    });
}

This design supports simultaneous connections to multiple S3-compatible endpoints (e.g., AWS S3 and a private MinIO cluster) within the same application instance.

File Operations and Multipart Uploads

All S3-specific logic resides in S3StorageHandler (continew-system/src/main/java/top/continew/admin/system/handler/impl/S3StorageHandler.java). This handler normalizes object keys and manages the multipart upload lifecycle:

  • Key normalization: buildS3Key() strips leading slashes and collapses duplicate slashes (e.g., /folder//file.png becomes folder/file.png)
  • Initiate upload: initMultipartUpload() creates a CreateMultipartUploadRequest and stores the returned uploadId
  • Part upload: uploadPart() streams MultipartFile bytes via UploadPartRequest
  • Completion: completeMultipartUpload() aggregates part ETags into a CompletedMultipartUpload
  • Cleanup: cleanPart() aborts incomplete multipart uploads to prevent orphaned storage charges

Practical Implementation Example

After configuring the storage record with code my-s3, use the FileService API to upload files:

import top.continew.admin.system.service.StorageService;
import top.continew.admin.system.service.FileService;
import org.dromara.x.file.storage.core.FileInfo;
import org.springframework.web.multipart.MultipartFile;

@Autowired
private StorageService storageService;

@Autowired
private FileService fileService;

public String uploadToS3(MultipartFile file) {
    // Retrieve the configured storage (null uses default)
    StorageDO storage = storageService.getByCode("my-s3");
    
    // Execute upload; parentPath can be empty for root
    FileInfo fileInfo = fileService.upload(
        file,           // MultipartFile from controller
        "",             // parentPath (optional subdirectory)
        storage.getCode() // platform code
    );
    
    // Returns public URL from configured domain or S3 endpoint
    return fileInfo.getUrl();
}

The FileService.upload method dispatches to S3StorageHandler based on the storage type enum (StorageDO.getType()), handling all multipart negotiation transparently.

Summary

  • OSS storage type: Use type = OSS in StorageTypeEnum for all S3-compatible services
  • Trailing slashes required: Configure bucketName and optional domain/recycleBinPath with trailing / characters
  • Client-side encryption: Always encrypt secretKey using RSA before API submission; decryption occurs in StorageServiceImpl.beforeCreate
  • Dynamic loading: StorageServiceImpl.load() builds AmazonS3Config objects that X File Storage transforms into SDK clients via S3ClientFactory
  • Key normalization: S3StorageHandler.buildS3Key() ensures S3 object keys comply with path conventions
  • Multipart support: Large files automatically use multipart upload with initMultipartUpload, uploadPart, and completeMultipartUpload operations

Frequently Asked Questions

How do I configure MinIO instead of AWS S3?

Set the endpoint to your MinIO server URL (e.g., https://minio.example.com) and provide the MinIO access/secret keys. ContiNew Admin treats all S3-compatible services identically once the OSS record is created; S3ClientFactory automatically configures the endpoint override in the AWS SDK builder.

Why does my upload fail with "Invalid bucket name" errors?

Verify that your bucketName field ends with a trailing slash (/). The system concatenates this value directly with the object key in S3StorageHandler. Additionally, ensure the endpoint contains no trailing slash, as this creates malformed URLs when the SDK appends bucket paths.

Can I use multiple S3 buckets simultaneously?

Yes. Create separate storage records with unique code values (e.g., s3-primary, s3-archive). StorageServiceImpl.load() registers each as a distinct platform in X File Storage. When uploading, specify the desired platform code in FileService.upload(), or set a default storage in the system configuration.

Is the secret key stored securely in the database?

Yes. The client encrypts the secret using the server's RSA public key before transmission. Upon receipt, StorageServiceImpl.beforeCreate decrypts it using the private key, then X File Storage may re-encrypt or handle it according to your configured FileStorageProperties security settings. Always ensure the RSA key pair is properly rotated according to your security policy.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →