using Microsoft.Extensions.Caching.Memory;
using Model;
using Model.Dto.System;
using Model.Entity.System;
using Model.Mapper;
using ORM;
using Service.Interface;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Service.Implement
{
///
/// 数据字典管理 服务实现
/// 缓存策略:按 typeCode 缓存启用项,新增/修改/删除字典分类或字典项时失效对应 typeCode;启动首次访问时按需加载
///
public class DictService : IDictService
{
private readonly IMemoryCache _cache;
private static readonly object _cacheLock = new();
public DictService(IMemoryCache cache)
{
_cache = cache;
}
// ==================== 字典分类 ====================
public async Task>> GetTypesPagedAsync(int pageIndex, int pageSize, RefAsync total, string? keyword = null)
{
try
{
var list = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.IsDel == 0)
.WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.Code.Contains(keyword!) || x.Name.Contains(keyword!))
.OrderBy(x => x.Sort).OrderBy(x => x.CreateTime, OrderByType.Desc)
.ToPageListAsync(pageIndex, pageSize, total);
var dtos = list.ToDtoList();
await FillItemCountAsync(dtos);
return Result>.Success(dtos);
}
catch (Exception ex)
{
return Result>.Error("查询字典分类失败", ex);
}
}
public async Task>> GetTypesAllAsync()
{
try
{
var list = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.IsDel == 0 && x.Status == 1)
.OrderBy(x => x.Sort).OrderBy(x => x.Code)
.ToListAsync();
return Result>.Success(list.ToDtoList());
}
catch (Exception ex)
{
return Result>.Error("查询字典分类失败", ex);
}
}
public async Task> GetTypeByIdAsync(long id)
{
try
{
var entity = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result.Error("字典分类不存在或已被删除");
var dto = entity.ToDto();
await FillItemCountAsync(new List { dto });
return Result.Success(dto);
}
catch (Exception ex)
{
return Result.Error("查询字典分类详情失败", ex);
}
}
public async Task> AddTypeAsync(DictTypeDto dto)
{
if (dto == null || string.IsNullOrWhiteSpace(dto.Code) || string.IsNullOrWhiteSpace(dto.Name))
return Result.Error("字典编码和名称不能为空");
try
{
var exists = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Code == dto.Code && x.IsDel == 0).AnyAsync();
if (exists) return Result.Error($"字典编码「{dto.Code}」已存在");
var entity = dto.ToEntity();
entity.Id = 0;
entity.CreateTime = DateTime.Now;
var id = await SqlSugarContext.DbContext.Insertable(entity).ExecuteReturnSnowflakeIdAsync();
entity.Id = id;
return Result.Success(entity.ToDto());
}
catch (Exception ex)
{
return Result.Error("新增字典分类失败", ex);
}
}
public async Task> UpdateTypeAsync(DictTypeDto dto)
{
if (dto == null || !long.TryParse(dto.Id, out var id) || id <= 0)
return Result.Error("字典分类Id无效");
try
{
var entity = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result.Error("字典分类不存在或已被删除");
var codeExists = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Code == dto.Code && x.Id != id && x.IsDel == 0).AnyAsync();
if (codeExists) return Result.Error($"字典编码「{dto.Code}」已存在");
var newEntity = dto.ToEntity();
newEntity.Id = id;
await SqlSugarContext.DbContext.Updateable(newEntity)
.IgnoreColumns(x => new { x.CreateTime, x.IsDel })
.ExecuteCommandAsync();
// 编码可能变更:旧/新 Code 都失效
InvalidateCache(entity.Code);
InvalidateCache(dto.Code);
return Result.Success(newEntity.ToDto());
}
catch (Exception ex)
{
return Result.Error("修改字典分类失败", ex);
}
}
public async Task DeleteTypeAsync(long id)
{
if (id <= 0) return Result.Error("字典分类Id无效");
try
{
var entity = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result.Error("字典分类不存在或已被删除");
var hasItems = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.TypeId == id && x.IsDel == 0).AnyAsync();
if (hasItems) return Result.Error("该字典分类下还有字典项,请先删除字典项再删除分类");
await SqlSugarContext.DbContext.Updateable()
.SetColumns(x => x.IsDel == 1)
.Where(x => x.Id == id).ExecuteCommandAsync();
InvalidateCache(entity.Code);
return Result.Success();
}
catch (Exception ex)
{
return Result.Error("删除字典分类失败", ex);
}
}
// ==================== 字典项 ====================
public async Task>> GetItemsByTypeAsync(long typeId)
{
try
{
var list = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.TypeId == typeId && x.IsDel == 0)
.OrderBy(x => x.Sort).OrderBy(x => x.CreateTime, OrderByType.Desc)
.ToListAsync();
return Result>.Success(list.ToDtoList());
}
catch (Exception ex)
{
return Result>.Error("查询字典项失败", ex);
}
}
public async Task> AddItemAsync(DictItemDto dto)
{
if (dto == null || !long.TryParse(dto.TypeId, out var typeId) || typeId <= 0)
return Result.Error("所属字典分类Id无效");
if (string.IsNullOrWhiteSpace(dto.Code) || string.IsNullOrWhiteSpace(dto.Name))
return Result.Error("字典项编码和名称不能为空");
try
{
var typeExists = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == typeId && x.IsDel == 0).AnyAsync();
if (!typeExists) return Result.Error("所属字典分类不存在");
var codeExists = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.TypeId == typeId && x.Code == dto.Code && x.IsDel == 0).AnyAsync();
if (codeExists) return Result.Error($"字典项编码「{dto.Code}」在该分类下已存在");
var entity = dto.ToEntity();
entity.Id = 0;
entity.TypeId = typeId;
entity.CreateTime = DateTime.Now;
if (entity.IsDefault == 1)
{
await SqlSugarContext.DbContext.Updateable()
.SetColumns(x => x.IsDefault == 0)
.Where(x => x.TypeId == typeId && x.IsDel == 0).ExecuteCommandAsync();
}
var id = await SqlSugarContext.DbContext.Insertable(entity).ExecuteReturnSnowflakeIdAsync();
entity.Id = id;
InvalidateCache(await GetTypeCodeAsync(typeId));
return Result.Success(entity.ToDto());
}
catch (Exception ex)
{
return Result.Error("新增字典项失败", ex);
}
}
public async Task> UpdateItemAsync(DictItemDto dto)
{
if (dto == null || !long.TryParse(dto.Id, out var id) || id <= 0)
return Result.Error("字典项Id无效");
if (!long.TryParse(dto.TypeId, out var typeId) || typeId <= 0)
return Result.Error("所属字典分类Id无效");
try
{
var entity = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result.Error("字典项不存在或已被删除");
var codeExists = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.TypeId == typeId && x.Code == dto.Code && x.Id != id && x.IsDel == 0).AnyAsync();
if (codeExists) return Result.Error($"字典项编码「{dto.Code}」在该分类下已存在");
// 默认项互斥:改为默认时清零同分类其它默认项
if (dto.IsDefault == 1 && entity.IsDefault == 0)
{
await SqlSugarContext.DbContext.Updateable()
.SetColumns(x => x.IsDefault == 0)
.Where(x => x.TypeId == typeId && x.IsDel == 0 && x.Id != id).ExecuteCommandAsync();
}
var newEntity = dto.ToEntity();
newEntity.Id = id;
newEntity.TypeId = typeId;
await SqlSugarContext.DbContext.Updateable(newEntity)
.IgnoreColumns(x => new { x.CreateTime, x.IsDel })
.ExecuteCommandAsync();
InvalidateCache(await GetTypeCodeAsync(typeId));
return Result.Success(newEntity.ToDto());
}
catch (Exception ex)
{
return Result.Error("修改字典项失败", ex);
}
}
public async Task DeleteItemAsync(long id)
{
if (id <= 0) return Result.Error("字典项Id无效");
try
{
var entity = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
if (entity == null) return Result.Error("字典项不存在或已被删除");
await SqlSugarContext.DbContext.Updateable()
.SetColumns(x => x.IsDel == 1)
.Where(x => x.Id == id).ExecuteCommandAsync();
InvalidateCache(await GetTypeCodeAsync(entity.TypeId));
return Result.Success();
}
catch (Exception ex)
{
return Result.Error("删除字典项失败", ex);
}
}
// ==================== 业务侧:按 typeCode 查询启用项(带缓存) ====================
public async Task>> GetOptionsByCodeAsync(string typeCode)
{
if (string.IsNullOrWhiteSpace(typeCode))
return Result>.Error("字典编码不能为空");
try
{
var key = $"dict:options:{typeCode}";
if (!_cache.TryGetValue(key, out List? cached) || cached == null)
{
lock (_cacheLock)
{
cached ??= LoadOptionsByCodeAsync(typeCode).GetAwaiter().GetResult();
_cache.Set(key, cached, TimeSpan.FromHours(1));
}
}
return Result>.Success(cached);
}
catch (Exception ex)
{
return Result>.Error("查询字典选项失败", ex);
}
}
// ==================== 私有工具 ====================
private async Task GetTypeCodeAsync(long typeId)
{
var code = await SqlSugarContext.DbContext.Queryable()
.Where(x => x.Id == typeId).Select(x => x.Code).FirstAsync();
return code ?? string.Empty;
}
private async Task> LoadOptionsByCodeAsync(string typeCode)
{
var db = SqlSugarContext.DbContext;
var typeId = await db.Queryable()
.Where(x => x.Code == typeCode && x.IsDel == 0 && x.Status == 1)
.Select(x => x.Id).FirstAsync();
if (typeId == 0) return new List();
var items = await db.Queryable()
.Where(x => x.TypeId == typeId && x.IsDel == 0 && x.Status == 1)
.OrderBy(x => x.Sort).OrderBy(x => x.Code)
.ToListAsync();
return items.Select(e => new DictOptionDto
{
Code = e.Code,
Name = e.Name,
IsDefault = e.IsDefault == 1
}).ToList();
}
private void InvalidateCache(string? typeCode)
{
if (string.IsNullOrWhiteSpace(typeCode)) return;
_cache.Remove($"dict:options:{typeCode}");
}
private async Task FillItemCountAsync(List dtos)
{
if (dtos == null || dtos.Count == 0) return;
var ids = dtos.Select(d => long.Parse(d.Id)).ToList();
var counts = await SqlSugarContext.DbContext.Queryable()
.Where(x => ids.Contains(x.TypeId) && x.IsDel == 0)
.GroupBy(x => x.TypeId)
.Select(x => new { TypeId = x.TypeId, Count = SqlFunc.AggregateCount(x.Id) })
.ToListAsync();
var map = counts.ToDictionary(c => c.TypeId, c => c.Count);
foreach (var d in dtos) d.ItemCount = map.TryGetValue(long.Parse(d.Id), out var c) ? c : 0;
}
}
}