private Map<String, String> getResourceUploadInfo(
            @NotNull String bucket,
            @NotNull String objectKey,
            @NotNull String allowedContentType,
            int minSizeBytes,
            int maxSizeBytes,
            int expireAfterSeconds) {
        PostPolicy policy = new PostPolicy(
                bucket,
                ZonedDateTime.now()
                        .plusSeconds(expireAfterSeconds));
        policy.addEqualsCondition("key", objectKey);
        MediaType mediaType = MediaType.create(allowedContentType);
        if (mediaType.isWildcardType()) {
            policy.addStartsWithCondition(HTTP_HEADER_CONTENT_TYPE, "");
        } else {
            if (mediaType.isWildcardSubtype()) {
                policy.addStartsWithCondition(HTTP_HEADER_CONTENT_TYPE,
                        mediaType.type()
                                + "/");
            } else {
                policy.addEqualsCondition(HTTP_HEADER_CONTENT_TYPE, mediaType.toString());
            }
        }
        boolean hasMaxSizeLimit = maxSizeBytes > 0;
        if (minSizeBytes > 0 || hasMaxSizeLimit) {
            if (hasMaxSizeLimit && minSizeBytes > maxSizeBytes) {
                throw new IllegalArgumentException(
                        "The minimum size cannot be greater than the maximum size");
            }
            policy.addContentLengthRangeCondition(minSizeBytes, maxSizeBytes);
        }
        try {
            Map<String, String> map = client.getPresignedPostFormData(policy);
            map = CollectionUtil.add(map, RESOURCE_ID, objectKey);
            map.put(RESOURCE_URL,
                    baseUrl
                            + "/"
                            + bucket);
            return map;
        } catch (Exception e) {
            throw new RuntimeException(
                    "Failed to get the presigned post form data for the resource object \""
                            + objectKey
                            + "\" in the bucket \""
                            + bucket
                            + "\"",
                    e);
        }
    }
@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