You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何覆盖SpringBoot静态资源的默认UTC时间设置

解决Spring Boot静态资源响应头时区问题

问题根源说明

  1. Files.getLastModifiedTime返回的FileTime本质是UTC时间的Instant,和JVM默认时区无关,需要手动转换为指定时区的时间。
  2. Spring默认的静态资源处理器ResourceHttpRequestHandler会用UTC时区生成Date和Last-Modified响应头,不受JVM默认时区设置影响。

单独转换文件最后修改时间(代码层面)

如果只是需要在业务代码中获取Europe/Zagreb时区的文件修改时间,可以直接转换:

FileTime fileTime = Files.getLastModifiedTime(Paths.get(lastProcessedPath));
ZonedDateTime zagrebLastModified = fileTime.toInstant().atZone(ZoneId.of("Europe/Zagreb"));
System.out.println(zagrebLastModified);

修改静态资源响应头时区的两种方案

方案一:自定义静态资源处理器

继承Spring默认的ResourceHttpRequestHandler,重写响应头设置逻辑:

  1. 创建自定义处理器类
public class TimezoneAwareResourceHttpRequestHandler extends ResourceHttpRequestHandler {

    private static final ZoneId ZAGREB_ZONE = ZoneId.of("Europe/Zagreb");
    private static final DateTimeFormatter RFC_1123_FORMATTER = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZAGREB_ZONE);

    @Override
    protected void setHeaders(HttpServletResponse response, Resource resource, MediaType mediaType) throws IOException {
        // 先执行默认逻辑
        super.setHeaders(response, resource, mediaType);
        
        // 替换Last-Modified头为Zagreb时区
        if (resource.lastModified() > 0) {
            ZonedDateTime lastModifiedZoned = Instant.ofEpochMilli(resource.lastModified()).atZone(ZAGREB_ZONE);
            response.setHeader(HttpHeaders.LAST_MODIFIED, RFC_1123_FORMATTER.format(lastModifiedZoned));
        }
        
        // 替换Date头为Zagreb时区
        ZonedDateTime currentDateZoned = ZonedDateTime.now(ZAGREB_ZONE);
        response.setHeader(HttpHeaders.DATE, RFC_1123_FORMATTER.format(currentDateZoned));
    }
}
  1. 在配置类中注册自定义处理器
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(final ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/data/**")
                .addResourceLocations("file:data/")
                .resourceChain(true)
                .addResolver(new PathResourceResolver())
                .addHandler(new TimezoneAwareResourceHttpRequestHandler());
    }
}

方案二:使用Filter+响应包装器

通过包装HttpServletResponse捕获并修改响应头:

  1. 创建响应包装类
public class TimezoneResponseWrapper extends HttpServletResponseWrapper {

    private static final ZoneId ZAGREB_ZONE = ZoneId.of("Europe/Zagreb");
    private static final DateTimeFormatter RFC_1123_UTC_FORMATTER = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC);
    private static final DateTimeFormatter RFC_1123_ZAGREB_FORMATTER = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZAGREB_ZONE);

    public TimezoneResponseWrapper(HttpServletResponse response) {
        super(response);
    }

    @Override
    public void setHeader(String name, String value) {
        // 处理Last-Modified头
        if (HttpHeaders.LAST_MODIFIED.equals(name)) {
            try {
                ZonedDateTime utcTime = ZonedDateTime.parse(value, RFC_1123_UTC_FORMATTER);
                ZonedDateTime zagrebTime = utcTime.withZoneSameInstant(ZAGREB_ZONE);
                super.setHeader(name, RFC_1123_ZAGREB_FORMATTER.format(zagrebTime));
                return;
            } catch (DateTimeParseException ignored) {
                // 解析失败则保留原头
            }
        }
        // 处理Date头
        else if (HttpHeaders.DATE.equals(name)) {
            try {
                ZonedDateTime utcTime = ZonedDateTime.parse(value, RFC_1123_UTC_FORMATTER);
                ZonedDateTime zagrebTime = utcTime.withZoneSameInstant(ZAGREB_ZONE);
                super.setHeader(name, RFC_1123_ZAGREB_FORMATTER.format(zagrebTime));
                return;
            } catch (DateTimeParseException ignored) {
                // 解析失败则保留原头
            }
        }
        super.setHeader(name, value);
    }
}
  1. 创建Filter并注册
@Component
public class TimezoneHeaderFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse wrappedResponse = new TimezoneResponseWrapper((HttpServletResponse) response);
        chain.doFilter(request, wrappedResponse);
    }
}

注意事项

HTTP协议规范中,Date和Last-Modified头的标准格式要求使用GMT时区(本质是UTC)。如果只是客户端显示问题,大部分浏览器会自动将GMT时间转换为本地时区,无需修改响应头。只有业务明确要求返回当地时区格式的头时,再使用上述方案。

内容的提问来源于stack exchange,提问作者Luka Jaković

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 02:57:11