如何覆盖SpringBoot静态资源的默认UTC时间设置
解决Spring Boot静态资源响应头时区问题
问题根源说明
Files.getLastModifiedTime返回的FileTime本质是UTC时间的Instant,和JVM默认时区无关,需要手动转换为指定时区的时间。- 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,重写响应头设置逻辑:
- 创建自定义处理器类
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)); } }
- 在配置类中注册自定义处理器
@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捕获并修改响应头:
- 创建响应包装类
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); } }
- 创建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ć
相关产品推荐
相关产品推荐

