Files
Fishing2Server/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelExporter.cs
2025-10-09 17:55:51 +08:00

745 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 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<string, List<int>>;
/// <summary>
/// Excel 数据导出器,用于从 Excel 文件导出数据到二进制格式和生成 C# 类文件。
/// </summary>
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<string> _loadFiles = [".xlsx", ".xlsm", ".csv"]; // 加载的支持文件扩展名。
private readonly ConcurrentDictionary<string, ExcelTable> _excelTables =
new ConcurrentDictionary<string, ExcelTable>(); // 存储解析后的 Excel 表。
/// <summary>
/// 导表支持的数据类型集合。
/// </summary>
public static readonly HashSet<string> ColTypeSet =
[
"", "0", "bool", "byte", "short", "ushort", "int", "uint", "long", "ulong", "float", "string",
"IntDictionaryConfig", "StringDictionaryConfig",
"short[]", "int[]", "long[]", "float[]", "string[]", "uint[]"
];
// 进度报告回调
private Action<string> _progressCallback;
static ExcelExporter()
{
ExcelPackage.License.SetNonCommercialOrganization("Fantasy");
}
/// <summary>
/// 根据指定的 exportType 初始化 ExcelExporter 类的新实例。
/// </summary>
/// <param name="exportType">要执行的导出类型AllExcel 或 AllExcelIncrement。</param>
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;
}
}
}
/// <summary>
/// 设置进度报告回调
/// </summary>
/// <param name="callback">进度报告回调函数</param>
public void SetProgressCallback(Action<string> callback)
{
_progressCallback = callback;
}
public void Run()
{
ReportProgress("正在查找配置文件...");
Find();
ReportProgress("正在解析配置表...");
Parsing();
ReportProgress("正在导出JSON数据...");
ExportToJson();
ReportProgress("导出完成");
}
private void ReportProgress(string message)
{
_progressCallback?.Invoke(message);
}
/// <summary>
/// 查找配置文件
/// </summary>
private void Find()
{
var dir = new DirectoryInfo(_excelProgramPath);
var excelFiles = dir.GetFiles("*", SearchOption.AllDirectories);
if (excelFiles.Length <= 0)
{
return;
}
var tables = new List<FileInfo>();
// var tables = new OneToManyList<string, ExportInfo>();
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<Task>();
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<int>();
var clientColInfoList = new List<int>();
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());
}
/// <summary>
/// 生成配置文件
/// </summary>
private void Parsing()
{
var generateTasks = new List<Task>();
foreach ((var tableName, ExcelTable excelTable) in _excelTables)
{
var task = Task.Run(() =>
{
// 生成cs文件
var writeToClassTask = new List<Task>();
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<string, List<Dictionary<string, object>>> _clientTableJsonDictionary =
new Dictionary<string, List<Dictionary<string, object>>>();
private Dictionary<string, List<Dictionary<string, object>>> _serverTableJsonDictionary =
new Dictionary<string, List<Dictionary<string, object>>>();
/// <summary>
/// 把数据生成json
/// </summary>
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<string, List<Dictionary<string, object>>> dictionary)
{
var result = new List<Dictionary<string, object>>();
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<string, object>();
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
/// <summary>
/// 从 Excel 文件加载工作表并返回 ExcelWorksheet 对象。
/// </summary>
/// <param name="name">工作表的名称或文件路径。</param>
/// <returns>表示 Excel 工作表的 ExcelWorksheet 对象。</returns>
public List<ExcelWorksheet> LoadExcel(string name)
{
var workbookWorksheets = ExcelHelper.LoadExcel(name).Workbook.Worksheets;
return workbookWorksheets.ToList();
}
/// <summary>
/// 写入到cs
/// </summary>
/// <param name="table"></param>
/// <param name="isServer"></param>
private void WriteToClass(ExcelTable table, bool isServer)
{
var colInfos = isServer ? table.ServerColInfos : table.ClientColInfos;
var exportPath = isServer ? App.Config.ServerPath : App.Config.ClientPath;
if (colInfos.Count <= 0)
{
return;
}
var excelWorksheet = table.Sheet;
var index = 0;
var fileBuilder = new StringBuilder();
var colNameSet = new HashSet<string>();
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)", "Fantasy")
.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;
}
}