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 byStorageTypeEnum.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.comorhttps://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.pngbecomesfolder/file.png) - Initiate upload:
initMultipartUpload()creates aCreateMultipartUploadRequestand stores the returneduploadId - Part upload:
uploadPart()streamsMultipartFilebytes viaUploadPartRequest - Completion:
completeMultipartUpload()aggregates part ETags into aCompletedMultipartUpload - 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 = OSSinStorageTypeEnumfor all S3-compatible services - Trailing slashes required: Configure
bucketNameand optionaldomain/recycleBinPathwith trailing/characters - Client-side encryption: Always encrypt
secretKeyusing RSA before API submission; decryption occurs inStorageServiceImpl.beforeCreate - Dynamic loading:
StorageServiceImpl.load()buildsAmazonS3Configobjects that X File Storage transforms into SDK clients viaS3ClientFactory - Key normalization:
S3StorageHandler.buildS3Key()ensures S3 object keys comply with path conventions - Multipart support: Large files automatically use multipart upload with
initMultipartUpload,uploadPart, andcompleteMultipartUploadoperations
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →