feat: 实现字典管理后端
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using TakeoutSaaS.Application.Dictionary.Abstractions;
|
||||
using TakeoutSaaS.Application.Dictionary.Models;
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Dictionary.ImportExport;
|
||||
|
||||
/// <summary>
|
||||
/// CSV 字典导入解析器。
|
||||
/// </summary>
|
||||
public sealed class CsvDictionaryParser : ICsvDictionaryParser
|
||||
{
|
||||
private static readonly CsvConfiguration CsvConfiguration = new(CultureInfo.InvariantCulture)
|
||||
{
|
||||
HasHeaderRecord = true,
|
||||
MissingFieldFound = null,
|
||||
BadDataFound = null,
|
||||
DetectColumnCountChanges = false,
|
||||
TrimOptions = TrimOptions.Trim,
|
||||
PrepareHeaderForMatch = args => args.Header?.Trim().ToLowerInvariant() ?? string.Empty
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<DictionaryImportRow>> ParseAsync(Stream stream, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Position = 0;
|
||||
}
|
||||
|
||||
var rows = new List<DictionaryImportRow>();
|
||||
using var reader = new StreamReader(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), detectEncodingFromByteOrderMarks: true, leaveOpen: true);
|
||||
using var csv = new CsvReader(reader, CsvConfiguration);
|
||||
|
||||
if (!await csv.ReadAsync() || !csv.ReadHeader())
|
||||
{
|
||||
return rows;
|
||||
}
|
||||
|
||||
while (await csv.ReadAsync())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var rowNumber = csv.Context?.Parser?.Row ?? 0;
|
||||
rows.Add(new DictionaryImportRow
|
||||
{
|
||||
RowNumber = rowNumber,
|
||||
Code = ReadString(csv, "code"),
|
||||
Key = ReadString(csv, "key"),
|
||||
Value = ReadString(csv, "value"),
|
||||
SortOrder = ReadInt(csv, "sortorder"),
|
||||
IsEnabled = ReadBool(csv, "isenabled"),
|
||||
Description = ReadString(csv, "description"),
|
||||
Source = ReadString(csv, "source")
|
||||
});
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static string? ReadString(CsvReader csv, string name)
|
||||
{
|
||||
return csv.TryGetField(name, out string? value)
|
||||
? string.IsNullOrWhiteSpace(value) ? null : value
|
||||
: null;
|
||||
}
|
||||
|
||||
private static int? ReadInt(CsvReader csv, string name)
|
||||
{
|
||||
if (csv.TryGetField(name, out string? value) && int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var number))
|
||||
{
|
||||
return number;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool? ReadBool(CsvReader csv, string name)
|
||||
{
|
||||
if (csv.TryGetField(name, out string? value) && bool.TryParse(value, out var flag))
|
||||
{
|
||||
return flag;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using System.Text.Json;
|
||||
using TakeoutSaaS.Application.Dictionary.Abstractions;
|
||||
using TakeoutSaaS.Application.Dictionary.Models;
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Dictionary.ImportExport;
|
||||
|
||||
/// <summary>
|
||||
/// JSON 字典导入解析器。
|
||||
/// </summary>
|
||||
public sealed class JsonDictionaryParser : IJsonDictionaryParser
|
||||
{
|
||||
private static readonly JsonDocumentOptions DocumentOptions = new()
|
||||
{
|
||||
AllowTrailingCommas = true
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<DictionaryImportRow>> ParseAsync(Stream stream, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Position = 0;
|
||||
}
|
||||
|
||||
using var document = await JsonDocument.ParseAsync(stream, DocumentOptions, cancellationToken);
|
||||
if (document.RootElement.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
return Array.Empty<DictionaryImportRow>();
|
||||
}
|
||||
|
||||
var rows = new List<DictionaryImportRow>();
|
||||
var index = 0;
|
||||
|
||||
foreach (var element in document.RootElement.EnumerateArray())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
index++;
|
||||
|
||||
rows.Add(new DictionaryImportRow
|
||||
{
|
||||
RowNumber = index,
|
||||
Code = ReadString(element, "code"),
|
||||
Key = ReadString(element, "key"),
|
||||
Value = ReadValue(element, "value"),
|
||||
SortOrder = ReadInt(element, "sortOrder"),
|
||||
IsEnabled = ReadBool(element, "isEnabled"),
|
||||
Description = ReadString(element, "description"),
|
||||
Source = ReadString(element, "source")
|
||||
});
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static string? ReadString(JsonElement element, string propertyName)
|
||||
{
|
||||
if (!TryGetProperty(element, propertyName, out var value) || value.ValueKind == JsonValueKind.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return value.ValueKind == JsonValueKind.String ? value.GetString() : value.GetRawText();
|
||||
}
|
||||
|
||||
private static string? ReadValue(JsonElement element, string propertyName)
|
||||
{
|
||||
if (!TryGetProperty(element, propertyName, out var value) || value.ValueKind == JsonValueKind.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return value.ValueKind == JsonValueKind.String ? value.GetString() : value.GetRawText();
|
||||
}
|
||||
|
||||
private static int? ReadInt(JsonElement element, string propertyName)
|
||||
{
|
||||
if (!TryGetProperty(element, propertyName, out var value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.ValueKind == JsonValueKind.Number && value.TryGetInt32(out var number))
|
||||
{
|
||||
return number;
|
||||
}
|
||||
|
||||
if (value.ValueKind == JsonValueKind.String && int.TryParse(value.GetString(), out var parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool? ReadBool(JsonElement element, string propertyName)
|
||||
{
|
||||
if (!TryGetProperty(element, propertyName, out var value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.ValueKind == JsonValueKind.True || value.ValueKind == JsonValueKind.False)
|
||||
{
|
||||
return value.GetBoolean();
|
||||
}
|
||||
|
||||
if (value.ValueKind == JsonValueKind.String && bool.TryParse(value.GetString(), out var parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool TryGetProperty(JsonElement element, string propertyName, out JsonElement value)
|
||||
{
|
||||
foreach (var property in element.EnumerateObject())
|
||||
{
|
||||
if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = property.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user