根因(?otcdebug=1 排查时暴露):JsVersion 只取 bin/YLErp*.dll 的 mtime, 纯前端改 wwwroot/Scripts 而不重编 DLL 时,?v= 缓存戳不变,浏览器仍加载旧版 ——即"发布了但不更新"。 修复: - JsVersion = max(DLL mtime, wwwroot/Scripts/** mtime, wwwroot/Statics/bundles/** mtime) - wwwroot 定位用 Directory.GetCurrentDirectory()(与 StaticUrlMiddleware 一致, 开发=项目根/部署=publish 目录),目录不存在则回退仅 DLL mtime - BinFileVersion 保持原义(仅 DLL,用于诊断显示后端构建时间) 附带修复 git 字段:AssemblyInformationalVersion 无 +sha(如固定版本 1.0.0)时, 回退从 .git/HEAD 读取当前 commit 短 sha,不再误显示 '1.0.0' 验证:dotnet build 0 错误
147 lines
6.0 KiB
C#
147 lines
6.0 KiB
C#
using Microsoft.AspNetCore.Html;
|
||
using System.IO;
|
||
using System.Reflection;
|
||
using YLErp.Events;
|
||
|
||
namespace YLErp
|
||
{
|
||
/// <summary>
|
||
/// HTML帮助类
|
||
/// </summary>
|
||
public static class HtmlUtil
|
||
{
|
||
/// <summary>
|
||
/// JS版本(每次启动后更新,不要用于layout中的js版本设置)
|
||
/// </summary>
|
||
public static readonly string JsVersion;
|
||
|
||
//bin目录文件版本
|
||
public static readonly DateTime BinFileVersion;
|
||
|
||
/// <summary>
|
||
/// Git提交哈希(取自程序集 AssemblyInformationalVersion,由.NET SDK在编译时自动生成,
|
||
/// 格式 "1.0.0+<sha>";前端诊断信息用它精确定位是哪次提交的部署)。
|
||
/// </summary>
|
||
public static readonly string GitCommit;
|
||
|
||
static long _dataCacheUpdateTime;
|
||
|
||
static HtmlUtil()
|
||
{
|
||
//获取bin目录中YLErp开头的文件最后修改日期作为后端构建版本
|
||
var binDir = new DirectoryInfo(AppContext.BaseDirectory);
|
||
var files = binDir.GetFiles("YLErp*");
|
||
BinFileVersion = files.Any() ? files.Max(n => n.LastWriteTime) : DateTime.MinValue;
|
||
|
||
//JsVersion(浏览器 ?v= 缓存戳)必须同时反映【后端 DLL】和【前端脚本】的变更:
|
||
//历史上只取 DLL mtime,导致纯前端改动(改 wwwroot/Scripts 而不重编 DLL)时
|
||
//缓存戳不变、浏览器仍加载旧版——即"发布了但不更新"(见 ?otcdebug=1 排查时的症状)。
|
||
//现纳入 wwwroot/Scripts(业务脚本)+ wwwroot/Statics/bundles(打包产物)的 mtime 取 max。
|
||
//wwwroot 定位用 Directory.GetCurrentDirectory()(与 StaticUrlMiddleware 一致,
|
||
//开发时=项目根、部署时=publish 目录),目录不存在则回退到仅 DLL mtime。
|
||
var versionClock = BinFileVersion;
|
||
var webRoot = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
|
||
foreach (var sub in new[] { "Scripts", Path.Combine("Statics", "bundles") })
|
||
{
|
||
var dir = Path.Combine(webRoot, sub);
|
||
if (Directory.Exists(dir))
|
||
{
|
||
foreach (var f in new DirectoryInfo(dir).GetFiles("*", SearchOption.AllDirectories))
|
||
{
|
||
if (f.LastWriteTime > versionClock) versionClock = f.LastWriteTime;
|
||
}
|
||
}
|
||
}
|
||
JsVersion = versionClock.ToString("yyMMddHHmmss");
|
||
|
||
//从 AssemblyInformationalVersion 读取 git sha(SDK 编译时已嵌入,零额外依赖)
|
||
var infoVer = typeof(HtmlUtil).Assembly
|
||
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||
//格式 "1.0.0+<sha>",取 + 之后部分;无 +(如固定版本号 1.0.0)则回退读 .git/HEAD
|
||
if (!string.IsNullOrEmpty(infoVer) && infoVer.Contains('+'))
|
||
{
|
||
GitCommit = infoVer.Substring(infoVer.LastIndexOf('+') + 1);
|
||
}
|
||
else
|
||
{
|
||
GitCommit = ReadGitCommitFromRepo() ?? (infoVer ?? "unknown");
|
||
}
|
||
|
||
EventBus.Subscribe<DataCacheUpdateEvent>(t =>
|
||
{
|
||
_dataCacheUpdateTime = DateTimeOffset.Now.ToUnixTimeSeconds();
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 .git/HEAD 读取当前 commit 短 sha,作为 AssemblyInformationalVersion 无 sha 时的回退。
|
||
/// 找不到 .git 或读取失败返回 null(不影响启动)。
|
||
/// </summary>
|
||
static string ReadGitCommitFromRepo()
|
||
{
|
||
try
|
||
{
|
||
//从 ContentRoot 向上找 .git 目录(开发时在项目根/仓库根)
|
||
var dir = new DirectoryInfo(Directory.GetCurrentDirectory());
|
||
while (dir != null)
|
||
{
|
||
var gitDir = Path.Combine(dir.FullName, ".git");
|
||
if (Directory.Exists(gitDir))
|
||
{
|
||
return ParseGitHead(gitDir);
|
||
}
|
||
dir = dir.Parent;
|
||
}
|
||
}
|
||
catch { /* 读取失败不影响启动,返回 null */ }
|
||
return null;
|
||
}
|
||
|
||
static string ParseGitHead(string gitDir)
|
||
{
|
||
var headFile = Path.Combine(gitDir, "HEAD");
|
||
if (!File.Exists(headFile)) return null;
|
||
var head = File.ReadAllText(headFile).Trim();
|
||
//HEAD 格式:"ref: refs/heads/xxx" 或 detached 时的直接 sha
|
||
if (head.StartsWith("ref: "))
|
||
{
|
||
var refPath = Path.Combine(gitDir, head.Substring(5).Replace('/', Path.DirectorySeparatorChar));
|
||
if (File.Exists(refPath))
|
||
{
|
||
return File.ReadAllText(refPath).Trim().Substring(0, 12);
|
||
}
|
||
}
|
||
else if (head.Length >= 12)
|
||
{
|
||
return head.Substring(0, 12);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取基础数据JS连接
|
||
/// </summary>
|
||
public static IHtmlContent BasicDataJs(params string[] types)
|
||
{
|
||
return new HtmlString(types == null ? "/front/basicData" :
|
||
$"/front/basicData?types={string.Join("&types=", types)}&t={_dataCacheUpdateTime}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取基础数据JS连接(无缓存版本)
|
||
/// </summary>
|
||
public static IHtmlContent BasicDataJsV2(params string[] types)
|
||
{
|
||
return new HtmlString(types == null ? "/front/basicData" :
|
||
$"/front/basicData?types={string.Join("&types=", types)}&t={DateTime.Now.Ticks}");
|
||
}
|
||
|
||
/// <summary>
|
||
///
|
||
/// </summary>
|
||
public static string DateStr(DateTime dt)
|
||
{
|
||
return dt.Year < 1000 ? string.Empty : dt.ToString("yyyy-MM-dd");
|
||
}
|
||
}
|
||
} |