internal virtual async Task <string> PresignedObjectAsync(string bucketName
                                                                  , string objectName
                                                                  , int expiresInt
                                                                  , PresignedObjectType type
                                                                  , Func <string, string, int, Task <string> > PresignedFunc)
        {
            try
            {
                if (string.IsNullOrEmpty(bucketName))
                {
                    throw new ArgumentNullException(nameof(bucketName));
                }
                objectName = FormatObjectName(objectName);
                if (expiresInt <= 0)
                {
                    throw new Exception("ExpiresIn time can not less than 0.");
                }
                if (expiresInt > 7 * 24 * 3600)
                {
                    throw new Exception("ExpiresIn time no more than 7 days.");
                }
                const int minExpiresInt = 600;

                if (Options.IsEnableCache && expiresInt > minExpiresInt)
                {
                    string            key         = BuildPresignedObjectCacheKey(bucketName, objectName, type);
                    var               cacheResult = _cache.Get <PresignedUrlCache>(key);
                    PresignedUrlCache cache       = cacheResult != null ? cacheResult : null;
                    //Unix时间
                    long nowTime = TimeUtil.Timestamp();
                    //缓存中存在,且有效时间不低于10分钟
                    if (cache != null &&
                        cache.Type == type &&
                        cache.CreateTime > 0 &&
                        (cache.CreateTime + expiresInt - nowTime) > minExpiresInt &&
                        cache.Name == objectName &&
                        cache.BucketName == bucketName)
                    {
                        return(cache.Url);
                    }
                    else
                    {
                        string presignedUrl = await PresignedFunc(bucketName, objectName, expiresInt);

                        if (string.IsNullOrEmpty(presignedUrl))
                        {
                            throw new Exception("Presigned object url failed.");
                        }
                        PresignedUrlCache urlCache = new PresignedUrlCache()
                        {
                            Url        = presignedUrl,
                            CreateTime = nowTime,
                            Name       = objectName,
                            BucketName = bucketName,
                            Type       = type
                        };
                        int randomSec = new Random().Next(0, 10);
                        _cache.Set(key, urlCache, TimeSpan.FromSeconds(expiresInt + randomSec));
                        return(urlCache.Url);
                    }
                }
                else
                {
                    string presignedUrl = await PresignedFunc(bucketName, objectName, expiresInt);

                    if (string.IsNullOrEmpty(presignedUrl))
                    {
                        throw new Exception("Presigned object url failed.");
                    }
                    return(presignedUrl);
                }
            }
            catch (Exception ex)
            {
                throw new Exception($"Presigned {(type == PresignedObjectType.Get ? "get" : "put")} url for object '{objectName}' from {bucketName} failed. {ex.Message}", ex);
            }
        }
        private async Task <string> PresignedObjectAsync(string bucketName, string objectName, int expiresInt, PresignedObjectType type)
        {
            if (string.IsNullOrEmpty(bucketName))
            {
                throw new ArgumentNullException(nameof(bucketName));
            }
            if (string.IsNullOrEmpty(objectName))
            {
                throw new ArgumentNullException(nameof(objectName));
            }
            if (expiresInt <= 0)
            {
                throw new Exception("ExpiresIn time can not less than 0.");
            }
            if (expiresInt > 7 * 24 * 3600)
            {
                throw new Exception("ExpiresIn time no more than 7 days.");
            }
            long      nowTime       = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000;
            const int minExpiresInt = 600;
            string    key           = Encrypt.MD5($"{bucketName}_{objectName}_{type.ToString().ToUpper()}");
            string    objectUrl     = null;

            //查找缓存
            if (Options.IsEnableCache && (expiresInt > minExpiresInt))
            {
                var cacheResult = await _cache.GetAsync <PresignedUrlCache>(key);

                PresignedUrlCache cache = cacheResult.HasValue ? cacheResult.Value : null;
                //缓存中存在,且有效时间不低于10分钟
                if (cache != null &&
                    cache.Type == type &&
                    cache.CreateTime > 0 &&
                    (cache.CreateTime + expiresInt - nowTime) > minExpiresInt &&
                    cache.Name == objectName &&
                    cache.BucketName == bucketName)
                {
                    return(cache.Url);
                }
            }
            if (type == PresignedObjectType.Get)
            {
                //生成URL
                AccessMode accessMode = await this.GetObjectAclAsync(bucketName, objectName);

                if (accessMode == AccessMode.PublicRead || accessMode == AccessMode.PublicReadWrite)
                {
                    string bucketUrl = await this.GetBucketEndpointAsync(bucketName);

                    objectUrl = $"{bucketUrl}{(objectName.StartsWith("/") ? "" : "/")}{objectName}";
                }
                else
                {
                    var req = new GeneratePresignedUriRequest(bucketName, objectName, SignHttpMethod.Get)
                    {
                        Expiration = DateTime.Now.AddSeconds(expiresInt)
                    };
                    var uri = _client.GeneratePresignedUri(req);
                    if (uri != null)
                    {
                        objectUrl = uri.ToString();
                    }
                }
            }
            else
            {
                var req = new GeneratePresignedUriRequest(bucketName, objectName, SignHttpMethod.Put)
                {
                    Expiration = DateTime.Now.AddSeconds(expiresInt)
                };
                var uri = _client.GeneratePresignedUri(req);
                if (uri != null)
                {
                    objectUrl = uri.ToString();
                }
            }
            if (string.IsNullOrEmpty(objectUrl))
            {
                throw new Exception("Presigned get object url failed.");
            }
            //save cache
            if (Options.IsEnableCache && expiresInt > minExpiresInt)
            {
                PresignedUrlCache urlCache = new PresignedUrlCache()
                {
                    Url        = objectUrl,
                    CreateTime = nowTime,
                    Name       = objectName,
                    BucketName = bucketName,
                    Type       = type
                };
                int randomSec = new Random().Next(5, 30);
                await _cache.SetAsync(key, urlCache, TimeSpan.FromSeconds(expiresInt + randomSec));
            }
            return(objectUrl);
        }
        private async Task <string> PresignedObjectAsync(string bucketName, string objectName, int expiresInt, PresignedObjectType type)
        {
            if (string.IsNullOrEmpty(bucketName))
            {
                throw new ArgumentNullException(nameof(bucketName));
            }
            if (string.IsNullOrEmpty(objectName))
            {
                throw new ArgumentNullException(nameof(objectName));
            }
            if (expiresInt <= 0)
            {
                throw new Exception("ExpiresIn time can not less than 0.");
            }
            if (expiresInt > 7 * 24 * 3600)
            {
                throw new Exception("ExpiresIn time no more than 7 days.");
            }
            long               nowTime            = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000;
            const int          minExpiresInt      = 600;
            string             key                = Encrypt.MD5($"{bucketName}_{objectName}_{type.ToString().ToUpper()}");
            string             objectUrl          = null;
            string             newBucketName      = ConvertBucketName(bucketName);
            PreSignatureStruct preSignatureStruct = new PreSignatureStruct()
            {
                appid              = Options.Endpoint,
                region             = Options.Region,
                bucket             = newBucketName,
                key                = objectName,
                httpMethod         = type.ToString().ToUpper(),
                isHttps            = Options.IsEnableHttps,
                signDurationSecond = expiresInt,
                headers            = null,
                queryParameters    = null,
            };

            //查找缓存
            if (Options.IsEnableCache && (expiresInt > minExpiresInt))
            {
                var cacheResult = await _cache.GetAsync <PresignedUrlCache>(key);

                PresignedUrlCache cache = cacheResult.HasValue ? cacheResult.Value : null;
                //缓存中存在,且有效时间不低于10分钟
                if (cache != null &&
                    cache.Type == type &&
                    cache.CreateTime > 0 &&
                    (cache.CreateTime + expiresInt - nowTime) > minExpiresInt &&
                    cache.Name == objectName &&
                    cache.BucketName == bucketName)
                {
                    return(cache.Url);
                }
            }
            if (type == PresignedObjectType.Get)
            {
                //生成URL
                AccessMode accessMode = await this.GetObjectAclAsync(bucketName, objectName);

                if (accessMode == AccessMode.PublicRead || accessMode == AccessMode.PublicReadWrite)
                {
                    objectUrl = $"{(Options.IsEnableHttps ? "https" : "http")}://{newBucketName}.cos.{Options.Region}.myqcloud.com{(objectName.StartsWith("/") ? "" : "/")}{objectName}";
                }
                else
                {
                    string uri = _client.GenerateSignURL(preSignatureStruct);
                    if (uri != null)
                    {
                        objectUrl = uri.ToString();
                    }
                }
            }
            else
            {
                string uri = _client.GenerateSignURL(preSignatureStruct);
                if (uri != null)
                {
                    objectUrl = uri.ToString();
                }
            }
            if (string.IsNullOrEmpty(objectUrl))
            {
                throw new Exception("Presigned get object url failed.");
            }
            //save cache
            if (Options.IsEnableCache && expiresInt > minExpiresInt)
            {
                PresignedUrlCache urlCache = new PresignedUrlCache()
                {
                    Url        = objectUrl,
                    CreateTime = nowTime,
                    Name       = objectName,
                    BucketName = bucketName,
                    Type       = type
                };
                int randomSec = new Random().Next(5, 30);
                await _cache.SetAsync(key, urlCache, TimeSpan.FromSeconds(expiresInt + randomSec));
            }
            return(objectUrl);
        }