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; } } }