using System.Collections.Concurrent;
using System.Text.Json;
using Microsoft.Extensions.Options;
namespace Dpz.Core.Auth.Service;
/// <summary>
/// 管理 Auth 站点的前端资源清单,提供 CSS / JS 文件的带 host 的完整 URL
/// </summary>
/// <remarks>
/// 非开发环境下首次加载后缓存至进程退出;开发环境下监听文件修改时间并按需重载
/// </remarks>
public class AuthAssetService(
IWebHostEnvironment environment,
IOptions<AuthAssetOptions> options,
ILogger<AuthAssetService> logger,
IConfiguration configuration
)
{
private static readonly ConcurrentDictionary<string, string[]> Manifest = new(
StringComparer.OrdinalIgnoreCase
);
private readonly AuthAssetOptions _options = options.Value;
private static DateTime _lastManifestWriteTimeUtc;
/// <summary>
/// 获取指定入口的所有样式文件 URL
/// </summary>
/// <param name="entryName">入口名称(如 "app")</param>
public IReadOnlyList<string> GetStyleUrls(string entryName)
{
return GetEntryFiles(entryName, ".css");
}
/// <summary>
/// 获取指定入口的所有脚本文件 URL
/// </summary>
/// <param name="entryName">入口名称(如 "app")</param>
public IReadOnlyList<string> GetScriptUrls(string entryName)
{
return GetEntryFiles(entryName, ".js");
}
/// <summary>
/// 根据入口名和扩展名,从已加载的资源清单中查找对应文件并拼接完整 URL
/// </summary>
private List<string> GetEntryFiles(string entryName, string extension)
{
LoadManifest();
var key = $"entries/{entryName}{extension}";
if (!Manifest.TryGetValue(key, out var paths))
{
logger.LogWarning("Auth 资源清单缺少入口:{EntryName}", key);
return [];
}
var host = _options.AssetsHost?.TrimEnd('/') ?? "";
return paths.Select(path => $"{host}/{path.TrimStart('/')}").ToList();
}
/// <summary>
/// 从磁盘加载 <c>auth-assets-manifest.json</c> 至内存
/// 非开发环境仅加载一次;开发环境文件变更时自动重载
/// </summary>
private void LoadManifest()
{
var manifestPath = Path.Combine(environment.WebRootPath, "auth-assets-manifest.json");
if (!File.Exists(manifestPath))
{
logger.LogWarning("Auth 资源清单不存在:{ManifestPath}", manifestPath);
return;
}
var writeTimeUtc = File.GetLastWriteTimeUtc(manifestPath);
if (!configuration.IsDevelopment && !Manifest.IsEmpty)
{
return;
}
if (
configuration.IsDevelopment
&& !Manifest.IsEmpty
&& writeTimeUtc == _lastManifestWriteTimeUtc
)
{
return;
}
try
{
var manifest =
JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
File.ReadAllText(manifestPath)
) ?? [];
Manifest.Clear();
foreach (var (key, value) in manifest)
{
if (value.ValueKind == JsonValueKind.Array)
{
Manifest.TryAdd(
key,
value
.EnumerateArray()
.Select(x => x.GetString())
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x!)
.ToArray()
);
continue;
}
var path = value.GetString();
if (!string.IsNullOrWhiteSpace(path))
{
Manifest.TryAdd(key, [path]);
}
}
_lastManifestWriteTimeUtc = writeTimeUtc;
}
catch (Exception e)
{
logger.LogError(e, "加载 Auth 资源清单失败");
}
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个用于管理认证(Auth)站点前端资源清单的服务类,主要功能是读取并提供 CSS 和 JS 文件的完整 URL。
核心功能
1. 类的职责
- 从
auth-assets-manifest.json文件中加载前端资源清单 - 提供获取特定入口的 CSS 和 JS 文件 URL 的接口
- 根据环境(开发/生产)实现不同的缓存策略
2. 依赖注入
构造函数注入了四个依赖:
IWebHostEnvironment: 获取 Web 根目录路径IOptions<AuthAssetOptions>: 配置选项(包含资源主机地址)ILogger: 日志记录IConfiguration: 判断当前环境
3. 核心字段
ConcurrentDictionary<string, string[]> Manifest // 线程安全的资源清单缓存
DateTime _lastManifestWriteTimeUtc // 文件最后修改时间(用于热重载)
主要方法
GetStyleUrls / GetScriptUrls
- 获取指定入口(如 "app")的所有 CSS 或 JS 文件 URL
- 内部调用
GetEntryFiles方法
GetEntryFiles (核心方法)
- 调用
LoadManifest()确保清单已加载 - 根据入口名和扩展名构建键(如
entries/app.css) - 从缓存字典中查找对应的文件路径数组
- 拼接完整 URL(
AssetsHost+ 文件路径)
LoadManifest (加载逻辑)
实现了智能缓存策略:
生产环境:
if (!configuration.IsDevelopment && !Manifest.IsEmpty)
return; // 只加载一次,进程级缓存
开发环境:
if (writeTimeUtc == _lastManifestWriteTimeUtc)
return; // 文件未变化则不重新加载
加载流程:
- 检查
auth-assets-manifest.json是否存在 - 读取文件修改时间
- 反序列化 JSON 内容
- 处理两种数据格式:
- 数组格式:
"entry": ["file1.js", "file2.js"] - 字符串格式:
"entry": "file.js"(转换为单元素数组)
- 数组格式:
- 更新静态缓存字典
设计特点
- 线程安全:使用
ConcurrentDictionary处理并发访问 - 性能优化:
- 生产环境一次性加载,避免重复 I/O
- 开发环境按需热重载,支持实时更新
- 健壮性:
- 文件不存在时记录警告但不抛异常
- JSON 解析失败时捕获异常并记录日志
- 灵活性:支持通过
AuthAssetOptions.AssetsHost配置 CDN 地址
典型使用场景
// 在 Razor 视图或控制器中注入
public MyController(AuthAssetService assetService)
{
var cssFiles = assetService.GetStyleUrls("app");
var jsFiles = assetService.GetScriptUrls("app");
// 生成 <link> 和 <script> 标签
}
这种设计特别适合前后端分离或使用 Vite/Webpack 等构建工具生成带 hash 文件名的场景。
AI 正在分析代码…
评论加载中...