using System.Collections.Concurrent; using System.Reflection; using System.Runtime.Loader; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using Microsoft.VisualBasic.ApplicationServices; using Newtonsoft.Json; using OfficeOpenXml; using static System.String; namespace NBConfigBuilder; using TableDictionary = SortedDictionary>; /// /// Excel 数据导出器,用于从 Excel 文件导出数据到二进制格式和生成 C# 类文件。 /// public sealed class ExcelExporter { public ExportType ExportType; private readonly string _excelProgramPath; private readonly string _versionFilePath; // public VersionInfo VersionInfo; // 存储 Excel 文件的版本信息。 private readonly Regex _regexName = new Regex("^[a-zA-Z][a-zA-Z0-9_]*$"); // 用于验证 Excel 表名的正则表达式。 private readonly HashSet _loadFiles = [".xlsx", ".xlsm", ".csv"]; // 加载的支持文件扩展名。 private readonly ConcurrentDictionary _excelTables = new ConcurrentDictionary(); // 存储解析后的 Excel 表。 /// /// 导表支持的数据类型集合。 /// public static readonly HashSet ColTypeSet = [ "", "0", "bool", "byte", "short", "ushort", "int", "uint", "long", "ulong", "float", "string", "IntDictionaryConfig", "StringDictionaryConfig", "short[]", "int[]", "long[]", "float[]", "string[]", "uint[]" ]; // 进度报告回调 private Action _progressCallback; static ExcelExporter() { ExcelPackage.License.SetNonCommercialOrganization("Fantasy"); } /// /// 根据指定的 exportType 初始化 ExcelExporter 类的新实例。 /// /// 要执行的导出类型(AllExcel 或 AllExcelIncrement)。 public ExcelExporter(ExportType exportType) { ExportType = exportType; if (App.Config.ExcelVersionPath == null || App.Config.ExcelVersionPath.Trim() == "") { Log.Info($"ExcelVersionFile Can not be empty!"); return; } if (App.Config.ExcelPath == null || App.Config.ExcelPath.Trim() == "") { Log.Info($"ExcelProgramPath Can not be empty!"); return; } _excelProgramPath = FileHelper.GetFullPath(App.Config.ExcelPath); _versionFilePath = FileHelper.GetFullPath(App.Config.ExcelVersionPath); if (App.Config.GenClient) { if (App.Config.ClientJsonPath == null || App.Config.ClientJsonPath.Trim() == "") { Log.Info($"ExcelClientJsonDirectory Can not be empty!"); return; } if (App.Config.ClientPath == null || App.Config.ClientPath.Trim() == "") { Log.Info($"ExcelServerFileDirectory Can not be empty!"); return; } } if (App.Config.GenServer) { if (App.Config.ServerPath == null || App.Config.ServerPath.Trim() == "") { Log.Info($"ExcelServerFileDirectory Can not be empty!"); return; } if (App.Config.ServerJsonPath == null || App.Config.ServerJsonPath.Trim() == "") { Log.Info($"ExcelServerJsonDirectory Can not be empty!"); return; } } switch (ExportType) { case ExportType.AllExcelIncrement: { break; } case ExportType.AllExcel: { if (File.Exists(_versionFilePath)) { File.Delete(_versionFilePath); } break; } } } /// /// 设置进度报告回调 /// /// 进度报告回调函数 public void SetProgressCallback(Action callback) { _progressCallback = callback; } public void Run() { ReportProgress("正在查找配置文件..."); Find(); ReportProgress("正在解析配置表..."); Parsing(); ReportProgress("正在导出JSON数据..."); ExportToJson(); ReportProgress("导出完成"); } private void ReportProgress(string message) { _progressCallback?.Invoke(message); } /// /// 查找配置文件 /// private void Find() { var dir = new DirectoryInfo(_excelProgramPath); var excelFiles = dir.GetFiles("*", SearchOption.AllDirectories); if (excelFiles.Length <= 0) { return; } var tables = new List(); // var tables = new OneToManyList(); foreach (var excelFile in excelFiles) { // 过滤掉非指定后缀的文件 if (!_loadFiles.Contains(excelFile.Extension)) { continue; } var lastIndexOf = excelFile.Name.LastIndexOf(".", StringComparison.Ordinal); if (lastIndexOf < 0) { continue; } var fullName = excelFile.FullName; var excelName = excelFile.Name.Substring(0, lastIndexOf); var path = fullName.Substring(0, fullName.Length - excelFile.Name.Length); // 过滤~开头文件 if (excelName.StartsWith("~", StringComparison.Ordinal)) { continue; } // 如果文件名以#开头,那么这个文件夹下的所有文件都不导出 if (excelName.StartsWith("#", StringComparison.Ordinal)) { continue; } // 如果文件夹名包含#,那么这个文件夹下的所有文件都不导出 if (path.Contains("#", StringComparison.Ordinal)) { continue; } if (!_regexName.IsMatch(excelName)) { Log.Info($"{excelName} 配置文件名非法"); continue; } tables.Add(excelFile); } var generateTasks = new List(); foreach (var table in tables) { var task = Task.Run(() => { var worksheets = LoadExcel(table.FullName); foreach (var worksheet in worksheets) { var tableName = worksheet.Name; if (tableName.Contains("#", StringComparison.Ordinal)) { continue; } ReportProgress($"正在解析配置表: {table.Name} Sheet:{tableName}"); var excelTable = new ExcelTable(worksheet); try { var serverColInfoList = new List(); var clientColInfoList = new List(); for (var col = 3; col <= worksheet.Columns.EndColumn; col++) { // 列名字第一个字符是#不参与导出 var colName = worksheet.GetCellValue(5, col); if (colName.StartsWith("#", StringComparison.Ordinal)) { continue; } // 数值列不参与导出 var numericalCol = worksheet.GetCellValue(3, col); if (numericalCol != "" && numericalCol != "0") { continue; } var serverType = worksheet.GetCellValue(1, col); var clientType = worksheet.GetCellValue(2, col); var isExportServer = !IsNullOrEmpty(serverType) && serverType != "0"; var isExportClient = !IsNullOrEmpty(clientType) && clientType != "0"; if (!isExportServer && !isExportClient) { continue; } if (isExportServer && isExportClient & serverType != clientType) { Log.Info( $"配置表 {tableName} {col} 列 [{colName}] 客户端类型 {clientType} 和 服务端类型 {serverType} 不一致"); continue; } if (!ColTypeSet.Contains(serverType) || !ColTypeSet.Contains(clientType)) { Log.Info( $"配置表 {tableName} {col} 列 [{colName}] 客户端类型 {clientType}, 服务端类型 {serverType} 不合法"); continue; } if (!_regexName.IsMatch(colName)) { Log.Info($"配置表 {tableName} {col} 列 [{colName}] 列名非法"); continue; } serverColInfoList.Add(col); if (isExportClient) { clientColInfoList.Add(col); } } if (clientColInfoList.Count > 0) { excelTable.ClientColInfos.Add(tableName, clientColInfoList); } if (serverColInfoList.Count > 0) { excelTable.ServerColInfos.Add(tableName, serverColInfoList); } } catch (Exception e) { Log.Error($"Config : {tableName}, Name : {tableName}, Error : {e}"); } _excelTables.TryAdd(tableName, excelTable); } }); generateTasks.Add(task); } Task.WaitAll(generateTasks.ToArray()); } /// /// 生成配置文件 /// private void Parsing() { var generateTasks = new List(); foreach ((var tableName, ExcelTable excelTable) in _excelTables) { var task = Task.Run(() => { // 生成cs文件 var writeToClassTask = new List(); var table = excelTable; if (App.Config.GenServer) { writeToClassTask.Add(Task.Run(() => { ReportProgress($"正在生成服务端CS文件: {tableName}"); WriteToClass(table, true); })); } if (App.Config.GenClient) { writeToClassTask.Add(Task.Run(() => { ReportProgress($"正在生成客户端CS文件: {tableName}"); WriteToClass(table, false); })); } Task.WaitAll(writeToClassTask.ToArray()); }); generateTasks.Add(task); } Task.WaitAll(generateTasks.ToArray()); Console.WriteLine("build cs success==="); } #region json private Dictionary>> _clientTableJsonDictionary = new Dictionary>>(); private Dictionary>> _serverTableJsonDictionary = new Dictionary>>(); /// /// 把数据生成json /// private void ExportToJson() { _clientTableJsonDictionary.Clear(); _serverTableJsonDictionary.Clear(); foreach (var (tableName, excelTable) in _excelTables) { // 示例:客户端JSON导出 if (App.Config.GenClient && excelTable.ClientColInfos.Count > 0) { ReportProgress($"正在生成客户端JSON文件: {tableName}"); ExportTableToJson(excelTable, excelTable.ClientColInfos, _clientTableJsonDictionary); } // 示例:服务端JSON导出 if (App.Config.GenServer && excelTable.ServerColInfos.Count > 0) { ReportProgress($"正在生成服务端JSON文件: {tableName}"); ExportTableToJson(excelTable, excelTable.ServerColInfos, _serverTableJsonDictionary); } } if (_clientTableJsonDictionary.Count > 0) { ReportProgress("正在写入客户端JSON文件: configs.json"); var json = JsonConvert.SerializeObject(_clientTableJsonDictionary, Formatting.Indented); File.WriteAllText(Path.Combine(App.Config.ClientJsonPath, $"configs.json"), json); } if (_serverTableJsonDictionary.Count > 0) { ReportProgress("正在写入服务端JSON文件: configs.json"); var json = JsonConvert.SerializeObject(_serverTableJsonDictionary, Formatting.Indented); File.WriteAllText(Path.Combine(App.Config.ServerJsonPath, $"configs.json"), json); } } private void ExportTableToJson(ExcelTable table, TableDictionary colInfos, Dictionary>> dictionary) { var result = new List>(); var worksheet = table.Sheet; foreach (var (fileName, columns) in colInfos) { // 从第7行开始读取数据(前6行为表头) for (int row = 7; row <= worksheet.Rows.EndRow; row++) { // 检查是否为空行 bool isEmptyRow = true; foreach (var col in columns) { var cellValue = worksheet.GetCellValue(row, col); if (!IsNullOrEmpty(cellValue)) { isEmptyRow = false; break; } } // 如果整行都为空,则停止处理 if (isEmptyRow) { break; } var rowData = new Dictionary(); foreach (var col in columns) { var columnName = worksheet.GetCellValue(5, col); var cellValue = worksheet.GetCellValue(row, col); // 可在此处做类型转换 var columnType = GetColumnType(worksheet, col); // 需要实现获取列类型的方法 rowData[columnName] = ConvertCellValue(cellValue, columnType); } result.Add(rowData); } } dictionary[table.Name] = result; } private string GetColumnType(ExcelWorksheet worksheet, int column) { var serverType = worksheet.GetCellValue(1, column); var clientType = worksheet.GetCellValue(2, column); // 根据实际情况确定使用哪个类型,这里简化处理 return !string.IsNullOrEmpty(serverType) && serverType != "0" ? serverType : clientType; } private object ConvertCellValue(string cellValue, string targetType) { if (string.IsNullOrEmpty(cellValue)) { // 返回默认值 if (targetType.EndsWith("[]")) { var elementType = targetType.Substring(0, targetType.Length - 2); return Array.CreateInstance(Type.GetType(elementType) ?? typeof(object), 0); } return GetDefaultValue(targetType); } // 处理数组类型 if (targetType.EndsWith("[]")) { var elementType = targetType.Substring(0, targetType.Length - 2); var values = cellValue.Split(','); switch (elementType) { case "short": return values.Select(v => { short result; return short.TryParse(v, out result) ? result : (short)0; }).ToArray(); case "ushort": return values.Select(v => { ushort result; return ushort.TryParse(v, out result) ? result : (ushort)0; }).ToArray(); case "int": return values.Select(v => { int result; return int.TryParse(v, out result) ? result : 0; }).ToArray(); case "uint": return values.Select(v => { uint result; return uint.TryParse(v, out result) ? result : 0u; }).ToArray(); case "long": return values.Select(v => { long result; return long.TryParse(v, out result) ? result : 0L; }).ToArray(); case "ulong": return values.Select(v => { ulong result; return ulong.TryParse(v, out result) ? result : 0UL; }).ToArray(); case "bool": return values.Select(v => { bool result; return bool.TryParse(v, out result) ? result : false; }).ToArray(); case "string": return values.Select(v => v.Trim()).ToArray(); default: return values; } } // 处理单个值 switch (targetType) { case "short": short shortResult; return short.TryParse(cellValue, out shortResult) ? shortResult : (short)0; case "ushort": ushort ushortResult; return ushort.TryParse(cellValue, out ushortResult) ? ushortResult : (ushort)0; case "int": int intResult; return int.TryParse(cellValue, out intResult) ? intResult : 0; case "uint": uint uintResult; return uint.TryParse(cellValue, out uintResult) ? uintResult : 0u; case "long": long longResult; return long.TryParse(cellValue, out longResult) ? longResult : 0L; case "ulong": ulong ulongResult; return ulong.TryParse(cellValue, out ulongResult) ? ulongResult : 0UL; case "bool": bool boolResult; if (bool.TryParse(cellValue, out boolResult)) { return boolResult; } else { int numericBool; return int.TryParse(cellValue, out numericBool) ? numericBool != 0 : false; } case "string": return cellValue; default: return cellValue; } } private object GetDefaultValue(string type) { switch (type) { case "short": return (short)0; case "ushort": return (ushort)0; case "int": return 0; case "uint": return 0u; case "long": return 0L; case "ulong": return 0UL; case "bool": return false; case "string": return ""; case "short[]": return new short[0]; case "ushort[]": return new ushort[0]; case "int[]": return new int[0]; case "uint[]": return new uint[0]; case "long[]": return new long[0]; case "ulong[]": return new ulong[0]; case "bool[]": return new bool[0]; case "string[]": return new string[0]; default: return null; } } #endregion /// /// 从 Excel 文件加载工作表并返回 ExcelWorksheet 对象。 /// /// 工作表的名称或文件路径。 /// 表示 Excel 工作表的 ExcelWorksheet 对象。 public List LoadExcel(string name) { var workbookWorksheets = ExcelHelper.LoadExcel(name).Workbook.Worksheets; return workbookWorksheets.ToList(); } /// /// 写入到cs /// /// /// private void WriteToClass(ExcelTable table, bool isServer) { var colInfos = isServer ? table.ServerColInfos : table.ClientColInfos; var exportPath = isServer ? App.Config.ServerPath : App.Config.ClientPath; var csNamespace = isServer ? App.Config.ServerNamespace : App.Config.ClientNamespace; if (string.IsNullOrEmpty(csNamespace)) { csNamespace = "NB"; } if (colInfos.Count <= 0) { return; } var excelWorksheet = table.Sheet; var index = 0; var fileBuilder = new StringBuilder(); var colNameSet = new HashSet(); if (colInfos.Count == 0) { return; } var csName = Path.GetFileNameWithoutExtension(colInfos.First().Key)?.Split('_')[0]; foreach (var (tableName, cols) in colInfos) { if (cols == null || cols.Count == 0) { continue; } foreach (var colIndex in cols) { var colName = excelWorksheet.GetCellValue(5, colIndex); if (!colNameSet.Add(colName)) { continue; } string colType; if (isServer) { colType = excelWorksheet.GetCellValue(1, colIndex); if (IsNullOrEmpty(colType) || colType == "0") { colType = excelWorksheet.GetCellValue(2, colIndex); } } else { colType = excelWorksheet.GetCellValue(2, colIndex); } var remarks = excelWorksheet.GetCellValue(4, colIndex); // 解决不同平台换行符不一致的问题 switch (Environment.OSVersion.Platform) { case PlatformID.Win32NT: case PlatformID.Win32S: case PlatformID.Win32Windows: case PlatformID.WinCE: { fileBuilder.Append($"\r\n\t\t[ProtoMember({++index})]\r\n"); break; } default: { fileBuilder.Append($"\n\t\t[ProtoMember({++index})]\n"); break; } } fileBuilder.Append( IsArray(colType, out var t) ? $"\t\tpublic {colType} {colName} {{ get; set; }} = Array.Empty<{t}>(); // {remarks}" : $"\t\tpublic {colType} {colName} {{ get; set; }} // {remarks}"); } } var template = ExcelTemplate.Template; if (fileBuilder.Length > 0) { if (!Directory.Exists(exportPath)) { FileHelper.CreateDirectory(exportPath); } var content = template.Replace("(namespace)", csNamespace) .Replace("(ConfigName)", csName) .Replace("(Fields)", fileBuilder.ToString()); File.WriteAllText(Path.Combine(exportPath, $"{csName}.cs"), content); } } private bool IsArray(string type, out string t) { t = null; var index = type.IndexOf("[]", StringComparison.Ordinal); if (index >= 0) { t = type.Remove(index, 2); } return index >= 0; } }