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