文件
fenleixinxi/application/common/behavior/FcAssets.php

125 行
4.5 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace app\common\behavior;
use think\Config;
/**
* 后台静态资源版本号自动刷新(app_init 行为)
*
* 背景(线上实测复现):
* FastAdmin 后台的浏览器缓存只有「站点版本号」一个开关,它就写在页面里(页面本身不缓存):
* require-backend.min.js?v={$site.version} // 入口 JS
* /ajax/lang?callback=define&controllername=...&lang=zh-cn&v={$site.version}
* // 后台语言包,JS 里的 __('Xxx') 全靠它
* 而语言包接口历史上返回的是 Cache-Control: public + 一个月的 Expires,
* URL 里的版本参数又等于 site.version。于是出现「改了语言包/后台 JS,但版本号没变」时,
* 浏览器会继续使用一个月前缓存的语言包 —— 后台列表标题一直显示英文 key
* (必须手动 Ctrl+F5,或去「常规管理 → 系统配置」改版本号才生效)。
*
* 方案:
* 生产环境下,把 site.version 动态拼上「后台语言包 + 后台模块 JS」的文件指纹,
* 任何后台文案/列定义的改动都会自然生成新的 URL,浏览器自动重新拉取,无需人工干预。
* 指纹只由数字组成,FastAdmin 自带的「清除缓存 → 浏览器缓存」(对版本号末段 +1)依然可用。
*/
class FcAssets
{
public function appInit(&$params)
{
// 本行为作用于每个请求,任何异常都不能影响站点,统一兜底
try {
// 调试模式下 FastAdmin 已把 site.version 置为时间戳(Common::appInit),无需处理
if (Config::get('app_debug')) {
return;
}
$site = Config::get('site');
if (!is_array($site) || empty($site['version'])) {
return;
}
// 只保留 x.y.z 主段,避免反复拼指纹导致版本号越来越长
$base = implode('.', array_slice(explode('.', $site['version']), 0, 3));
$site['version'] = $base . '.' . self::fingerprint();
Config::set('site', $site);
} catch (\Exception $e) {
// 指纹失败时保持原版本号即可(仅影响浏览器缓存刷新时机)
}
}
/**
* 后台静态资源指纹(纯数字串)
* 覆盖范围:后台语言包(文案来源)+ 后台模块 JS(表格列、枚举 formatter 等)+ 后台主题 CSS
* @return string
*/
protected static function fingerprint()
{
static $fingerprint = null;
if ($fingerprint !== null) {
return $fingerprint;
}
$dirs = [
APP_PATH . 'admin' . DS . 'lang' . DS . 'zh-cn',
ROOT_PATH . 'public' . DS . 'assets' . DS . 'js' . DS . 'backend',
ROOT_PATH . 'public' . DS . 'assets' . DS . 'css',
];
// FastAdmin 后台公共脚本(表格操作列、工具栏按钮)不在 backend 目录,需单独纳入指纹
$files = [
ROOT_PATH . 'public' . DS . 'assets' . DS . 'js' . DS . 'require-table.js',
ROOT_PATH . 'public' . DS . 'assets' . DS . 'js' . DS . 'require-backend.min.js',
];
$mtime = 0;
$count = 0;
foreach ($dirs as $dir) {
foreach (self::files($dir) as $file) {
$time = @filemtime($file);
if ($time > $mtime) {
$mtime = $time;
}
$count++;
}
}
foreach ($files as $file) {
if (!is_file($file)) {
continue;
}
$time = @filemtime($file);
if ($time > $mtime) {
$mtime = $time;
}
$count++;
}
// 最新修改时间 + 文件数量作为种子,crc32 转成纯数字(URL 安全,且兼容版本号 +1 逻辑)
$fingerprint = sprintf('%u', crc32($mtime . '-' . $count));
return $fingerprint;
}
/**
* 递归列举目录下的所有文件
* @param string $dir
* @return array
*/
protected static function files($dir)
{
$result = [];
if (!is_dir($dir)) {
return $result;
}
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$result[] = $file->getPathname();
}
}
return $result;
}
}