Files
zszq-trs/YLErpWeb/Common/HtmlUtil.cs
T
hjhan 7a9702c707 fix(diag): JsVersion 纳入前端文件 mtime,修复纯前端改动缓存不刷新
根因(?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 错误
2026-07-30 11:25:11 +08:00

147 lines
6.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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+&lt;sha&gt;";前端诊断信息用它精确定位是哪次提交的部署)。
/// </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 shaSDK 编译时已嵌入,零额外依赖)
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");
}
}
}