private Mono<Void> initBuckets() {
        StorageResourceType[] resourceTypes =
                ClassUtil.getSharedEnumConstants(StorageResourceType.class);
        List<Mono<Void>> initBuckets = new ArrayList<>(resourceTypes.length - 1);
        for (StorageResourceType resourceType : resourceTypes) {
            if (resourceType == StorageResourceType.UNRECOGNIZED) {
                continue;
            }
            String bucket = getBucketName(resourceType);
            Mono<Void> initBucket = bucketExists(bucket).flatMap(exists -> {
                if (exists) {
                    LOGGER.info("The bucket \"{}\" has already existed", bucket);
                    return Mono.empty();
                }
                StorageItemProperties itemProperties = switch (resourceType) {
                    case USER_PROFILE_PICTURE -> storageProperties.getUserProfilePicture();
                    case GROUP_PROFILE_PICTURE -> storageProperties.getGroupProfilePicture();
                    case MESSAGE_ATTACHMENT -> storageProperties.getMessageAttachment();
                    default -> throw ResponseException.get(ResponseStatusCode.ILLEGAL_ARGUMENT,
                            "Unknown storage resource type: "
                                    + resourceType);
                };
                Mono<Void> createBucket = createBucket(resourceType).then(Mono.defer(
                        () -> setBucketLifecycle(bucket, itemProperties.getExpireAfterDays())));
                if (resourceType == StorageResourceType.USER_PROFILE_PICTURE
                        || resourceType == GROUP_PROFILE_PICTURE) {
                    createBucket = createBucket.then(Mono.defer(
                            () -> setBucketPolicy(bucket, itemProperties.getAllowedReferrers())));
                }
                return createBucket.doOnSuccess(
                        unused -> LOGGER.info("The bucket \"{}\" has been created", bucket));
            })
                    .onErrorMap(
                            t -> new RuntimeException("Caught an error while creating buckets", t));
            initBuckets.add(initBucket);
        }
        return Mono.whenDelayError(initBuckets);
    }
@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