一、背景
修改 JS 或 CSS 文件,发布到服务器后,访问经常会失效,需要清除一下浏览器缓存,才可以正常访问。
二、问题原因
浏览器会默认缓存 JS 和 CSS 文件,如果文件名未发生改变,则会直接调用缓存的文件,所以就会出现更新了 JS 或 CSS 文件后,访问不会生效。
三、解决方案
1、PHP
(1)版本号/时间戳防缓存(推荐)
在引用 CSS、JS 时给资源加上一个变化的参数,让浏览器认为是新的文件。
<!-- 用文件修改时间做版本号 -->
<link rel="stylesheet" href="/assets/css/style.css?v=<?php echo filemtime('assets/css/style.css'); ?>">
<script src="/assets/js/app.js?v=<?php echo filemtime('assets/js/app.js'); ?>"></script>原理:文件更新后,filemtime() 会返回最新的修改时间,相当于 URL 不一样,浏览器就会去重新加载。
(2)发布时手动更换版本号
如果不想每次都读取 filemtime,可以写死一个版本号,每次上线时手动改:
<link rel="stylesheet" href="/assets/css/style.css?v=20251103">
<script src="/assets/js/app.js?v=20251103"></script>(3)服务端设置 HTTP 头控制缓存
如果是后台管理系统,不需要长时间缓存,可以在 PHP 输出头部禁用或缩短缓存时间,例如:
<?php
header("Cache-Control: no-cache, must-revalidate"); // HTTP 1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // 让它过期
?>或者在 Nginx/Apache 配置中针对 .css .js 资源设置较短的 max-age。
2、JAVA
(1)使用版本号/时间戳(在模板里实现)
如果你用的是 Thymeleaf 或 FreeMarker 之类的模板引擎,可以在引用静态资源时加版本标记: Thymeleaf 示例:
<link th:href="@{/css/style.css(v=${T(java.lang.System).currentTimeMillis()})}">
<script th:src="@{/js/app.js(v=${T(java.lang.System).currentTimeMillis()})}"></script>这样每次页面渲染,都会带上一个当前时间参数,浏览器自然不会用旧缓存。
(2)用文件实际修改时间做版本号(更精准)
可以写一个工具方法,在模板里调用文件的修改时间: 工具类:
import java.nio.file.*;
import java.io.IOException;
public class VersionUtil {
public static long fileVersion(String path) {
try {
return Files.getLastModifiedTime(Paths.get(path)).toMillis();
} catch (IOException e) {
return System.currentTimeMillis();
}
}
}Thymeleaf 调用:
<link th:href="@{/css/style.css(v=${T(com.example.util.VersionUtil).fileVersion('src/main/resources/static/css/style.css')})}">这样只有文件真正修改才会更新版本号。
(3)使用 Spring Boot 资源版本化(推荐生产环境用)
Spring MVC 提供了 ResourceUrlEncodingFilter,可以根据构建过程自动给静态文件加 hash 值,这样文件名都不同,缓存自然失效。 配置例子:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.VersionResourceResolver;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/")
.resourceChain(true)
.addResolver(new VersionResourceResolver()
.addContentVersionStrategy("/**"));
}
}这样 /css/style.css 在生成时会变成 /css/style.css?v=文件内容hash,只要文件改变 hash 就会变。
(4)构建工具解决(推荐前端项目用)
如果前端用 Webpack / Vite 之类的构建工具,可以在打包时直接让生成的文件带 hash:
style.3a8b7.css
app.81f2c.js然后 Thymeleaf 或 HTML 模板直接引用打包后的文件名。这样缓存问题完全解决。
3、JS
由于 JS 无法直接获取本地文件,所以没法根据文件修改日期自动添加版本号,因此我们采用 JS 追加版本号的方式。
(1)新建 config.js 文件
文件内容如下:
window.APP_VERSION = "v1.0.0";
(function () {
const ver = window.APP_VERSION || '1.0.0';
console.log(`应用版本号: ${ver}`);
// 处理 CSS 链接
document.querySelectorAll('link[rel="stylesheet"]').forEach(link => {
const href = link.getAttribute('href');
// 跳过外部CDN(避免污染)
if (!href.startsWith('http')) {
link.setAttribute('href', `${href}?v=${ver}`);
}
});
// 处理 JS 脚本
document.querySelectorAll('script[src]').forEach(script => {
const src = script.getAttribute('src');
if (!src.startsWith('http') && !src.includes('APP_VERSION_LOADER')) {
// 防止给自己加版本号导致重复加载
script.setAttribute('src', `${src}?v=${ver}`);
}
});
})();(2)将 config.js 引用在 html 页面最后。
<!--配置-->
<script type="text/javascript" src="../static/custom/js/config.js"></script>运行网页后,即可自动给所有 css 和 js 文件引用增加版本号。