using MiAssessment.Admin.Data; using MiAssessment.Admin.Entities; using MiAssessment.Admin.Models.Common; using MiAssessment.Admin.Models.Dict; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace MiAssessment.Admin.Services; /// /// 字典服务实现 /// public class DictService : IDictService { private readonly AdminDbContext _dbContext; private readonly ILogger _logger; /// /// 数据源类型:静态数据 /// private const byte SourceTypeStatic = 1; /// /// 数据源类型:SQL查询 /// private const byte SourceTypeSql = 2; /// /// 状态:启用 /// private const byte StatusEnabled = 1; public DictService(AdminDbContext dbContext, ILogger logger) { _dbContext = dbContext; _logger = logger; } /// public async Task> GetTypesAsync() { return await _dbContext.DictTypes .Where(t => t.Status == StatusEnabled) .OrderBy(t => t.Sort) .ThenBy(t => t.Id) .Select(t => new DictTypeDto { Id = t.Id, Code = t.Code, Name = t.Name, Description = t.Description, SourceType = t.SourceType, SourceSql = t.SourceSql, Status = t.Status, Sort = t.Sort, CreatedAt = t.CreatedAt, UpdatedAt = t.UpdatedAt }) .ToListAsync(); } /// public async Task GetTypeByCodeAsync(string code) { var dictType = await _dbContext.DictTypes .FirstOrDefaultAsync(t => t.Code == code); if (dictType == null) { return null; } return new DictTypeDto { Id = dictType.Id, Code = dictType.Code, Name = dictType.Name, Description = dictType.Description, SourceType = dictType.SourceType, SourceSql = dictType.SourceSql, Status = dictType.Status, Sort = dictType.Sort, CreatedAt = dictType.CreatedAt, UpdatedAt = dictType.UpdatedAt }; } /// public async Task> GetItemsByTypeCodeAsync(string typeCode) { // 获取字典类型 var dictType = await _dbContext.DictTypes .FirstOrDefaultAsync(t => t.Code == typeCode); if (dictType == null) { _logger.LogWarning("字典类型不存在: {TypeCode}", typeCode); return new List(); } // 检查字典类型是否禁用 (Requirements 4.6) if (dictType.Status != StatusEnabled) { _logger.LogInformation("字典类型已禁用,不返回数据: {TypeCode}", typeCode); return new List(); } // 根据数据源类型返回数据 if (dictType.SourceType == SourceTypeStatic) { // 静态数据查询 (Requirements 4.4) return await GetStaticItemsAsync(dictType.Id); } else if (dictType.SourceType == SourceTypeSql) { // 动态 SQL 查询 (Requirements 4.5) return await GetDynamicItemsAsync(dictType); } _logger.LogWarning("未知的数据源类型: {SourceType}, TypeCode: {TypeCode}", dictType.SourceType, typeCode); return new List(); } /// /// 获取静态字典数据项 (Requirements 4.4, 4.7) /// private async Task> GetStaticItemsAsync(int typeId) { return await _dbContext.DictItems .Where(i => i.TypeId == typeId && i.Status == StatusEnabled) // 过滤禁用项 (Requirements 4.7) .OrderBy(i => i.Sort) .ThenBy(i => i.Id) .Select(i => new DictItemDto { Id = i.Id, TypeId = i.TypeId, Label = i.Label, Value = i.Value, Description = i.Description, CssClass = i.CssClass, Status = i.Status, Sort = i.Sort, CreatedAt = i.CreatedAt, UpdatedAt = i.UpdatedAt }) .ToListAsync(); } /// /// 执行动态 SQL 查询获取字典数据 (Requirements 4.5) /// SQL 查询必须返回 label 和 value 两列 /// private async Task> GetDynamicItemsAsync(DictType dictType) { if (string.IsNullOrWhiteSpace(dictType.SourceSql)) { _logger.LogWarning("字典类型 {TypeCode} 的 SQL 查询语句为空", dictType.Code); return new List(); } var items = new List(); try { // 验证 SQL 安全性(只允许 SELECT 语句) var sql = dictType.SourceSql.Trim(); if (!sql.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase)) { _logger.LogError("字典类型 {TypeCode} 的 SQL 不是 SELECT 语句,拒绝执行", dictType.Code); return new List(); } // 检查是否包含危险关键字 var dangerousKeywords = new[] { "INSERT", "UPDATE", "DELETE", "DROP", "TRUNCATE", "ALTER", "CREATE", "EXEC", "EXECUTE", "--", "/*" }; var upperSql = sql.ToUpperInvariant(); foreach (var keyword in dangerousKeywords) { if (upperSql.Contains(keyword)) { _logger.LogError("字典类型 {TypeCode} 的 SQL 包含危险关键字 {Keyword},拒绝执行", dictType.Code, keyword); return new List(); } } // 使用 ADO.NET 执行 SQL 查询 var connection = _dbContext.Database.GetDbConnection(); await connection.OpenAsync(); try { using var command = connection.CreateCommand(); command.CommandText = sql; command.CommandTimeout = 30; // 30秒超时 using var reader = await command.ExecuteReaderAsync(); // 检查是否有 label 和 value 列 var hasLabel = false; var hasValue = false; for (int i = 0; i < reader.FieldCount; i++) { var columnName = reader.GetName(i).ToLowerInvariant(); if (columnName == "label") hasLabel = true; if (columnName == "value") hasValue = true; } if (!hasLabel || !hasValue) { _logger.LogError("字典类型 {TypeCode} 的 SQL 查询结果必须包含 label 和 value 列", dictType.Code); return new List(); } var sortIndex = 0; while (await reader.ReadAsync()) { var item = new DictItemDto { Id = 0, // 动态数据没有 ID TypeId = dictType.Id, Label = reader["label"]?.ToString() ?? string.Empty, Value = reader["value"]?.ToString() ?? string.Empty, Status = StatusEnabled, Sort = sortIndex++, CreatedAt = DateTime.Now }; // 尝试读取可选列 try { var descOrdinal = reader.GetOrdinal("description"); if (!reader.IsDBNull(descOrdinal)) { item.Description = reader.GetString(descOrdinal); } } catch { /* 列不存在,忽略 */ } try { var cssOrdinal = reader.GetOrdinal("css_class"); if (!reader.IsDBNull(cssOrdinal)) { item.CssClass = reader.GetString(cssOrdinal); } } catch { /* 列不存在,忽略 */ } items.Add(item); } } finally { if (connection.State == System.Data.ConnectionState.Open) { await connection.CloseAsync(); } } _logger.LogInformation("字典类型 {TypeCode} 动态 SQL 查询成功,返回 {Count} 条数据", dictType.Code, items.Count); } catch (Exception ex) { _logger.LogError(ex, "执行字典类型 {TypeCode} 的动态 SQL 查询失败", dictType.Code); } return items; } /// public async Task CreateTypeAsync(CreateDictTypeRequest request) { // 检查编码是否重复 var codeExists = await _dbContext.DictTypes.AnyAsync(t => t.Code == request.Code); if (codeExists) { throw new AdminException(AdminErrorCodes.DuplicateDictTypeCode, "字典编码已存在"); } // 验证 SQL 查询类型必须提供 SQL 语句 if (request.SourceType == SourceTypeSql && string.IsNullOrWhiteSpace(request.SourceSql)) { throw new AdminException(AdminErrorCodes.InvalidParameter, "SQL查询类型必须提供SQL语句"); } var dictType = new DictType { Code = request.Code, Name = request.Name, Description = request.Description, SourceType = request.SourceType, SourceSql = request.SourceSql, Status = request.Status, Sort = request.Sort, CreatedAt = DateTime.Now }; _dbContext.DictTypes.Add(dictType); await _dbContext.SaveChangesAsync(); _logger.LogInformation("创建字典类型成功: {DictTypeId} - {DictTypeCode}", dictType.Id, dictType.Code); return new DictTypeDto { Id = dictType.Id, Code = dictType.Code, Name = dictType.Name, Description = dictType.Description, SourceType = dictType.SourceType, SourceSql = dictType.SourceSql, Status = dictType.Status, Sort = dictType.Sort, CreatedAt = dictType.CreatedAt, UpdatedAt = dictType.UpdatedAt }; } /// public async Task UpdateTypeAsync(int id, UpdateDictTypeRequest request) { var dictType = await _dbContext.DictTypes.FindAsync(id); if (dictType == null) { throw new AdminException(AdminErrorCodes.DictTypeNotFound, "字典类型不存在"); } // 检查编码是否重复(排除自己) var codeExists = await _dbContext.DictTypes.AnyAsync(t => t.Code == request.Code && t.Id != id); if (codeExists) { throw new AdminException(AdminErrorCodes.DuplicateDictTypeCode, "字典编码已存在"); } // 验证 SQL 查询类型必须提供 SQL 语句 if (request.SourceType == SourceTypeSql && string.IsNullOrWhiteSpace(request.SourceSql)) { throw new AdminException(AdminErrorCodes.InvalidParameter, "SQL查询类型必须提供SQL语句"); } dictType.Code = request.Code; dictType.Name = request.Name; dictType.Description = request.Description; dictType.SourceType = request.SourceType; dictType.SourceSql = request.SourceSql; dictType.Status = request.Status; dictType.Sort = request.Sort; dictType.UpdatedAt = DateTime.Now; await _dbContext.SaveChangesAsync(); _logger.LogInformation("更新字典类型成功: {DictTypeId} - {DictTypeCode}", dictType.Id, dictType.Code); return true; } /// public async Task DeleteTypeAsync(int id) { var dictType = await _dbContext.DictTypes.FindAsync(id); if (dictType == null) { throw new AdminException(AdminErrorCodes.DictTypeNotFound, "字典类型不存在"); } // 删除关联的字典数据项 var items = await _dbContext.DictItems.Where(i => i.TypeId == id).ToListAsync(); _dbContext.DictItems.RemoveRange(items); _dbContext.DictTypes.Remove(dictType); await _dbContext.SaveChangesAsync(); _logger.LogInformation("删除字典类型成功: {DictTypeId} - {DictTypeCode},同时删除 {ItemCount} 条数据项", id, dictType.Code, items.Count); return true; } /// public async Task CreateItemAsync(CreateDictItemRequest request) { // 检查字典类型是否存在 var dictType = await _dbContext.DictTypes.FindAsync(request.TypeId); if (dictType == null) { throw new AdminException(AdminErrorCodes.DictTypeNotFound, "字典类型不存在"); } // 检查字典类型是否为静态数据类型 if (dictType.SourceType != SourceTypeStatic) { throw new AdminException(AdminErrorCodes.InvalidParameter, "只有静态数据类型的字典才能添加数据项"); } var dictItem = new DictItem { TypeId = request.TypeId, Label = request.Label, Value = request.Value, Description = request.Description, CssClass = request.CssClass, Status = request.Status, Sort = request.Sort, CreatedAt = DateTime.Now }; _dbContext.DictItems.Add(dictItem); await _dbContext.SaveChangesAsync(); _logger.LogInformation("创建字典数据项成功: {DictItemId} - {Label}", dictItem.Id, dictItem.Label); return new DictItemDto { Id = dictItem.Id, TypeId = dictItem.TypeId, Label = dictItem.Label, Value = dictItem.Value, Description = dictItem.Description, CssClass = dictItem.CssClass, Status = dictItem.Status, Sort = dictItem.Sort, CreatedAt = dictItem.CreatedAt, UpdatedAt = dictItem.UpdatedAt }; } /// public async Task UpdateItemAsync(int id, UpdateDictItemRequest request) { var dictItem = await _dbContext.DictItems.FindAsync(id); if (dictItem == null) { throw new AdminException(AdminErrorCodes.DictItemNotFound, "字典数据项不存在"); } dictItem.Label = request.Label; dictItem.Value = request.Value; dictItem.Description = request.Description; dictItem.CssClass = request.CssClass; dictItem.Status = request.Status; dictItem.Sort = request.Sort; dictItem.UpdatedAt = DateTime.Now; await _dbContext.SaveChangesAsync(); _logger.LogInformation("更新字典数据项成功: {DictItemId} - {Label}", dictItem.Id, dictItem.Label); return true; } /// public async Task DeleteItemAsync(int id) { var dictItem = await _dbContext.DictItems.FindAsync(id); if (dictItem == null) { throw new AdminException(AdminErrorCodes.DictItemNotFound, "字典数据项不存在"); } _dbContext.DictItems.Remove(dictItem); await _dbContext.SaveChangesAsync(); _logger.LogInformation("删除字典数据项成功: {DictItemId} - {Label}", id, dictItem.Label); return true; } }