完善系统参数配置和数据字典管理模块
This commit is contained in:
@@ -1,12 +1,360 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据字典管理 服务实现
|
||||
/// 缓存策略:按 typeCode 缓存启用项,新增/修改/删除字典分类或字典项时失效对应 typeCode;启动首次访问时按需加载
|
||||
/// </summary>
|
||||
public class DictService : IDictService
|
||||
{
|
||||
// TODO: 实现 数据字典管理 相关方法
|
||||
private readonly IMemoryCache _cache;
|
||||
private static readonly object _cacheLock = new();
|
||||
|
||||
public DictService(IMemoryCache cache)
|
||||
{
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
// ==================== 字典分类 ====================
|
||||
|
||||
public async Task<Result<List<DictTypeDto>>> GetTypesPagedAsync(int pageIndex, int pageSize, RefAsync<int> total, string? keyword = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await SqlSugarContext.DbContext.Queryable<DictTypeEntity>()
|
||||
.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<List<DictTypeDto>>.Success(dtos);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<List<DictTypeDto>>.Error("查询字典分类失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result<List<DictTypeDto>>> GetTypesAllAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await SqlSugarContext.DbContext.Queryable<DictTypeEntity>()
|
||||
.Where(x => x.IsDel == 0 && x.Status == 1)
|
||||
.OrderBy(x => x.Sort).OrderBy(x => x.Code)
|
||||
.ToListAsync();
|
||||
return Result<List<DictTypeDto>>.Success(list.ToDtoList());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<List<DictTypeDto>>.Error("查询字典分类失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result<DictTypeDto>> GetTypeByIdAsync(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var entity = await SqlSugarContext.DbContext.Queryable<DictTypeEntity>()
|
||||
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
|
||||
if (entity == null) return Result<DictTypeDto>.Error("字典分类不存在或已被删除");
|
||||
var dto = entity.ToDto();
|
||||
await FillItemCountAsync(new List<DictTypeDto> { dto });
|
||||
return Result<DictTypeDto>.Success(dto);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<DictTypeDto>.Error("查询字典分类详情失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result<DictTypeDto>> AddTypeAsync(DictTypeDto dto)
|
||||
{
|
||||
if (dto == null || string.IsNullOrWhiteSpace(dto.Code) || string.IsNullOrWhiteSpace(dto.Name))
|
||||
return Result<DictTypeDto>.Error("字典编码和名称不能为空");
|
||||
|
||||
try
|
||||
{
|
||||
var exists = await SqlSugarContext.DbContext.Queryable<DictTypeEntity>()
|
||||
.Where(x => x.Code == dto.Code && x.IsDel == 0).AnyAsync();
|
||||
if (exists) return Result<DictTypeDto>.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<DictTypeDto>.Success(entity.ToDto());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<DictTypeDto>.Error("新增字典分类失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result<DictTypeDto>> UpdateTypeAsync(DictTypeDto dto)
|
||||
{
|
||||
if (dto == null || !long.TryParse(dto.Id, out var id) || id <= 0)
|
||||
return Result<DictTypeDto>.Error("字典分类Id无效");
|
||||
|
||||
try
|
||||
{
|
||||
var entity = await SqlSugarContext.DbContext.Queryable<DictTypeEntity>()
|
||||
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
|
||||
if (entity == null) return Result<DictTypeDto>.Error("字典分类不存在或已被删除");
|
||||
|
||||
var codeExists = await SqlSugarContext.DbContext.Queryable<DictTypeEntity>()
|
||||
.Where(x => x.Code == dto.Code && x.Id != id && x.IsDel == 0).AnyAsync();
|
||||
if (codeExists) return Result<DictTypeDto>.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<DictTypeDto>.Success(newEntity.ToDto());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<DictTypeDto>.Error("修改字典分类失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result> DeleteTypeAsync(long id)
|
||||
{
|
||||
if (id <= 0) return Result.Error("字典分类Id无效");
|
||||
try
|
||||
{
|
||||
var entity = await SqlSugarContext.DbContext.Queryable<DictTypeEntity>()
|
||||
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
|
||||
if (entity == null) return Result.Error("字典分类不存在或已被删除");
|
||||
|
||||
var hasItems = await SqlSugarContext.DbContext.Queryable<DictItemEntity>()
|
||||
.Where(x => x.TypeId == id && x.IsDel == 0).AnyAsync();
|
||||
if (hasItems) return Result.Error("该字典分类下还有字典项,请先删除字典项再删除分类");
|
||||
|
||||
await SqlSugarContext.DbContext.Updateable<DictTypeEntity>()
|
||||
.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<Result<List<DictItemDto>>> GetItemsByTypeAsync(long typeId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await SqlSugarContext.DbContext.Queryable<DictItemEntity>()
|
||||
.Where(x => x.TypeId == typeId && x.IsDel == 0)
|
||||
.OrderBy(x => x.Sort).OrderBy(x => x.CreateTime, OrderByType.Desc)
|
||||
.ToListAsync();
|
||||
return Result<List<DictItemDto>>.Success(list.ToDtoList());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<List<DictItemDto>>.Error("查询字典项失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result<DictItemDto>> AddItemAsync(DictItemDto dto)
|
||||
{
|
||||
if (dto == null || !long.TryParse(dto.TypeId, out var typeId) || typeId <= 0)
|
||||
return Result<DictItemDto>.Error("所属字典分类Id无效");
|
||||
if (string.IsNullOrWhiteSpace(dto.Code) || string.IsNullOrWhiteSpace(dto.Name))
|
||||
return Result<DictItemDto>.Error("字典项编码和名称不能为空");
|
||||
|
||||
try
|
||||
{
|
||||
var typeExists = await SqlSugarContext.DbContext.Queryable<DictTypeEntity>()
|
||||
.Where(x => x.Id == typeId && x.IsDel == 0).AnyAsync();
|
||||
if (!typeExists) return Result<DictItemDto>.Error("所属字典分类不存在");
|
||||
|
||||
var codeExists = await SqlSugarContext.DbContext.Queryable<DictItemEntity>()
|
||||
.Where(x => x.TypeId == typeId && x.Code == dto.Code && x.IsDel == 0).AnyAsync();
|
||||
if (codeExists) return Result<DictItemDto>.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<DictItemEntity>()
|
||||
.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<DictItemDto>.Success(entity.ToDto());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<DictItemDto>.Error("新增字典项失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result<DictItemDto>> UpdateItemAsync(DictItemDto dto)
|
||||
{
|
||||
if (dto == null || !long.TryParse(dto.Id, out var id) || id <= 0)
|
||||
return Result<DictItemDto>.Error("字典项Id无效");
|
||||
if (!long.TryParse(dto.TypeId, out var typeId) || typeId <= 0)
|
||||
return Result<DictItemDto>.Error("所属字典分类Id无效");
|
||||
|
||||
try
|
||||
{
|
||||
var entity = await SqlSugarContext.DbContext.Queryable<DictItemEntity>()
|
||||
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
|
||||
if (entity == null) return Result<DictItemDto>.Error("字典项不存在或已被删除");
|
||||
|
||||
var codeExists = await SqlSugarContext.DbContext.Queryable<DictItemEntity>()
|
||||
.Where(x => x.TypeId == typeId && x.Code == dto.Code && x.Id != id && x.IsDel == 0).AnyAsync();
|
||||
if (codeExists) return Result<DictItemDto>.Error($"字典项编码「{dto.Code}」在该分类下已存在");
|
||||
|
||||
// 默认项互斥:改为默认时清零同分类其它默认项
|
||||
if (dto.IsDefault == 1 && entity.IsDefault == 0)
|
||||
{
|
||||
await SqlSugarContext.DbContext.Updateable<DictItemEntity>()
|
||||
.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<DictItemDto>.Success(newEntity.ToDto());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<DictItemDto>.Error("修改字典项失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result> DeleteItemAsync(long id)
|
||||
{
|
||||
if (id <= 0) return Result.Error("字典项Id无效");
|
||||
try
|
||||
{
|
||||
var entity = await SqlSugarContext.DbContext.Queryable<DictItemEntity>()
|
||||
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
|
||||
if (entity == null) return Result.Error("字典项不存在或已被删除");
|
||||
|
||||
await SqlSugarContext.DbContext.Updateable<DictItemEntity>()
|
||||
.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<Result<List<DictOptionDto>>> GetOptionsByCodeAsync(string typeCode)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(typeCode))
|
||||
return Result<List<DictOptionDto>>.Error("字典编码不能为空");
|
||||
|
||||
try
|
||||
{
|
||||
var key = $"dict:options:{typeCode}";
|
||||
if (!_cache.TryGetValue(key, out List<DictOptionDto>? cached) || cached == null)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
cached ??= LoadOptionsByCodeAsync(typeCode).GetAwaiter().GetResult();
|
||||
_cache.Set(key, cached, TimeSpan.FromHours(1));
|
||||
}
|
||||
}
|
||||
return Result<List<DictOptionDto>>.Success(cached);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result<List<DictOptionDto>>.Error("查询字典选项失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 私有工具 ====================
|
||||
|
||||
private async Task<string> GetTypeCodeAsync(long typeId)
|
||||
{
|
||||
var code = await SqlSugarContext.DbContext.Queryable<DictTypeEntity>()
|
||||
.Where(x => x.Id == typeId).Select(x => x.Code).FirstAsync();
|
||||
return code ?? string.Empty;
|
||||
}
|
||||
|
||||
private async Task<List<DictOptionDto>> LoadOptionsByCodeAsync(string typeCode)
|
||||
{
|
||||
var db = SqlSugarContext.DbContext;
|
||||
var typeId = await db.Queryable<DictTypeEntity>()
|
||||
.Where(x => x.Code == typeCode && x.IsDel == 0 && x.Status == 1)
|
||||
.Select(x => x.Id).FirstAsync();
|
||||
if (typeId == 0) return new List<DictOptionDto>();
|
||||
|
||||
var items = await db.Queryable<DictItemEntity>()
|
||||
.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<DictTypeDto> dtos)
|
||||
{
|
||||
if (dtos == null || dtos.Count == 0) return;
|
||||
var ids = dtos.Select(d => long.Parse(d.Id)).ToList();
|
||||
var counts = await SqlSugarContext.DbContext.Queryable<DictItemEntity>()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user