using MiaoYu.Core.CodeGenerator.Abstractions; using MiaoYu.Core.CodeGenerator.Core; namespace MiaoYu.Core.CodeGenerator.Services; /// /// 代码生成服务 /// [Component] public class CodeGenerationService : ICodeGenerationService, IScopedDependency { private readonly string _webRootPath; private readonly string templateRootPath = "/wwwroot/code_generation/template/"; private readonly string templateRootPath_v4 = "/wwwroot/code_generation/templatev4/"; private readonly string codesRootPath = "/code_generation/codes"; private readonly string zipRootPath = "/code_generation/zip"; //domain 模板文件 private readonly string templateModel = "tempModel.cshtml"; private readonly string templateService = "tempService.cshtml"; private readonly string templateController = "tempController.cshtml"; private readonly string templateServiceJs = "tempClientService.cshtml"; private readonly string templateIndex = "tempClientIndex.cshtml"; private readonly string templateInfo = "tempClientInfo.cshtml"; // 客户端名称 private readonly string projectClientName = "admin-client"; private readonly IDatabaseTableService _databaseTableService; private readonly IRazorViewRender _razorViewRender; private readonly DataSourceManager _dataSourceManager; private readonly PathResolver _pathResolver; private readonly ITableMetaConfigService _tableMetaConfigService; /// /// 构造函数 /// /// 数据库表服务 /// Razor视图渲染器 /// Web宿主环境 /// 数据源管理器 /// 路径解析器 /// 表元信息配置服务 public CodeGenerationService(IDatabaseTableService databaseTableService, IRazorViewRender razorViewRender, IWebHostEnvironment webHostEnvironment, DataSourceManager dataSourceManager, PathResolver pathResolver, ITableMetaConfigService tableMetaConfigService) { _databaseTableService = databaseTableService; _razorViewRender = razorViewRender; _webRootPath = webHostEnvironment.WebRootPath; _dataSourceManager = dataSourceManager; _pathResolver = pathResolver; _tableMetaConfigService = tableMetaConfigService; } /// /// 生成上下文集合 /// /// public PagingView GetGenContextDtos(int page, int size, GenFormDto search) { var PagingView = new PagingView(page, size); var result = new List>(); var query = _databaseTableService.GetAllTablesByCache().AsEnumerable(); if (!string.IsNullOrWhiteSpace(search.TableName)) { query = query.Where(w => w.TableName != null && w.TableName.Contains(search.TableName)); } if (!string.IsNullOrWhiteSpace(search.DataBase)) { query = query.Where(w => w.DataBase == search.DataBase); } var tables = query .Skip((page - 1) * size) .Take(size) .ToList(); foreach (var item in tables) { var dic = new Dictionary(); dic.Add(nameof(item.TableName), item.TableName); dic.Add(nameof(item.Remark), item.Remark); dic.Add(nameof(item.DataBase), item.DataBase); result.Add(dic); } PagingView.Total = query.LongCount(); PagingView.Page = page; PagingView.Size = size; PagingView.DataSource = result; PagingView.PageCount = PagingView.Total / size; return PagingView; } /// /// 获取所有表集合信息 /// /// 表名 /// 数据库标识(强烈建议传入,避免多数据源同名表冲突) /// public GenDbTableDto GetGenContextDtoByTableName(string tableName, string? databaseKey = null) { var query = _databaseTableService.GetAllTables().AsEnumerable(); // 如果指定了数据库,则精确匹配 if (!string.IsNullOrWhiteSpace(databaseKey)) { query = query.Where(w => w.TableName == tableName && w.DataBase == databaseKey); } else { // 警告:如果多个数据源有同名表,将返回第一个匹配的表 // 建议:始终传入 databaseKey 参数以避免歧义 query = query.Where(w => w.TableName == tableName); // 记录警告日志 var matchedTables = query.ToList(); if (matchedTables.Count > 1) { var databases = string.Join(", ", matchedTables.Select(t => t.DataBase)); LogUtil.Log.Warning($"表 '{tableName}' 在多个数据源中存在: [{databases}]。未指定 databaseKey,将使用第一个匹配项。"); } } var genDbTableDto = query.FirstOrDefault(); if (genDbTableDto != null) { FillPathByLowCodeTable(genDbTableDto); } return genDbTableDto; } /// /// 根据 lowCodeTable 填充路径(支持多数据源) /// /// 低代码表配置 /// 填充路径后的低代码表配置 public LowCodeTable FillPathByLowCodeTable(LowCodeTable lowCodeTable) { var provider = _dataSourceManager.GetProvider(lowCodeTable.DataBase ?? DataSourceConstants.Admin); if (provider == null) { LogUtil.Log.Warning($"未找到数据源 {lowCodeTable.DataBase},使用默认Admin配置"); provider = _dataSourceManager.GetProvider(DataSourceConstants.Admin); } var config = provider!.Config; if (string.IsNullOrWhiteSpace(lowCodeTable.ModelPath)) { lowCodeTable.ModelPath = _pathResolver.ResolvePath( config.ModelPathTemplate, config, lowCodeTable.TableName); } if (string.IsNullOrWhiteSpace(lowCodeTable.ServicePath)) { lowCodeTable.ServicePath = _pathResolver.ResolvePath( config.ServicePathTemplate, config, lowCodeTable.TableName); } if (string.IsNullOrWhiteSpace(lowCodeTable.ControllerPath)) { lowCodeTable.ControllerPath = _pathResolver.ResolvePath( config.ControllerPathTemplate, config, lowCodeTable.TableName); } if (string.IsNullOrWhiteSpace(lowCodeTable.ClientIndexPath)) { lowCodeTable.ClientIndexPath = _pathResolver.ResolvePath( config.ClientIndexPathTemplate, config, lowCodeTable.TableName?.ToLower()); } if (string.IsNullOrWhiteSpace(lowCodeTable.ClientInfoPath)) { lowCodeTable.ClientInfoPath = _pathResolver.ResolvePath( config.ClientInfoPathTemplate, config, lowCodeTable.TableName?.ToLower()); } if (string.IsNullOrWhiteSpace(lowCodeTable.ClientServicePath)) { lowCodeTable.ClientServicePath = _pathResolver.ResolvePath( config.ClientServicePathTemplate, config, lowCodeTable.TableName?.ToLower()); } if (string.IsNullOrWhiteSpace(lowCodeTable.MenuPath)) { lowCodeTable.MenuPath = _pathResolver.ResolvePath( config.MenuPathTemplate, config, lowCodeTable.TableName ?? string.Empty); } if (string.IsNullOrWhiteSpace(lowCodeTable.RouterPath)) { lowCodeTable.RouterPath = _pathResolver.ResolvePath( config.RouterPathTemplate, config, lowCodeTable.TableName ?? string.Empty); } return lowCodeTable; } /// /// 获取代码生成上下文 /// /// /// public GenDbTableDto GetGenContextDto(GenFormDto genFormDto) { var tableName = genFormDto.TableName; var tableInfo = GetGenContextDtoByTableName(tableName, genFormDto.DataBase); if (tableInfo == null) return null; tableInfo.Namespace = Tools.GetNamespacePrefix(); return tableInfo; } /// /// 生成model(支持多数据源) /// /// /// public async Task GenModelAsync(GenFormDto genFormDto) { var context = GetGenContextDto(genFormDto); var provider = _dataSourceManager.GetProvider(context.DataBase ?? DataSourceConstants.Admin); var templatePath = provider?.Config.TemplatePath ?? templateRootPath; return ClearSymbol(await _razorViewRender.RenderAsync(templatePath + templateModel, context)); } /// /// 生成service(支持多数据源) /// /// /// public async Task GenServiceAsync(GenFormDto genFormDto) { var context = GetGenContextDto(genFormDto); var provider = _dataSourceManager.GetProvider(context.DataBase ?? DataSourceConstants.Admin); var templatePath = provider?.Config.TemplatePath ?? templateRootPath; return ClearSymbol(await _razorViewRender.RenderAsync(templatePath + templateService, context)); } /// /// 生成controller(支持多数据源) /// /// /// public async Task GenControllerAsync(GenFormDto genFormDto) { var context = GetGenContextDto(genFormDto); var provider = _dataSourceManager.GetProvider(context.DataBase ?? DataSourceConstants.Admin); var templatePath = provider?.Config.TemplatePath ?? templateRootPath; return ClearSymbol(await _razorViewRender.RenderAsync(templatePath + templateController, context)); } /// /// 生成service js(支持多数据源) /// /// /// public async Task GenServiceJsAsync(GenFormDto genFormDto) { var context = GetGenContextDto(genFormDto); var provider = _dataSourceManager.GetProvider(context.DataBase ?? DataSourceConstants.Admin); var templatePath = provider?.Config.TemplatePath ?? templateRootPath; return ClearSymbol(await _razorViewRender.RenderAsync(templatePath + templateServiceJs, context)); } /// /// 生成 index vue(支持多数据源) /// /// /// public async Task GenIndexAsync(GenFormDto genFormDto) { var context = GetGenContextDto(genFormDto); var provider = _dataSourceManager.GetProvider(context.DataBase ?? DataSourceConstants.Admin); var templatePath = provider?.Config.TemplatePath ?? templateRootPath; return ClearSymbol(await _razorViewRender.RenderAsync(templatePath + templateIndex, context)); } /// /// 生成 info vue(支持多数据源) /// /// /// public async Task GenInfoAsync(GenFormDto genFormDto) { var context = GetGenContextDto(genFormDto); var provider = _dataSourceManager.GetProvider(context.DataBase ?? DataSourceConstants.Admin); var templatePath = provider?.Config.TemplatePath ?? templateRootPath; return ClearSymbol(await _razorViewRender.RenderAsync(templatePath + templateInfo, context)); } /// /// 获取代码 /// /// /// public async Task GetCodeByTypeAndTableNameAsync(GenFormDto genFormDto) { return genFormDto.Type switch { "MiaoYu.Models" => await GenModelAsync(genFormDto), //"MiaoYu.Repository.DbSet" => await this.CreateRepositoryDbSetAsync(), "MiaoYu.Services.Admin" => await GenServiceAsync(genFormDto), "MiaoYu.Controllers.Admin" => await GenControllerAsync(genFormDto), "Client.Index" => await GenIndexAsync(genFormDto), "Client.Info" => await GenInfoAsync(genFormDto), "Client.Service" => await GenServiceJsAsync(genFormDto), _ => string.Empty }; } /// /// 创建所有代码文件 /// /// /// public async Task CreateAllCodeFilesAsync(GenFormDto genFormDto) { var tables = _databaseTableService.GetAllTablesByCache(); foreach (var item in tables) { genFormDto.TableName = item.TableName; genFormDto.DataBase = item.DataBase; await CreateCodeFilesAsync(genFormDto); // 保存元信息到配置文件 await SaveTableMetaConfigAsync(item); await Task.Delay(25); } return true; } /// /// 获取下载代码信息 /// /// /// public async Task<(byte[] codeBytes, string contentType, string fileName)> DownloadAsync(GenFormDto genFormDto) { var fileName = FindCodeFileClassName(genFormDto); var contentType = Tools.GetFileContentType[".cs"]; if (fileName == "Index.vue" || fileName == "Info.vue") { contentType = Tools.GetFileContentType[".txt"]; } return (Encoding.UTF8.GetBytes(await GetCodeByTypeAndTableNameAsync(genFormDto)), contentType, fileName); } /// /// 根据类型下载所有代码 /// /// /// public async Task<(byte[] codeBytes, string contentType, string fileName)> DownloadAllAsync(GenFormDto genFormDto) { var isViews = genFormDto.Type == "Client.Index" || genFormDto.Type == "Client.Info"; var success = await CreateAllCodeFilesAsync(genFormDto); if (!success) LogUtil.Log.Warning("无法下载,代码创建失败!"); string path; string zipPath; if (isViews) { path = $"{_webRootPath}{codesRootPath}/pages"; zipPath = $"{_webRootPath}{zipRootPath}"; if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } if (!Directory.Exists(zipPath)) { Directory.CreateDirectory(zipPath); } zipPath += "/pages.zip"; } else { path = $"{_webRootPath}{codesRootPath}/{genFormDto.Type}"; zipPath = $"{_webRootPath}{zipRootPath}"; if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } if (!Directory.Exists(zipPath)) { Directory.CreateDirectory(zipPath); } zipPath += $"/{genFormDto.Type}.zip"; } //开始压缩 var zip = new MiaoYu.Core.Zips.Zip(path, zipPath); var bytes = await File.ReadAllBytesAsync(zipPath); //删除文件 if (File.Exists(zipPath)) File.Delete(zipPath); if (Directory.Exists(path)) Directory.Delete(path, true); return (bytes, Tools.GetFileContentType[".zip"], $"{(isViews ? "pages" : genFormDto.Type)}.zip"); } /// /// 创建数据库字典文件 /// /// public (byte[] excel, string dataBase) CreateDataDictionary() { var tables = _databaseTableService.GetAllTablesByCache(); var workbook = new XSSFWorkbook(); var dataBaseName = _databaseTableService.GetDatabaseName(); foreach (var item in tables) { var sheet = workbook.CreateSheet(item.TableName + (string.IsNullOrWhiteSpace(item.Remark) ? "" : "_" + item.Remark)); var i = 0; #region 配置表头 var rowTitle = sheet.CreateRow(i); rowTitle.CreateCell(0).SetCellValue("表空间"); sheet.SetColumnWidth(0, 20 * 256); rowTitle.CreateCell(1).SetCellValue("表名"); sheet.SetColumnWidth(1, 20 * 256); rowTitle.CreateCell(2).SetCellValue("表描述"); sheet.SetColumnWidth(2, 20 * 256); rowTitle.CreateCell(3).SetCellValue("字段"); sheet.SetColumnWidth(3, 20 * 256); rowTitle.CreateCell(4).SetCellValue("字段描述"); sheet.SetColumnWidth(4, 20 * 256); rowTitle.CreateCell(5).SetCellValue("是否主键"); sheet.SetColumnWidth(5, 20 * 256); rowTitle.CreateCell(6).SetCellValue("是否自增"); sheet.SetColumnWidth(6, 20 * 256); rowTitle.CreateCell(7).SetCellValue("可否为 Null"); sheet.SetColumnWidth(7, 20 * 256); rowTitle.CreateCell(8).SetCellValue("数据库类型"); sheet.SetColumnWidth(8, 20 * 256); rowTitle.CreateCell(9).SetCellValue("C#类型"); sheet.SetColumnWidth(9, 20 * 256); rowTitle.CreateCell(10).SetCellValue("数据长度"); sheet.SetColumnWidth(10, 20 * 256); #endregion //组装数据 foreach (var tableInfo in item.TableInfos) { i++; var index = item.TableInfos.IndexOf(tableInfo); var row = sheet.CreateRow(i); //row.CreateCell(0).SetCellValue(item.Schema); row.CreateCell(1).SetCellValue(item.TableName); row.CreateCell(2).SetCellValue(item.Remark); row.CreateCell(3).SetCellValue(tableInfo.ColumnName); row.CreateCell(4).SetCellValue(tableInfo.Describe); row.CreateCell(5).SetCellValue(tableInfo.IsPrimary ? "是" : "否"); row.CreateCell(6).SetCellValue(tableInfo.IsIdentity ? "是" : "否"); row.CreateCell(7).SetCellValue(tableInfo.IsNullable ? "是" : "否"); row.CreateCell(8).SetCellValue(tableInfo.DatabaseColumnType); row.CreateCell(9).SetCellValue(tableInfo.CsType); row.CreateCell(10).SetCellValue(tableInfo.MaxLength ?? 0); } } //填充byte using var ms = new MemoryStream(); workbook.Write(ms); return (ms.ToArray(), dataBaseName); } /// /// 自动导入文件到项目 /// /// /// public async Task AutoImprotProjectAsync(GenFormDto genFormDto) { var context = GetGenContextDto(genFormDto); if (context == null) { LogUtil.Log.Warning($"找不到数据表: {genFormDto.TableName} (数据库: {genFormDto.DataBase})"); return; } //获取表路径信息 var tables = _databaseTableService.GetAllTablesByCache(); var tableInfo = tables.FirstOrDefault(w => w.TableName == genFormDto.TableName && w.DataBase == genFormDto.DataBase); var fileTyps = Enum.GetValues(); foreach (var fileType in fileTyps) { var (filePath, oldName, replaceName) = GetFileAbsolutelyPath( genFormDto.TableName, fileType, genFormDto.DataBase); await SaveToFileAsync(genFormDto.TableName, fileType, filePath, oldName, replaceName); } } /// /// 获取所有数据库列表 /// /// public List GetAllDataSources() { var providers = _dataSourceManager.GetAllProviders() .OrderBy(p => p.Config.Order) .ToList(); return providers.Select(p => new DataSourceDto { Key = p.Config.DatabaseKey, DisplayName = p.Config.DisplayName }).ToList(); } #region 私有方法 /// /// 清除多余符号 /// /// /// private string ClearSymbol(StringBuilder code) { return code .ToString() .Replace("
", "")
                .Replace("
", "") .Trim(); } /// /// 创建代码文件 /// /// /// private async Task CreateCodeFilesAsync(GenFormDto genFormDto) { var path = $"{_webRootPath}{codesRootPath}"; if (genFormDto.Type == "Client.Index" || genFormDto.Type == "Client.Info") { path += $"/pages"; // if (!Directory.Exists(path)) Directory.CreateDirectory(path); path += $"/{genFormDto.TableName}"; if (!Directory.Exists(path)) Directory.CreateDirectory(path); //Index var codeString = await GenIndexAsync(genFormDto); await File.WriteAllTextAsync($"{path}/Index.vue", codeString, Encoding.UTF8); //Info codeString = await GenInfoAsync(genFormDto); await File.WriteAllTextAsync($"{path}/Info.vue", codeString, Encoding.UTF8); return path; } // path = $"{_webRootPath}{codesRootPath}/{genFormDto.Type}"; var className = FindCodeFileClassName(genFormDto); var code = await GetCodeByTypeAndTableNameAsync(genFormDto); // if (!Directory.Exists(path)) Directory.CreateDirectory(path); await File.WriteAllTextAsync($"{path}/{className}", code, Encoding.UTF8); return path; } /// /// 获取代码文件名称 /// /// /// private string FindCodeFileClassName(GenFormDto genFormDto) { var provider = _dataSourceManager.GetProvider(genFormDto.DataBase ?? DataSourceConstants.Admin); var entityName = _pathResolver.GetEntityName(genFormDto.TableName, provider.Config); return genFormDto.Type switch { "MiaoYu.Models" => $"{entityName}.cs", "MiaoYu.Services.Admin" => $"{entityName}Service.cs", "MiaoYu.Controllers.Admin" => $"{entityName}Controller.cs", "Client.Index" => $"Index.vue", "Client.Info" => $"Info.vue", "Client.Service" => $"{entityName}Service.ts", _ => string.Empty }; } /// /// 获取要生成文件的绝对路径 /// /// /// /// 数据库标识(可选) /// private (string, string, string) GetFileAbsolutelyPath(string tableName, FileTypeEnum type, string? databaseKey = null) { var replaceName = string.Empty; var oldFileName = string.Empty; var dto = new GenFormDto() { TableName = tableName, Type = GetEnumDescription(type), DataBase = databaseKey }; var provider = _dataSourceManager.GetProvider(databaseKey ?? DataSourceConstants.Admin); var entityName = _pathResolver.GetEntityName(tableName, provider.Config); var entityNameWithPath = provider.Config.UsesPluralPath ? entityName + "s" : entityName; var fileName = FindCodeFileClassName(dto); var path = string.Empty; //获取表路径信息 var tableInfo = GetGenContextDtoByTableName(tableName, databaseKey); switch (type) { case FileTypeEnum.Model: path = provider.Config.UsesPluralPath ? $"{tableInfo.ModelPath}s" : tableInfo.ModelPath; break; case FileTypeEnum.Service: path = provider.Config.UsesPluralPath ? $"{tableInfo.ServicePath}s" : tableInfo.ServicePath; break; case FileTypeEnum.Controller: path = provider.Config.UsesPluralPath ? $"{tableInfo.ControllerPath}s" : tableInfo.ControllerPath; break; case FileTypeEnum.ClientIndex: path = tableInfo.ClientIndexPath + $"/"; break; case FileTypeEnum.ClientInfo: path = tableInfo.ClientInfoPath + $"/"; break; case FileTypeEnum.ClientService: path = tableInfo.ClientServicePath + $"/"; break; } var fileDirPath = Path.Combine(path); if (!Directory.Exists(fileDirPath)) { Directory.CreateDirectory(fileDirPath); } // 组合成完整路劲 var filePath = $"{fileDirPath}/{fileName}"; // 判断是否覆盖文件 if (!(tableInfo.IsCover ?? false)) { // 如果文件已存在 加尾缀 重新创建文件夹 if (File.Exists(filePath)) { oldFileName = FindCodeFileClassName(dto); replaceName = $"{oldFileName}{DateTime.Now.ToString("yyyyMMddHHmmss")}"; filePath = $"{fileDirPath}/{replaceName}"; } } return (filePath, oldFileName, replaceName); } /// /// 保存到文件 /// /// /// /// /// /// /// 数据库标识(可选) /// private async Task SaveToFileAsync(string tableName, FileTypeEnum type, string filePath, string oldName, string replaceName, string? databaseKey = null) { var dto = new GenFormDto() { TableName = tableName, Type = GetEnumDescription(type), DataBase = databaseKey }; var codeString = await GetCodeByTypeAndTableNameAsync(dto); if (!string.IsNullOrWhiteSpace(replaceName) && !string.IsNullOrWhiteSpace(oldName)) { if (type == FileTypeEnum.Model || type == FileTypeEnum.Service || type == FileTypeEnum.Controller) { codeString = codeString.Replace(oldName, replaceName); } } await Task.Delay(500); await File.WriteAllTextAsync(filePath, codeString, Encoding.UTF8); } /// /// 获取枚举上的描述特性 /// /// /// private string GetEnumDescription(FileTypeEnum type) { return type.GetType().GetField(Enum.GetName(type)).GetCustomAttribute().Description; } /// /// 保存表元信息到配置文件 /// /// 表信息 private async Task SaveTableMetaConfigAsync(GenDbTableDto tableDto) { if (string.IsNullOrEmpty(tableDto.DataBase) || string.IsNullOrEmpty(tableDto.TableName)) { return; } var config = new TableMetaConfig { 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() }; // 保存列配置 if (tableDto.TableInfos != null) { foreach (var column in tableDto.TableInfos) { if (!string.IsNullOrEmpty(column.ColumnName)) { config.Columns[column.ColumnName] = new ColumnMetaConfig { DisplayName = column.DisplayName, Describe = column.Describe, CsField = column.CsField, IsTableSelect = column.IsTableSelect, IsImageId = column.IsImageId, IsTableColumnShow = column.IsTableColumnShow }; } } } await _tableMetaConfigService.SaveConfigAsync(tableDto.DataBase, tableDto.TableName, config); } #endregion } public enum FileTypeEnum { [Description("MiaoYu.Models")] Model, [Description("MiaoYu.Services.Admin")] Service, [Description("MiaoYu.Controllers.Admin")] Controller, [Description("Client.Index")] ClientIndex, [Description("Client.Info")] ClientInfo, [Description("Client.Service")] ClientService }