feat: 实现字典管理后端

This commit is contained in:
2025-12-30 19:38:13 +08:00
parent a427b0f22a
commit dc9f6136d6
83 changed files with 6901 additions and 50 deletions

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using System.Security.Cryptography;
using TakeoutSaaS.Application.Dictionary.Abstractions;
using TakeoutSaaS.Application.Dictionary.Contracts;
using TakeoutSaaS.Application.Dictionary.Models;
@@ -47,8 +48,10 @@ public sealed class DictionaryAppService(
Code = normalizedCode,
Name = request.Name.Trim(),
Scope = request.Scope,
AllowOverride = request.AllowOverride,
Description = request.Description?.Trim(),
IsEnabled = true
IsEnabled = true,
RowVersion = RandomNumberGenerator.GetBytes(16)
};
// 4. 持久化并返回
@@ -71,13 +74,32 @@ public sealed class DictionaryAppService(
var group = await RequireGroupAsync(groupId, cancellationToken);
EnsureScopePermission(group.Scope);
if (request.RowVersion == null || request.RowVersion.Length == 0)
{
throw new BusinessException(ErrorCodes.ValidationFailed, "RowVersion 不能为空");
}
if (!request.RowVersion.SequenceEqual(group.RowVersion))
{
throw new BusinessException(ErrorCodes.Conflict, "字典分组已被修改,请刷新后重试");
}
// 2. 更新字段
group.Name = request.Name.Trim();
group.Description = request.Description?.Trim();
group.IsEnabled = request.IsEnabled;
group.AllowOverride = request.AllowOverride;
group.RowVersion = RandomNumberGenerator.GetBytes(16);
// 3. 持久化并失效缓存
await repository.SaveChangesAsync(cancellationToken);
try
{
await repository.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (IsConcurrencyException(exception))
{
throw new BusinessException(ErrorCodes.Conflict, "字典分组已被修改,请刷新后重试");
}
await InvalidateCacheAsync(group, cancellationToken);
logger.LogInformation("更新字典分组:{GroupId}", group.Id);
return MapGroup(group, includeItems: false);
@@ -154,11 +176,12 @@ public sealed class DictionaryAppService(
TenantId = group.TenantId,
GroupId = group.Id,
Key = request.Key.Trim(),
Value = request.Value.Trim(),
Value = DictionaryValueConverter.Serialize(request.Value),
Description = request.Description?.Trim(),
SortOrder = request.SortOrder,
IsDefault = request.IsDefault,
IsEnabled = request.IsEnabled
IsEnabled = request.IsEnabled,
RowVersion = RandomNumberGenerator.GetBytes(16)
};
// 3. 持久化并失效缓存
@@ -183,15 +206,34 @@ public sealed class DictionaryAppService(
var group = await RequireGroupAsync(item.GroupId, cancellationToken);
EnsureScopePermission(group.Scope);
if (request.RowVersion == null || request.RowVersion.Length == 0)
{
throw new BusinessException(ErrorCodes.ValidationFailed, "RowVersion 不能为空");
}
if (!request.RowVersion.SequenceEqual(item.RowVersion))
{
throw new BusinessException(ErrorCodes.Conflict, "字典项已被修改,请刷新后重试");
}
// 2. 更新字段
item.Value = request.Value.Trim();
item.Key = request.Key.Trim();
item.Value = DictionaryValueConverter.Serialize(request.Value);
item.Description = request.Description?.Trim();
item.SortOrder = request.SortOrder;
item.IsDefault = request.IsDefault;
item.IsEnabled = request.IsEnabled;
item.RowVersion = RandomNumberGenerator.GetBytes(16);
// 3. 持久化并失效缓存
await repository.SaveChangesAsync(cancellationToken);
try
{
await repository.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (IsConcurrencyException(exception))
{
throw new BusinessException(ErrorCodes.Conflict, "字典项已被修改,请刷新后重试");
}
await InvalidateCacheAsync(group, cancellationToken);
logger.LogInformation("更新字典项:{ItemId}", item.Id);
return MapItem(item);
@@ -381,10 +423,15 @@ public sealed class DictionaryAppService(
{
Id = group.Id,
Code = group.Code,
TenantId = group.TenantId,
Name = group.Name,
Scope = group.Scope,
Description = group.Description,
AllowOverride = group.AllowOverride,
IsEnabled = group.IsEnabled,
CreatedAt = group.CreatedAt,
UpdatedAt = group.UpdatedAt,
RowVersion = group.RowVersion,
Items = includeItems ? items ?? group.Items.Select(MapItem).ToList() : Array.Empty<DictionaryItemDto>()
};
}
@@ -395,10 +442,15 @@ public sealed class DictionaryAppService(
Id = item.Id,
GroupId = item.GroupId,
Key = item.Key,
Value = item.Value,
Value = DictionaryValueConverter.Deserialize(item.Value),
IsDefault = item.IsDefault,
IsEnabled = item.IsEnabled,
SortOrder = item.SortOrder,
Description = item.Description
Description = item.Description,
Source = item.TenantId == 0 ? "system" : "tenant",
RowVersion = item.RowVersion
};
private static bool IsConcurrencyException(Exception exception)
=> string.Equals(exception.GetType().Name, "DbUpdateConcurrencyException", StringComparison.Ordinal);
}