From b09df29fa0fbc4f13c4c0e0d2e9321b8c5f6eab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=96=AF=E7=8B=82=E7=9A=84=E7=8B=AE=E5=AD=90Li?= <15040126243@163.com> Date: Wed, 1 Jul 2026 09:46:56 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E9=87=8D=E5=86=99=20s3=20=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E7=AD=BE=E5=90=8D=20=E6=94=AF=E6=8C=81=E7=A7=81?= =?UTF-8?q?=E6=9C=89=E5=BA=93=E8=87=AA=E5=AE=9A=E4=B9=89=E5=9F=9F=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/common/core/utils/DateUtils.java | 381 +++--------------- .../oss/client/AbstractOssClientImpl.java | 113 +++++- 2 files changed, 174 insertions(+), 320 deletions(-) diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java index b349de644..186cb6ee6 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java @@ -1,344 +1,96 @@ package org.dromara.common.core.utils; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.BetweenFormatter; import cn.hutool.core.date.DateUnit; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Assert; import lombok.AccessLevel; import lombok.NoArgsConstructor; -import org.apache.commons.lang3.time.DateFormatUtils; -import org.dromara.common.core.enums.FormatsType; import org.dromara.common.core.exception.ServiceException; -import java.lang.management.ManagementFactory; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.time.*; -import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.concurrent.TimeUnit; /** * 时间工具类 * - * @author ruoyi + * @author AprilWind */ @NoArgsConstructor(access = AccessLevel.PRIVATE) -public class DateUtils extends org.apache.commons.lang3.time.DateUtils { - /** - * 日期解析格式集合。 - */ - private static final String[] PARSE_PATTERNS = { - "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", - "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", - "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM" - }; +public class DateUtils extends DateUtil { /** - * 获取当前日期和时间 + * 计算时间差并格式化(精确到秒) * - * @return 当前日期和时间的 Date 对象表示 + * @param start 开始时间(支持 Date/LocalDateTime) + * @param end 结束时间(支持 Date/LocalDateTime) + * @return 时分秒格式时间差 */ - public static Date getNowDate() { - return new Date(); + public static String formatBetweenBySecond(Object start, Object end) { + return formatTimeBetween(start, end, BetweenFormatter.Level.SECOND); } /** - * 获取当前日期的字符串表示,格式为YYYY-MM-DD + * 通用时间差格式化 * - * @return 当前日期的字符串表示 + * @param startDate 开始时间 + * @param endDate 结束时间 + * @param level 精度级别 + * @return 格式化时长 */ - public static String getDate() { - return dateTimeNow(FormatsType.YYYY_MM_DD); + public static String formatTimeBetween(Object startDate, Object endDate, BetweenFormatter.Level level) { + // 非空校验 + Assert.notNull(startDate, "开始时间不能为空"); + Assert.notNull(endDate, "结束时间不能为空"); + Assert.notNull(level, "时间精度级别不能为空"); + + // 统一转为Date并校验格式合法性 + Date start = Convert.toDate(startDate); + Date end = Convert.toDate(endDate); + Assert.notNull(start, "开始时间格式错误,无法解析为时间"); + Assert.notNull(end, "结束时间格式错误,无法解析为时间"); + + long diffMillis = Math.abs(end.getTime() - start.getTime()); + return formatBetween(diffMillis, level); } /** - * 获取当前日期的字符串表示,格式为yyyyMMdd - * - * @return 当前日期的字符串表示 - */ - public static String getCurrentDate() { - return DateFormatUtils.format(new Date(), FormatsType.YYYYMMDD.getTimeFormat()); - } - - /** - * 获取当前日期的路径格式字符串,格式为"yyyy/MM/dd" - * - * @return 当前日期的路径格式字符串 - */ - public static String datePath() { - Date now = new Date(); - return DateFormatUtils.format(now, FormatsType.YYYY_MM_DD_SLASH.getTimeFormat()); - } - - /** - * 获取当前时间的字符串表示,格式为YYYY-MM-DD HH:MM:SS - * - * @return 当前时间的字符串表示 - */ - public static String getTime() { - return dateTimeNow(FormatsType.YYYY_MM_DD_HH_MM_SS); - } - - /** - * 获取当前时间的字符串表示,格式为 "HH:MM:SS" - * - * @return 当前时间的字符串表示,格式为 "HH:MM:SS" - */ - public static String getTimeWithHourMinuteSecond() { - return dateTimeNow(FormatsType.HH_MM_SS); - } - - /** - * 获取当前日期和时间的字符串表示,格式为YYYYMMDDHHMMSS - * - * @return 当前日期和时间的字符串表示 - */ - public static String dateTimeNow() { - return dateTimeNow(FormatsType.YYYYMMDDHHMMSS); - } - - /** - * 获取当前日期和时间的指定格式的字符串表示 - * - * @param format 日期时间格式,例如"YYYY-MM-DD HH:MM:SS" - * @return 当前日期和时间的字符串表示 - */ - public static String dateTimeNow(final FormatsType format) { - return parseDateToStr(format, new Date()); - } - - /** - * 将指定日期格式化为 YYYY-MM-DD 格式的字符串 - * - * @param date 要格式化的日期对象 - * @return 格式化后的日期字符串 - */ - public static String formatDate(final Date date) { - return parseDateToStr(FormatsType.YYYY_MM_DD, date); - } - - /** - * 将指定日期格式化为 YYYY-MM-DD HH:MM:SS 格式的字符串 - * - * @param date 要格式化的日期对象 - * @return 格式化后的日期时间字符串 - */ - public static String formatDateTime(final Date date) { - return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, date); - } - - /** - * 将 LocalDateTime 格式化为 YYYY-MM-DD HH:MM:SS 格式的字符串 - * - * @param dateTime 要格式化的 LocalDateTime 对象 - * @return 格式化后的日期时间字符串 - */ - public static String formatDateTime(final LocalDateTime dateTime) { - return dateTime.format(DateTimeFormatter.ofPattern(FormatsType.YYYY_MM_DD_HH_MM_SS.getTimeFormat())); - } - - /** - * 将指定日期按照指定格式进行格式化 - * - * @param format 要使用的日期时间格式,例如"YYYY-MM-DD HH:MM:SS" - * @param date 要格式化的日期对象 - * @return 格式化后的日期时间字符串 - */ - public static String parseDateToStr(final FormatsType format, final Date date) { - return new SimpleDateFormat(format.getTimeFormat()).format(date); - } - - /** - * 将指定格式的日期时间字符串转换为 Date 对象 - * - * @param format 要解析的日期时间格式,例如"YYYY-MM-DD HH:MM:SS" - * @param ts 要解析的日期时间字符串 - * @return 解析后的 Date 对象 - * @throws RuntimeException 如果解析过程中发生异常 - */ - public static Date parseDateTime(final FormatsType format, final String ts) { - try { - return new SimpleDateFormat(format.getTimeFormat()).parse(ts); - } catch (ParseException e) { - throw new ServiceException("日期时间解析失败:" + ts, e); - } - } - - /** - * 将对象转换为日期对象 - * - * @param str 要转换的对象,通常是字符串 - * @return 转换后的日期对象,如果转换失败或输入为null,则返回null - */ - public static Date parseDate(Object str) { - if (str == null) { - return null; - } - try { - return parseDate(str.toString(), PARSE_PATTERNS); - } catch (ParseException e) { - return null; - } - } - - /** - * 获取服务器启动时间 - * - * @return 服务器启动时间的 Date 对象表示 - */ - public static Date getServerStartDate() { - long time = ManagementFactory.getRuntimeMXBean().getStartTime(); - return new Date(time); - } - - /** - * 计算两个时间之间的时间差,并以指定单位返回(绝对值) - * - * @param start 起始时间 - * @param end 结束时间 - * @param unit 所需返回的时间单位(DAYS、HOURS、MINUTES、SECONDS、MILLISECONDS、MICROSECONDS、NANOSECONDS) - * @return 时间差的绝对值,以指定单位表示 - */ - public static long difference(Date start, Date end, TimeUnit unit) { - // 计算时间差,单位为毫秒,取绝对值避免负数 - long diffInMillis = Math.abs(end.getTime() - start.getTime()); - - // 根据目标单位转换时间差 - return switch (unit) { - case DAYS -> diffInMillis / TimeUnit.DAYS.toMillis(1); - case HOURS -> diffInMillis / TimeUnit.HOURS.toMillis(1); - case MINUTES -> diffInMillis / TimeUnit.MINUTES.toMillis(1); - case SECONDS -> diffInMillis / TimeUnit.SECONDS.toMillis(1); - case MILLISECONDS -> diffInMillis; - case MICROSECONDS -> TimeUnit.MILLISECONDS.toMicros(diffInMillis); - case NANOSECONDS -> TimeUnit.MILLISECONDS.toNanos(diffInMillis); - }; - } - - /** - * 计算两个日期之间的时间差,并以天、小时和分钟的格式返回 - * - * @param endDate 结束日期 - * @param nowDate 当前日期 - * @return 表示时间差的字符串,格式为"天 小时 分钟" - */ - public static String getDatePoor(Date endDate, Date nowDate) { - long diffInMillis = endDate.getTime() - nowDate.getTime(); - long day = TimeUnit.MILLISECONDS.toDays(diffInMillis); - long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24; - long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60; - return String.format("%d天 %d小时 %d分钟", day, hour, min); - } - - /** - * 计算两个时间点的差值(天、小时、分钟、秒),当值为0时不显示该单位 - * - * @param endDate 结束时间 - * @param nowDate 当前时间 - * @return 时间差字符串,格式为 "x天 x小时 x分钟 x秒",若为 0 则不显示 - */ - public static String getTimeDifference(Date endDate, Date nowDate) { - long diffInMillis = endDate.getTime() - nowDate.getTime(); - long day = TimeUnit.MILLISECONDS.toDays(diffInMillis); - long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24; - long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60; - long sec = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60; - // 构建时间差字符串,条件是值不为0才显示 - StringBuilder result = new StringBuilder(); - if (day > 0) { - result.append(String.format("%d天 ", day)); - } - if (hour > 0) { - result.append(String.format("%d小时 ", hour)); - } - if (min > 0) { - result.append(String.format("%d分钟 ", min)); - } - if (sec > 0) { - result.append(String.format("%d秒", sec)); - } - return !result.isEmpty() ? result.toString().trim() : "0秒"; - } - - /** - * 计算两个 LocalDateTime 时间点的差值(天、小时、分钟、秒),当值为0时不显示该单位 - * - * @param endDate 结束时间 - * @param nowDate 当前时间 - * @return 时间差字符串,格式为 "x天 x小时 x分钟 x秒",若为 0 则不显示 - */ - public static String getTimeDifference(LocalDateTime endDate, LocalDateTime nowDate) { - long diffInMillis = java.time.Duration.between(nowDate, endDate).toMillis(); - long day = TimeUnit.MILLISECONDS.toDays(diffInMillis); - long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24; - long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60; - long sec = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60; - StringBuilder result = new StringBuilder(); - if (day > 0) { - result.append(String.format("%d天 ", day)); - } - if (hour > 0) { - result.append(String.format("%d小时 ", hour)); - } - if (min > 0) { - result.append(String.format("%d分钟 ", min)); - } - if (sec > 0) { - result.append(String.format("%d秒", sec)); - } - return !result.isEmpty() ? result.toString().trim() : "0秒"; - } - - /** - * 将 LocalDateTime 对象转换为 Date 对象 - * - * @param temporalAccessor 要转换的 LocalDateTime 对象 - * @return 转换后的 Date 对象 - */ - public static Date toDate(LocalDateTime temporalAccessor) { - ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); - return Date.from(zdt.toInstant()); - } - - /** - * 将 LocalDate 对象转换为 Date 对象 - * - * @param temporalAccessor 要转换的 LocalDate 对象 - * @return 转换后的 Date 对象 - */ - public static Date toDate(LocalDate temporalAccessor) { - LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); - ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); - return Date.from(zdt.toInstant()); - } - - /** - * 校验日期范围 + * 校验日期范围及最大时间跨度 * * @param startDate 开始日期 * @param endDate 结束日期 - * @param maxValue 最大时间跨度的限制值 - * @param unit 时间跨度的单位,可选择 "DAYS"、"HOURS" 或 "MINUTES" + * @param maxValue 最大时间跨度限制值 + * @param unit 时间单位 */ - public static void validateDateRange(Date startDate, Date endDate, int maxValue, TimeUnit unit) { - // 校验结束日期不能早于开始日期 - if (endDate.before(startDate)) { + public static void validateDateRange(Object startDate, Object endDate, int maxValue, TimeUnit unit) { + // 基础非空校验 + Assert.notNull(startDate, "开始日期不能为空"); + Assert.notNull(endDate, "结束日期不能为空"); + Assert.notNull(unit, "时间单位不能为空"); + + // 统一转换并校验时间合法性 + Date start = Convert.toDate(startDate); + Date end = Convert.toDate(endDate); + Assert.notNull(start, "开始日期格式错误,无法解析为时间"); + Assert.notNull(end, "结束日期格式错误,无法解析为时间"); + + // 校验结束时间不能早于开始时间 + if (end.before(start)) { throw new ServiceException("结束日期不能早于开始日期"); } - // 计算时间跨度 - long diffInMillis = endDate.getTime() - startDate.getTime(); - - // 根据单位转换时间跨度 + long diffMillis = end.getTime() - start.getTime(); long diff = switch (unit) { - case DAYS -> TimeUnit.MILLISECONDS.toDays(diffInMillis); - case HOURS -> TimeUnit.MILLISECONDS.toHours(diffInMillis); - case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffInMillis); - default -> throw new IllegalArgumentException("不支持的时间单位"); + case DAYS -> TimeUnit.MILLISECONDS.toDays(diffMillis); + case HOURS -> TimeUnit.MILLISECONDS.toHours(diffMillis); + case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffMillis); + default -> throw new IllegalArgumentException("不支持的时间单位:" + unit.name()); }; - // 校验时间跨度不超过最大限制 if (diff > maxValue) { - throw new ServiceException("最大时间跨度为 {} {}", maxValue, unit.toString().toLowerCase()); + String msg = String.format("最大时间跨度为 %d %s", maxValue, unit.name().toLowerCase()); + throw new ServiceException(msg); } } @@ -349,7 +101,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { * @return 时间段描述 */ public static String getTodayHour(Date date) { - int hour = DateUtil.hour(date, true); + int hour = hour(date, true); if (hour <= 6) { return "凌晨"; } else if (hour < 12) { @@ -384,38 +136,37 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { if (date == null) { return ""; } - Date now = DateUtil.date(); + Date now = new Date(); // 未来时间或非今年 - if (date.after(now) || DateUtil.year(date) != DateUtil.year(now)) { - return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM, date); + if (date.after(now) || year(date) != year(now)) { + return formatDateTime(now); } // 今天 - if (DateUtil.isSameDay(date, now)) { - long minutes = DateUtil.between(date, now, DateUnit.MINUTE); + if (isSameDay(date, now)) { + long minutes = between(date, now, DateUnit.MINUTE); if (minutes < 1) { return "刚刚"; } if (minutes < 60) { return minutes + "分钟前"; } - return getTodayHour(date) + " " + DateUtil.format(date, "HH:mm"); + return getTodayHour(date) + " " + format(date, "HH:mm"); } // 昨天 - if (DateUtil.isSameDay(date, DateUtil.yesterday())) { - return "昨天 " + DateUtil.format(date, "HH:mm"); + if (isSameDay(date, yesterday())) { + return "昨天 " + format(date, "HH:mm"); } // 本周 - if (DateUtil.isSameWeek(date, now, true)) { - return DateUtil.dayOfWeekEnum(date).toChinese("周") - + " " + DateUtil.format(date, "HH:mm"); + if (isSameWeek(date, now, true)) { + return dayOfWeekEnum(date).toChinese("周") + " " + format(date, "HH:mm"); } // 今年内其它时间 - return DateUtil.format(date, "MM-dd HH:mm"); + return format(date, "MM-dd HH:mm"); } } diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/client/AbstractOssClientImpl.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/client/AbstractOssClientImpl.java index 0fc385069..0197fca0c 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/client/AbstractOssClientImpl.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/client/AbstractOssClientImpl.java @@ -15,6 +15,14 @@ import software.amazon.awssdk.core.ResponseInputStream; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.core.async.ResponsePublisher; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner; +import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; +import software.amazon.awssdk.http.auth.spi.signer.SignRequest; +import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectResponse; @@ -25,20 +33,21 @@ import software.amazon.awssdk.transfer.s3.S3TransferManager; import software.amazon.awssdk.transfer.s3.model.CompletedUpload; import software.amazon.awssdk.transfer.s3.model.DownloadRequest; import software.amazon.awssdk.transfer.s3.progress.TransferListener; +import software.amazon.awssdk.utils.http.SdkHttpUtils; import java.io.*; +import java.net.URI; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.channels.SeekableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Clock; import java.time.Duration; +import java.time.Instant; import java.time.ZoneOffset; -import java.util.Collection; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -203,7 +212,7 @@ public abstract class AbstractOssClientImpl implements OssClient { .orElse(""); String mergedPrefix = mergePrefix(defaultPrefix, businessPrefix); String suffix = suffix(fileName); - String datePath = DateUtils.datePath(); + String datePath = DateUtils.format(new Date(), "yyyy/MM/dd"); String uuid = IdUtil.fastSimpleUUID(); String path = mergedPrefix.isEmpty() ? datePath + StringUtils.SLASH + uuid : mergedPrefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid; return path + suffix; @@ -719,6 +728,9 @@ public abstract class AbstractOssClientImpl implements OssClient { */ @Override public String bucketPresignGetUrl(String bucket, String key, Duration expiredTime) { + if (useBucketBoundDomain(bucket)) { + return bucketBoundDomainPresignUrl(SdkHttpMethod.GET, key, expiredTime, Collections.emptyMap()); + } try { return s3Presigner.presignGetObject(getObjectPresignRequestBuilder -> { getObjectPresignRequestBuilder.signatureDuration(expiredTime) @@ -742,6 +754,9 @@ public abstract class AbstractOssClientImpl implements OssClient { */ @Override public String bucketPresignPutUrl(String bucket, String key, Duration expiredTime, Map metadata) { + if (useBucketBoundDomain(bucket)) { + return bucketBoundDomainPresignUrl(SdkHttpMethod.PUT, key, expiredTime, metadata); + } try { return s3Presigner.presignPutObject(putObjectPresignRequestBuilder -> { putObjectPresignRequestBuilder.signatureDuration(expiredTime) @@ -754,6 +769,94 @@ public abstract class AbstractOssClientImpl implements OssClient { } } + /** + * 是否使用已绑定默认桶的自定义域名生成无桶名预签名 URL。 + * + * @param bucket 存储桶名称 + * @return 是否使用自定义域名签名 + */ + private boolean useBucketBoundDomain(String bucket) { + return config.domain() + .filter(StringUtils::isNotBlank) + .isPresent() && config.bucket() + .filter(defaultBucket -> Objects.equals(defaultBucket, bucket)) + .isPresent(); + } + + /** + * 使用绑定默认桶的自定义域名生成预签名 URL,避免 S3 SDK 自动拼接桶名。 + * + * @param method HTTP 方法 + * @param key 对象键 + * @param expiredTime 过期时间 + * @param metadata 对象元数据 + * @return 预签名 URL + */ + private String bucketBoundDomainPresignUrl(SdkHttpMethod method, String key, Duration expiredTime, Map metadata) { + try { + URI domainUri = URI.create(config.getDomainUrl()); + SdkHttpFullRequest.Builder requestBuilder = SdkHttpFullRequest.builder() + .method(method) + .uri(domainUri) + .encodedPath(bucketBoundDomainPath(domainUri, key)); + if (metadata != null) { + metadata.forEach((metadataKey, metadataValue) -> { + if (StringUtils.isNotBlank(metadataKey)) { + requestBuilder.putHeader("x-amz-meta-" + metadataKey, String.valueOf(metadataValue)); + } + }); + } + AwsCredentialsIdentity credentials = AwsCredentialsIdentity.create( + config.accessKey() + .filter(StringUtils::isNotBlank) + .orElseThrow(() -> S3StorageException.form("accessKey is not configured.")), + config.secretKey() + .filter(StringUtils::isNotBlank) + .orElseThrow(() -> S3StorageException.form("secretKey is not configured.")) + ); + Clock signingClock = Clock.fixed(Instant.now(), ZoneOffset.UTC); + return AwsV4HttpSigner.create() + .sign(SignRequest.builder(credentials) + .request(requestBuilder.build()) + .putProperty(AwsV4HttpSigner.REGION_NAME, config.region().orElse(Region.US_EAST_1).id()) + .putProperty(AwsV4FamilyHttpSigner.SERVICE_SIGNING_NAME, "s3") + .putProperty(AwsV4FamilyHttpSigner.AUTH_LOCATION, AwsV4FamilyHttpSigner.AuthLocation.QUERY_STRING) + .putProperty(AwsV4FamilyHttpSigner.PAYLOAD_SIGNING_ENABLED, false) + .putProperty(AwsV4FamilyHttpSigner.EXPIRATION_DURATION, expiredTime) + .putProperty(HttpSigner.SIGNING_CLOCK, signingClock) + .putProperty(AwsV4FamilyHttpSigner.DOUBLE_URL_ENCODE, false) + .putProperty(AwsV4FamilyHttpSigner.NORMALIZE_PATH, false) + .build()) + .request() + .getUri() + .toString(); + } catch (Exception e) { + throw toStorageException(e); + } + } + + /** + * 构建绑定桶域名下的对象访问路径。 + * + * @param domainUri 自定义域名 URI + * @param key 对象键 + * @return 编码后的访问路径 + */ + private String bucketBoundDomainPath(URI domainUri, String key) { + String basePath = Optional.ofNullable(domainUri.getRawPath()) + .filter(StringUtils::isNotBlank) + .filter(path -> !"/".equals(path)) + .orElse(""); + String objectPath = SdkHttpUtils.urlEncodeIgnoreSlashes(key); + if (!basePath.startsWith("/")) { + basePath = "/" + basePath; + } + if (!basePath.endsWith("/")) { + basePath = basePath + "/"; + } + return basePath + objectPath; + } + /** * 上传本地路径文件到默认存储桶。 *