private Mono<Void> initClientAndBuckets(
            TurmsMongoClient mongoClient,
            MinioStorageProperties properties,
            URI uri,
            String endpoint) {
        messageAttachmentRepository = new MessageAttachmentRepository(mongoClient);
        baseUrl = uri.getScheme()
                + "://"
                + uri.getAuthority();
        MinioStorageProperties.ResourceIdBase62 base62Properties = properties.getResourceId()
                .getBase62();
        MinioStorageProperties.ResourceIdMac macProperties = properties.getResourceId()
                .getMac();
        isMacEnabled = macProperties.isEnabled();
        isBase62Enabled = isMacEnabled || base62Properties.isEnabled();
        if (isBase62Enabled) {
            String charset = base62Properties.getCharset();
            base62 = StringUtil.isEmpty(charset)
                    ? Base62.getDefaultInstance()
                    : new Base62(charset);
        } else {
            base62 = null;
        }
        if (isMacEnabled) {
            byte[] key;
            String base64Key = macProperties.getBase64Key();
            try {
                key = Base64.getDecoder()
                        .decode(base64Key);
            } catch (Exception e) {
                return Mono.error(new IllegalArgumentException(
                        "The HMAC key must be Base64-encoded, but got: "
                                + base64Key,
                        e));
            }
            if (key.length < 16) {
                return Mono.error(new IllegalArgumentException(
                        "The length of HMAC key must be greater than or equal to 16, but got: "
                                + key.length));
            }
            macKey = new SecretKeySpec(key, "HmacMD5");
        } else {
            macKey = null;
        }
        String region = properties.getRegion();
        String accessKey = properties.getAccessKey();
        String secretKey = properties.getSecretKey();
        MinioAsyncClient.Builder builder = MinioAsyncClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey);
        if (StringUtil.isNotBlank(region)) {
            builder.region(region);
        }
        client = new TurmsMinioAsyncClient(builder.build());

        Mono<Void> initBuckets =
                initBuckets().timeout(Duration.ofSeconds(INIT_BUCKETS_TIMEOUT_SECONDS))
                        .doOnSuccess(unused -> isServing = true);

        MinioStorageProperties.Retry retry = properties.getRetry();
        int maxAttempts = retry.getMaxAttempts();
        if (!retry.isEnabled() || maxAttempts <= 0) {
            return initBuckets.onErrorResume(t -> Mono
                    .error(new RuntimeException("Failed to initialize the MinIO client", t)));
        }
        return initBuckets.retryWhen(Retry.max(maxAttempts)
                .doBeforeRetryAsync(retrySignal -> Mono.deferContextual(contextView -> {
                    long totalRetries = retrySignal.totalRetries();
                    try (TracingCloseableContext ignored =
                            TracingContext.getCloseableContext(contextView)) {
                        LOGGER.error("Failed to initialize the MinIO client. Retry times: "
                                + totalRetries, retrySignal.failure());
                    }
                    if (0 == totalRetries) {
                        return Mono.delay(Duration.ofMillis(retry.getInitialIntervalMillis()))
                                .then();
                    }
                    return Mono.delay(Duration.ofMillis(retry.getIntervalMillis()))
                            .then();
                })));
    }
@implNote 1. We use HMAC(key, message) instead of a HASH(key + message) to avoid the length
          extension attack. To put simply, if a hacker knows the signature of the resource
          "1", and he can also know the signature of resource "12", "13", "123", and so on
          without knowledge of the key.
          <p>
          2. Use MD5 because its output size (128 bits) is small, and it is a 22-character
          Base62-encoded string.
turms-plugins/turms-plugin-minio/src/main/java/im/turms/plugin/minio/MinioStorageServiceProvider.java