HuanMengAdmin/admin-server/MiaoYu.Api.Admin/ApplicationServices/DevelopmentTools/LowCode/Impl/LowCodeTableService.cs
2025-11-08 15:00:24 +08:00

597 lines
24 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using MiaoYu.Repository.Admin.Entities.LowCode;
using MiaoYu.Repository.ChatAI.Admin.Entities;
using MiaoYu.Shared.Admin.Models.LowCodes;
using CoreIDatabaseTableService = MiaoYu.Core.CodeGenerator.Services.IDatabaseTableService;
using CoreICodeGenerationService = MiaoYu.Core.CodeGenerator.Services.ICodeGenerationService;
using CoreDataSourceManager = MiaoYu.Core.CodeGenerator.Core.DataSourceManager;
using CoreITableMetaConfigService = MiaoYu.Core.CodeGenerator.Services.ITableMetaConfigService;
using CoreITableSchemaCache = MiaoYu.Core.CodeGenerator.Services.ITableSchemaCache;
using CoreEntityNamingStrategy = MiaoYu.Core.CodeGenerator.Abstractions.EntityNamingStrategy;
using CorePathResolver = MiaoYu.Core.CodeGenerator.Core.PathResolver;
using CoreGenDbTableDto = MiaoYu.Core.CodeGenerator.Models.GenDbTableDto;
using CoreTableMetaConfig = MiaoYu.Core.CodeGenerator.Models.TableMetaConfig;
using CoreColumnMetaConfig = MiaoYu.Core.CodeGenerator.Models.ColumnMetaConfig;
namespace MiaoYu.Api.Admin.ApplicationServices.DevelopmentTools.LowCode.Impl;
/// <summary>
/// 服务 Low_Code_TableService
/// </summary>
public class LowCodeTableService : ApplicationService<IRepository<LowCodeTable>>
{
private readonly IRepository<LowCodeTableInfo> _lowCodeTableInfoRepository;
private readonly LowCodeTableInfoService _lowCodeTableInfoService;
private readonly CoreIDatabaseTableService _databaseTableService;
private readonly CoreICodeGenerationService _codeGenerationService;
private readonly CoreDataSourceManager _dataSourceManager;
private readonly CoreITableMetaConfigService _tableMetaConfigService;
private readonly CoreITableSchemaCache _tableSchemaCache;
private readonly CorePathResolver _pathResolver;
private readonly ILogger<LowCodeTableService>? _logger;
public LowCodeTableService(
IRepository<LowCodeTable> defaultRepository,
LowCodeTableInfoService lowCodeTableInfoService,
IRepository<LowCodeTableInfo> lowCodeTableInfoRepository,
CoreIDatabaseTableService databaseTableService,
CoreICodeGenerationService codeGenerationService,
CoreDataSourceManager dataSourceManager,
CoreITableMetaConfigService tableMetaConfigService,
CoreITableSchemaCache tableSchemaCache,
CorePathResolver pathResolver,
ILogger<LowCodeTableService>? logger = null) : base(defaultRepository)
{
_lowCodeTableInfoService = lowCodeTableInfoService;
_lowCodeTableInfoRepository = lowCodeTableInfoRepository;
_databaseTableService = databaseTableService;
_codeGenerationService = codeGenerationService;
_dataSourceManager = dataSourceManager;
_tableMetaConfigService = tableMetaConfigService;
_tableSchemaCache = tableSchemaCache;
_pathResolver = pathResolver;
_logger = logger;
}
/// <summary>
/// 获取列表数据(从 Core 层查询表结构并合并配置)
/// </summary>
/// <param name="pagingSearchInput"></param>
/// <returns></returns>
public async Task<PagingView> FindListAsync(PagingSearchInput<LowCodeTable> pagingSearchInput)
{
// ✅ 从 Core 层获取所有表信息(已包含配置合并)
var allTables = _databaseTableService.GetAllTables();
// 应用筛选条件
var filteredTables = allTables.AsEnumerable();
// ✅ 数据库筛选条件(重要:支持多数据源筛选)
if (!string.IsNullOrWhiteSpace(pagingSearchInput.Search.DataBase))
{
filteredTables = filteredTables.Where(t =>
t.DataBase == pagingSearchInput.Search.DataBase);
}
if (!string.IsNullOrWhiteSpace(pagingSearchInput.Search.TableName))
{
filteredTables = filteredTables.Where(t =>
t.TableName != null && t.TableName.Contains(pagingSearchInput.Search.TableName));
}
if (!string.IsNullOrWhiteSpace(pagingSearchInput.Search.EntityName))
{
filteredTables = filteredTables.Where(t =>
t.EntityName != null && t.EntityName.Contains(pagingSearchInput.Search.EntityName));
}
if (!string.IsNullOrWhiteSpace(pagingSearchInput.Search.DisplayName))
{
filteredTables = filteredTables.Where(t =>
t.DisplayName != null && t.DisplayName.Contains(pagingSearchInput.Search.DisplayName));
}
// 排序和分页
var pagedTablesList = filteredTables
.Skip((pagingSearchInput.Page - 1) * pagingSearchInput.Size)
.Take(pagingSearchInput.Size)
.ToList();
// 异步获取 ID 并构建结果
var pagedTables = new List<Dictionary<string, object?>>();
foreach (var t in pagedTablesList)
{
var id = await GetOrCreateTableId(t.TableName, t.DataBase);
pagedTables.Add(new Dictionary<string, object?>
{
["id"] = id,
["tableName"] = t.TableName,
["displayName"] = t.DisplayName,
["entityName"] = t.EntityName,
["remark"] = t.Remark,
["lastModificationTime"] = DateTime.Now.ToString("yyyy-MM-dd"),
["creationTime"] = DateTime.Now.ToString("yyyy-MM-dd"),
["dataBase"] = t.DataBase
});
}
return new PagingView(pagingSearchInput.Page, pagingSearchInput.Size)
{
Total = filteredTables.Count(),
DataSource = pagedTables,
PageCount = (filteredTables.Count() + pagingSearchInput.Size - 1) / pagingSearchInput.Size
};
}
/// <summary>
/// 获取或创建表的 ID用于兼容性
/// </summary>
private async Task<Guid> GetOrCreateTableId(string? tableName, string? dataBase)
{
if (string.IsNullOrEmpty(tableName) || string.IsNullOrEmpty(dataBase))
return Guid.NewGuid();
var existingTable = await _defaultRepository.FindAsync(t =>
t.TableName == tableName && t.DataBase == dataBase);
return existingTable?.Id ?? Guid.NewGuid();
}
/// <summary>
/// 根据id数组删除
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
public async Task DeleteListAsync(List<Guid> ids)
{
// 查询要删除的表信息,用于删除配置文件
var tablesToDelete = await _defaultRepository.Select.Where(w => ids.Contains(w.Id)).ToListAsync();
_databaseTableService.ClearAllTablesByCache();
//删除子表
await _lowCodeTableInfoRepository.DeleteAsync(w => ids.Contains(w.Low_Code_TableId));
//删除主表
await _defaultRepository.DeleteByIdsAsync(ids);
// 删除对应的配置文件
foreach (var table in tablesToDelete)
{
if (!string.IsNullOrEmpty(table.DataBase) && !string.IsNullOrEmpty(table.TableName))
{
try
{
await _tableMetaConfigService.DeleteConfigAsync(table.DataBase, table.TableName);
}
catch (Exception)
{
// 配置文件可能不存在,忽略异常
}
}
}
}
/// <summary>
/// 同步表(支持多数据源)
/// </summary>
public async Task SynchronizationAsync()
{
// 刷新表结构缓存(从数据库重新加载表结构)
_tableSchemaCache.RefreshCache();
var allTables = _databaseTableService.GetAllTableInfos();
var oldAllTables = await _defaultRepository.ToListAllAsync();
var insertList = new List<LowCodeTable>();
var updateList = new List<LowCodeTable>();
var ids = new List<Guid>();
foreach (var item in allTables)
{
// 修复:同时匹配表名和数据库标识,避免多数据源同名表冲突
var table = oldAllTables.Find(w => w.TableName == item.Name && w.DataBase == item.DataBase);
var id = Guid.NewGuid();
// 获取数据源提供者
var provider = _dataSourceManager.GetProvider(item.DataBase ?? "Admin");
if (table == null)
{
var lowCodeTable = new LowCodeTable
{
Id = id,
DisplayName = item.Comment,
TableName = item.Name,
DataBase = item.DataBase,
Schema = item.Schema,
// 根据命名策略生成实体名
EntityName = provider?.Config.NamingStrategy == CoreEntityNamingStrategy.KeepOriginal
? item.Name
: ConvertToPascalCase(item.Name)
};
insertList.Add(lowCodeTable);
}
else
{
id = table.Id;
table.DataBase = item.DataBase;
table.Schema = item.Schema;
table.EntityName = provider?.Config.NamingStrategy == CoreEntityNamingStrategy.KeepOriginal
? item.Name
: ConvertToPascalCase(item.Name);
updateList.Add(table);
}
ids.Add(id);
}
if (insertList.Count > 0)
{
await _defaultRepository.InsertRangeAsync(insertList);
}
if (updateList.Count > 0)
{
await _defaultRepository.UpdateRangeAsync(updateList);
}
// 同步列信息
foreach (var item in ids)
{
await _lowCodeTableInfoService.SynchronizationColumnByTableIdAsync(item, true);
}
// 注意:这里不需要清除缓存,因为缓存在开始时已经刷新过了
// 清除缓存会导致下次查询重新加载,没有必要
}
/// <summary>
/// 将下划线命名转换为 PascalCase
/// </summary>
private static string ConvertToPascalCase(string input)
{
if (string.IsNullOrEmpty(input))
return input;
var words = input.Split(new[] { '_', '-' }, StringSplitOptions.RemoveEmptyEntries);
var result = new System.Text.StringBuilder();
foreach (var word in words)
{
if (word.Length > 0)
{
result.Append(char.ToUpper(word[0]));
if (word.Length > 1)
{
result.Append(word.Substring(1).ToLower());
}
}
}
return result.ToString();
}
/// <summary>
/// 根据表名和数据库标识变更表配置(推荐使用)
/// </summary>
/// <param name="input">变更参数,包含表配置列表</param>
/// <returns>保存成功返回 true</returns>
/// <exception cref="ArgumentException">参数验证失败或表不存在时抛出</exception>
public async Task<bool> ChangeByTableAsync(TableChangeInput input)
{
// 参数验证
if (input == null)
throw new ArgumentException("参数不能为空", nameof(input));
if (input.Tables == null || input.Tables.Count == 0)
throw new ArgumentException("表配置列表不能为空", nameof(input));
// 清除缓存
_databaseTableService.ClearAllTablesByCache();
// 从内存缓存获取所有表
var allTables = _databaseTableService.GetAllTables();
var updatedCount = 0;
foreach (var tableItem in input.Tables)
{
// 验证参数
if (string.IsNullOrWhiteSpace(tableItem.TableName))
throw new ArgumentException("表名不能为空", nameof(input));
if (string.IsNullOrWhiteSpace(tableItem.DataBase))
throw new ArgumentException("数据库标识不能为空", nameof(input));
// 查找目标表
var targetTable = allTables.FirstOrDefault(t =>
t.TableName == tableItem.TableName && t.DataBase == tableItem.DataBase);
if (targetTable == null)
{
_logger?.LogWarning($"表不存在,跳过更新:表名={tableItem.TableName}, 数据库={tableItem.DataBase}");
continue;
}
// 更新表配置(只更新传递的字段)
if (tableItem.DisplayName != null)
targetTable.DisplayName = tableItem.DisplayName;
if (tableItem.EntityName != null)
targetTable.EntityName = tableItem.EntityName;
if (tableItem.Remark != null)
targetTable.Remark = tableItem.Remark;
// 保存到配置文件
await SaveTableMetaConfigAsync(targetTable);
updatedCount++;
}
if (updatedCount == 0)
{
throw new ArgumentException("没有成功更新任何表配置,请检查表名和数据库标识是否正确", nameof(input));
}
// 清除缓存,确保下次查询时重新加载并合并最新的配置文件
_databaseTableService.ClearAllTablesByCache();
return true;
}
/// <summary>
/// 变更数据
/// </summary>
/// <param name="lowCodeTables"></param>
/// <returns></returns>
[Obsolete("请使用 ChangeByTableAsync 方法,该方法使用 TableName + DataBase 作为查询参数")]
public async Task ChangeAsync(List<LowCodeTable> lowCodeTables)
{
_databaseTableService.ClearAllTablesByCache();
var oldLowCodeTables =
await _defaultRepository.ToListAsync(w => lowCodeTables.Select(w => w.Id).Contains(w.Id));
var updateList = new List<LowCodeTable>();
foreach (var item in lowCodeTables)
{
var lowCodeTable = oldLowCodeTables.Find(w => w.Id == item.Id);
lowCodeTable.DisplayName = item.DisplayName;
lowCodeTable.EntityName = item.EntityName;
lowCodeTable.Remark = item.Remark;
updateList.Add(lowCodeTable);
}
await _defaultRepository.UpdateRangeAsync(updateList);
}
/// <summary>
/// 根据表名和数据库标识查询表单数据(推荐使用)
/// </summary>
/// <param name="input">查询参数,包含表名和数据库标识</param>
/// <returns>表单数据字典</returns>
/// <exception cref="ArgumentException">参数验证失败或表不存在时抛出</exception>
public Task<Dictionary<string, object>> FindFormByTableAsync(TableFormQueryInput input)
{
// 参数验证
if (input == null)
throw new ArgumentException("参数不能为空", nameof(input));
if (string.IsNullOrWhiteSpace(input.TableName))
throw new ArgumentException("表名不能为空", nameof(input));
if (string.IsNullOrWhiteSpace(input.DataBase))
throw new ArgumentException("数据库标识不能为空", nameof(input));
// 从内存缓存获取表信息
var allTables = _databaseTableService.GetAllTables();
var targetTable = allTables.FirstOrDefault(t =>
t.TableName == input.TableName && t.DataBase == input.DataBase);
if (targetTable == null)
throw new ArgumentException($"指定的表不存在:表名={input.TableName}, 数据库={input.DataBase}", nameof(input));
// 填充路径(使用 CodeGenerationService 的方法)
_codeGenerationService.FillPathByLowCodeTable(targetTable);
var res = new Dictionary<string, object>();
res["id"] = Guid.NewGuid().ToString(); // 临时ID
res["form"] = new
{
tableName = targetTable.TableName,
dataBase = targetTable.DataBase,
displayName = targetTable.DisplayName,
entityName = targetTable.EntityName,
remark = targetTable.Remark,
modelPath = targetTable.ModelPath,
servicePath = targetTable.ServicePath,
controllerPath = targetTable.ControllerPath,
clientIndexPath = targetTable.ClientIndexPath,
clientInfoPath = targetTable.ClientInfoPath,
clientServicePath = targetTable.ClientServicePath,
isCover = targetTable.IsCover ?? false
};
res["menu"] = $"views/apps/{targetTable.TableName}s/Index.vue";
res["router"] = $"/apps/{targetTable.TableName?.ToLower()}s";
return Task.FromResult(res);
}
/// <summary>
/// 查询表单数据
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[Obsolete("请使用 FindFormByTableAsync 方法,该方法使用 TableName + DataBase 作为查询参数")]
public async Task<Dictionary<string, object>> FindFormAsync(Guid id)
{
var res = new Dictionary<string, object>();
var form = (await _defaultRepository.FindByIdAsync(id)).NullSafe();
FillPathByLowCodeTable(form);
res[nameof(id)] = id == Guid.Empty ? "" : id;
res[nameof(form)] = form;
res["menu"] = $"views/apps/{form.TableName}s/Index.vue";
res["router"] = $"/apps/{form.TableName.ToLower()}s";
return res;
}
/// <summary>
/// 根据 lowCodeTable 填充路径(支持多数据源)
/// </summary>
private void FillPathByLowCodeTable(LowCodeTable lowCodeTable)
{
var provider = _dataSourceManager.GetProvider(lowCodeTable.DataBase ?? "Admin");
if (provider == null)
{
return;
}
if (string.IsNullOrEmpty(lowCodeTable.ModelPath))
lowCodeTable.ModelPath = _pathResolver.ResolvePath(provider.Config.ModelPathTemplate, provider.Config, lowCodeTable.TableName!);
if (string.IsNullOrEmpty(lowCodeTable.ServicePath))
lowCodeTable.ServicePath = _pathResolver.ResolvePath(provider.Config.ServicePathTemplate, provider.Config, lowCodeTable.TableName!);
if (string.IsNullOrEmpty(lowCodeTable.ControllerPath))
lowCodeTable.ControllerPath = _pathResolver.ResolvePath(provider.Config.ControllerPathTemplate, provider.Config, lowCodeTable.TableName!);
if (string.IsNullOrEmpty(lowCodeTable.ClientIndexPath))
lowCodeTable.ClientIndexPath = _pathResolver.ResolvePath(provider.Config.ClientIndexPathTemplate, provider.Config, lowCodeTable.TableName?.ToLower()!);
if (string.IsNullOrEmpty(lowCodeTable.ClientInfoPath))
lowCodeTable.ClientInfoPath = _pathResolver.ResolvePath(provider.Config.ClientInfoPathTemplate, provider.Config, lowCodeTable.TableName?.ToLower()!);
if (string.IsNullOrEmpty(lowCodeTable.ClientServicePath))
lowCodeTable.ClientServicePath = _pathResolver.ResolvePath(provider.Config.ClientServicePathTemplate, provider.Config, lowCodeTable.TableName?.ToLower()!);
}
/// <summary>
/// 根据表名和数据库标识保存表单数据(推荐使用)
/// </summary>
/// <param name="input">保存参数,包含表名、数据库标识和配置信息</param>
/// <returns>保存成功返回 true</returns>
/// <exception cref="ArgumentException">参数验证失败或表不存在时抛出</exception>
public async Task<bool> SaveFormByTableAsync(TableFormSaveInput input)
{
// 参数验证
if (input == null)
throw new ArgumentException("参数不能为空", nameof(input));
if (string.IsNullOrWhiteSpace(input.TableName))
throw new ArgumentException("表名不能为空", nameof(input));
if (string.IsNullOrWhiteSpace(input.DataBase))
throw new ArgumentException("数据库标识不能为空", nameof(input));
// 清除缓存
_databaseTableService.ClearAllTablesByCache();
// 从内存缓存获取表信息
var allTables = _databaseTableService.GetAllTables();
var targetTable = allTables.FirstOrDefault(t =>
t.TableName == input.TableName && t.DataBase == input.DataBase);
if (targetTable == null)
throw new ArgumentException($"指定的表不存在:表名={input.TableName}, 数据库={input.DataBase}", nameof(input));
// 更新表配置(只更新传递的字段)
if (input.DisplayName != null)
targetTable.DisplayName = input.DisplayName;
if (input.EntityName != null)
targetTable.EntityName = input.EntityName;
if (input.Remark != null)
targetTable.Remark = input.Remark;
if (input.ModelPath != null)
targetTable.ModelPath = input.ModelPath;
if (input.ServicePath != null)
targetTable.ServicePath = input.ServicePath;
if (input.ControllerPath != null)
targetTable.ControllerPath = input.ControllerPath;
if (input.ClientIndexPath != null)
targetTable.ClientIndexPath = input.ClientIndexPath;
if (input.ClientInfoPath != null)
targetTable.ClientInfoPath = input.ClientInfoPath;
if (input.ClientServicePath != null)
targetTable.ClientServicePath = input.ClientServicePath;
if (input.IsCover.HasValue)
targetTable.IsCover = input.IsCover;
// 保存到配置文件
await SaveTableMetaConfigAsync(targetTable);
// 清除缓存,确保下次查询时重新加载并合并最新的配置文件
_databaseTableService.ClearAllTablesByCache();
return true;
}
/// <summary>
/// 保存数据
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
[Transactional]
[Obsolete("请使用 SaveFormByTableAsync 方法,该方法使用 TableName + DataBase 作为查询参数")]
public virtual async Task SaveFormAsync(LowCodeTable form)
{
await _defaultRepository.InsertOrUpdateAsync(form);
}
/// <summary>
/// 保存表元信息到配置文件
/// </summary>
private async Task SaveTableMetaConfigAsync(CoreGenDbTableDto tableDto)
{
if (string.IsNullOrEmpty(tableDto.DataBase) || string.IsNullOrEmpty(tableDto.TableName))
return;
var config = new CoreTableMetaConfig
{
DisplayName = tableDto.DisplayName,
EntityName = tableDto.EntityName,
Remark = tableDto.Remark,
ModelPath = tableDto.ModelPath,
ServicePath = tableDto.ServicePath,
ControllerPath = tableDto.ControllerPath,
ClientIndexPath = tableDto.ClientIndexPath,
ClientInfoPath = tableDto.ClientInfoPath,
ClientServicePath = tableDto.ClientServicePath,
IsCover = tableDto.IsCover ?? false,
Columns = new Dictionary<string, CoreColumnMetaConfig>()
};
// 保存列配置
if (tableDto.TableInfos != null)
{
foreach (var column in tableDto.TableInfos)
{
if (!string.IsNullOrEmpty(column.ColumnName))
{
config.Columns[column.ColumnName] = new CoreColumnMetaConfig
{
DisplayName = column.DisplayName,
Describe = column.Describe,
CsField = column.CsField,
IsTableSelect = column.IsTableSelect,
IsImageId = column.IsImageId,
IsTableColumnShow = column.IsTableColumnShow,
Width = column.Width,
OrderById = column.OrderById
};
}
}
}
await _tableMetaConfigService.SaveConfigAsync(tableDto.DataBase!, tableDto.TableName!, config);
}
}