using Microsoft.AspNetCore.Html; using System.IO; using System.Reflection; using YLErp.Events; namespace YLErp { /// /// HTML帮助类 /// public static class HtmlUtil { /// /// JS版本(每次启动后更新,不要用于layout中的js版本设置) /// public static readonly string JsVersion; //bin目录文件版本 public static readonly DateTime BinFileVersion; /// /// Git提交哈希(取自程序集 AssemblyInformationalVersion,由.NET SDK在编译时自动生成, /// 格式 "1.0.0+<sha>";前端诊断信息用它精确定位是哪次提交的部署)。 /// 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()?.InformationalVersion; //格式 "1.0.0+",取 + 之后部分;无 +(如固定版本号 1.0.0)则回退读 .git/HEAD if (!string.IsNullOrEmpty(infoVer) && infoVer.Contains('+')) { GitCommit = infoVer.Substring(infoVer.LastIndexOf('+') + 1); } else { GitCommit = ReadGitCommitFromRepo() ?? (infoVer ?? "unknown"); } EventBus.Subscribe(t => { _dataCacheUpdateTime = DateTimeOffset.Now.ToUnixTimeSeconds(); }); } /// /// 从 .git/HEAD 读取当前 commit 短 sha,作为 AssemblyInformationalVersion 无 sha 时的回退。 /// 找不到 .git 或读取失败返回 null(不影响启动)。 /// 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; } /// /// 获取基础数据JS连接 /// public static IHtmlContent BasicDataJs(params string[] types) { return new HtmlString(types == null ? "/front/basicData" : $"/front/basicData?types={string.Join("&types=", types)}&t={_dataCacheUpdateTime}"); } /// /// 获取基础数据JS连接(无缓存版本) /// 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}"); } /// /// /// public static string DateStr(DateTime dt) { return dt.Year < 1000 ? string.Empty : dt.ToString("yyyy-MM-dd"); } } }