GFramework/GFramework.Game/Config/YamlConfigTextValidator.cs
GeWuYou 12e54ce637 feat(config): 添加YAML配置序列化和校验功能
- 实现YamlConfigTextSerializer提供YAML文本序列化功能
- 实现YamlConfigTextValidator提供YAML文本校验功能
- 添加缓存机制优化schema文件加载性能
- 实现同步和异步校验接口支持
- 添加集成测试验证生成配置绑定功能
- 扩展SchemaConfigGenerator支持配置类型生成
- 实现GeneratedConfigConsumerIntegrationTests完整测试覆盖
2026-04-12 15:41:45 +08:00

182 lines
8.3 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.

namespace GFramework.Game.Config;
/// <summary>
/// 提供面向宿主的 YAML 文本校验入口,使保存前校验可以复用运行时同一套 schema 规则。
/// </summary>
public static class YamlConfigTextValidator
{
// Cache parsed schemas by table/path plus last write time so save-path validation can
// avoid repeated disk IO and JSON parsing while still observing schema edits.
private static readonly ConcurrentDictionary<SchemaCacheKey, SchemaCacheEntry> SchemaCache = new();
/// <summary>
/// 使用指定 schema 文件同步校验 YAML 文本。
/// </summary>
/// <param name="tableName">所属配置表名称。</param>
/// <param name="schemaPath">Schema 文件绝对路径。</param>
/// <param name="yamlPath">YAML 文件路径,仅用于诊断信息。</param>
/// <param name="yamlText">待校验的 YAML 文本。</param>
/// <exception cref="ArgumentException">当 <paramref name="tableName" /> 或 <paramref name="schemaPath" /> 为空白时抛出。</exception>
/// <exception cref="ArgumentNullException">当 <paramref name="yamlPath" /> 或 <paramref name="yamlText" /> 为 <see langword="null" /> 时抛出。</exception>
/// <exception cref="GFramework.Game.Abstractions.Config.ConfigLoadException">当 schema 文件不可用,或 YAML 内容与 schema 不匹配时抛出。</exception>
public static void Validate(
string tableName,
string schemaPath,
string yamlPath,
string yamlText)
{
var schema = GetOrLoadSchema(tableName, schemaPath);
YamlConfigSchemaValidator.Validate(tableName, schema, yamlPath, yamlText);
}
/// <summary>
/// 使用指定 schema 文件异步校验 YAML 文本。
/// </summary>
/// <param name="tableName">所属配置表名称。</param>
/// <param name="schemaPath">Schema 文件绝对路径。</param>
/// <param name="yamlPath">YAML 文件路径,仅用于诊断信息。</param>
/// <param name="yamlText">待校验的 YAML 文本。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>表示异步校验操作的任务。</returns>
/// <exception cref="ArgumentException">当 <paramref name="tableName" /> 或 <paramref name="schemaPath" /> 为空白时抛出。</exception>
/// <exception cref="ArgumentNullException">当 <paramref name="yamlPath" /> 或 <paramref name="yamlText" /> 为 <see langword="null" /> 时抛出。</exception>
/// <exception cref="GFramework.Game.Abstractions.Config.ConfigLoadException">当 schema 文件不可用,或 YAML 内容与 schema 不匹配时抛出。</exception>
public static async Task ValidateAsync(
string tableName,
string schemaPath,
string yamlPath,
string yamlText,
CancellationToken cancellationToken = default)
{
var schema = await GetOrLoadSchemaAsync(tableName, schemaPath, cancellationToken)
.ConfigureAwait(false);
YamlConfigSchemaValidator.Validate(tableName, schema, yamlPath, yamlText);
}
/// <summary>
/// 获取可复用的 schema 模型,必要时从磁盘重新加载。
/// </summary>
/// <param name="tableName">所属配置表名称。</param>
/// <param name="schemaPath">Schema 文件绝对路径。</param>
/// <returns>与当前 schema 文件内容匹配的已解析模型。</returns>
/// <exception cref="ArgumentException">当 <paramref name="tableName" /> 或 <paramref name="schemaPath" /> 为空白时抛出。</exception>
/// <exception cref="GFramework.Game.Abstractions.Config.ConfigLoadException">当 schema 文件不可用或内容非法时抛出。</exception>
private static YamlConfigSchema GetOrLoadSchema(
string tableName,
string schemaPath)
{
var cacheKey = CreateCacheKey(tableName, schemaPath);
if (TryGetCachedSchema(cacheKey, out var cachedSchema))
{
return cachedSchema;
}
var schema = YamlConfigSchemaValidator.Load(tableName, schemaPath);
CacheSchema(cacheKey, schema);
return schema;
}
/// <summary>
/// 异步获取可复用的 schema 模型,必要时从磁盘重新加载。
/// </summary>
/// <param name="tableName">所属配置表名称。</param>
/// <param name="schemaPath">Schema 文件绝对路径。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>与当前 schema 文件内容匹配的已解析模型。</returns>
/// <exception cref="ArgumentException">当 <paramref name="tableName" /> 或 <paramref name="schemaPath" /> 为空白时抛出。</exception>
/// <exception cref="GFramework.Game.Abstractions.Config.ConfigLoadException">当 schema 文件不可用或内容非法时抛出。</exception>
private static async Task<YamlConfigSchema> GetOrLoadSchemaAsync(
string tableName,
string schemaPath,
CancellationToken cancellationToken)
{
var cacheKey = CreateCacheKey(tableName, schemaPath);
if (TryGetCachedSchema(cacheKey, out var cachedSchema))
{
return cachedSchema;
}
var schema = await YamlConfigSchemaValidator.LoadAsync(tableName, schemaPath, cancellationToken)
.ConfigureAwait(false);
CacheSchema(cacheKey, schema);
return schema;
}
/// <summary>
/// 创建 schema 缓存键,并提前执行与公开入口一致的参数契约检查。
/// </summary>
/// <param name="tableName">所属配置表名称。</param>
/// <param name="schemaPath">Schema 文件绝对路径。</param>
/// <returns>用于缓存查找的稳定键。</returns>
/// <exception cref="ArgumentException">当 <paramref name="tableName" /> 或 <paramref name="schemaPath" /> 为空白时抛出。</exception>
private static SchemaCacheKey CreateCacheKey(
string tableName,
string schemaPath)
{
if (string.IsNullOrWhiteSpace(tableName))
{
throw new ArgumentException("Table name cannot be null or whitespace.", nameof(tableName));
}
if (string.IsNullOrWhiteSpace(schemaPath))
{
throw new ArgumentException("Schema path cannot be null or whitespace.", nameof(schemaPath));
}
return new SchemaCacheKey(tableName, schemaPath);
}
/// <summary>
/// 尝试命中当前 schema 文件版本对应的缓存项。
/// </summary>
/// <param name="cacheKey">缓存键。</param>
/// <param name="schema">命中的 schema未命中时为 <see langword="null" />。</param>
/// <returns>当缓存项仍与当前文件时间戳一致时返回 <see langword="true" />。</returns>
private static bool TryGetCachedSchema(
SchemaCacheKey cacheKey,
out YamlConfigSchema schema)
{
var lastWriteTimeUtc = File.GetLastWriteTimeUtc(cacheKey.SchemaPath);
if (SchemaCache.TryGetValue(cacheKey, out var cacheEntry) &&
cacheEntry.LastWriteTimeUtc == lastWriteTimeUtc)
{
schema = cacheEntry.Schema;
return true;
}
schema = null!;
return false;
}
/// <summary>
/// 使用最新的文件时间戳刷新 schema 缓存。
/// </summary>
/// <param name="cacheKey">缓存键。</param>
/// <param name="schema">最新加载的 schema。</param>
private static void CacheSchema(
SchemaCacheKey cacheKey,
YamlConfigSchema schema)
{
var lastWriteTimeUtc = File.GetLastWriteTimeUtc(cacheKey.SchemaPath);
SchemaCache[cacheKey] = new SchemaCacheEntry(lastWriteTimeUtc, schema);
}
/// <summary>
/// 表示一个 schema 缓存键。
/// </summary>
/// <param name="TableName">所属配置表名称。</param>
/// <param name="SchemaPath">Schema 文件绝对路径。</param>
private readonly record struct SchemaCacheKey(
string TableName,
string SchemaPath);
/// <summary>
/// 表示一个带文件时间戳的 schema 缓存条目。
/// </summary>
/// <param name="LastWriteTimeUtc">加载时观察到的 schema 文件修改时间。</param>
/// <param name="Schema">已解析的 schema 模型。</param>
private readonly record struct SchemaCacheEntry(
DateTime LastWriteTimeUtc,
YamlConfigSchema Schema);
}