private Mono<String> getPresignedDownloadUrl(
            @NotNull String bucket,
            @NotNull String key,
            int expireAfterSeconds) {
        GetPresignedObjectUrlArgs.Builder builder = GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)
                .bucket(bucket)
                .object(key);
        if (expireAfterSeconds > 0) {
            builder.expiry(expireAfterSeconds);
        }
        return PublisherUtil.fromFuture(() -> client.getPresignedObjectUrlAsync(builder.build()))
                .onErrorMap(t -> new RuntimeException(
                        "Failed to get the presigned URL to download the resource object \""
                                + key
                                + "\" in the bucket: \""
                                + bucket
                                + "\"",
                        t));
    }
@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