511 lines
18 KiB
C#
511 lines
18 KiB
C#
/*
|
||
* @Author: 白哉
|
||
* @Description:
|
||
* @Date: 2025年12月02日 星期二 15:12:27
|
||
* @Modify:
|
||
*/
|
||
|
||
|
||
using System.Text;
|
||
using OfficeOpenXml;
|
||
|
||
|
||
namespace ExcelConfig
|
||
{
|
||
public class HeadInfo
|
||
{
|
||
public string FieldDesc { get; set; }
|
||
public string FieldName { get; set; }
|
||
public string FieldType { get; set; }
|
||
public int FieldIndex { get; set; }
|
||
|
||
public HeadInfo(string desc, string name, string type, int index)
|
||
{
|
||
this.FieldDesc = desc;
|
||
this.FieldName = name;
|
||
this.FieldType = type;
|
||
this.FieldIndex = index;
|
||
}
|
||
}
|
||
|
||
|
||
public class Table
|
||
{
|
||
public int Index { get; set; }
|
||
public Dictionary<string, HeadInfo?> HeadInfos { get; set; } = new Dictionary<string, HeadInfo?>();
|
||
}
|
||
|
||
|
||
public static class ExcelExporter
|
||
{
|
||
private static string templateMain = "";
|
||
private static string templatePartial = "";
|
||
|
||
private const string ClassDir = "GenerateCS";
|
||
private const string TargetClassDir = "../Unity/Assets/Scripts/TH1_Config/GenerateCS";
|
||
private const string ClassPartialDir = "GenerateCSPartial";
|
||
private const string excelDir = "./Excel/";
|
||
private const string BytesDir = "GenerateBytes";
|
||
private const string TargetBytesDir = "../Unity/Assets/Resources/ExcelConfig/GenerateBytes";
|
||
|
||
private static Dictionary<string, Table> tables = new Dictionary<string, Table>();
|
||
private static Dictionary<string, ExcelPackage> packages = new Dictionary<string, ExcelPackage>();
|
||
|
||
private static Table GetTable(string protoName)
|
||
{
|
||
if (!tables.TryGetValue(protoName, out var table))
|
||
{
|
||
table = new Table();
|
||
tables[protoName] = table;
|
||
}
|
||
|
||
return table;
|
||
}
|
||
|
||
public static ExcelPackage GetPackage(string filePath)
|
||
{
|
||
if (!packages.TryGetValue(filePath, out var package))
|
||
{
|
||
using Stream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||
package = new ExcelPackage(stream);
|
||
packages[filePath] = package;
|
||
}
|
||
|
||
return package;
|
||
}
|
||
|
||
// 开始导CS
|
||
public static void ExportCS()
|
||
{
|
||
try
|
||
{
|
||
templateMain = File.ReadAllText("ExportTemplate_Main.txt");
|
||
templatePartial = File.ReadAllText("ExportTemplate_Partial.txt");
|
||
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
|
||
|
||
// 清理旧的生成目录
|
||
CleanDirectory(ClassDir);
|
||
CleanDirectory(ClassPartialDir);
|
||
|
||
// 扫描所有Excel文件,收集表头信息
|
||
List<string> files = FileHelper.GetAllFiles(excelDir, "*.xlsx");
|
||
foreach (string path in files)
|
||
{
|
||
string fileName = Path.GetFileName(path);
|
||
if (!fileName.EndsWith(".xlsx") || fileName.StartsWith("~$") || fileName.Contains("#"))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
|
||
|
||
ExcelPackage p = GetPackage(Path.GetFullPath(path));
|
||
|
||
string protoName = fileNameWithoutExtension;
|
||
if (fileNameWithoutExtension.Contains('_'))
|
||
{
|
||
protoName = fileNameWithoutExtension.Substring(0, fileNameWithoutExtension.LastIndexOf('_'));
|
||
}
|
||
|
||
Table table = GetTable(protoName);
|
||
ExportExcelClass(p, protoName, table);
|
||
}
|
||
|
||
// 第二步:生成C#类文件
|
||
foreach (var kv in tables)
|
||
{
|
||
ExportClass(kv.Key, kv.Value.HeadInfos);
|
||
}
|
||
|
||
Console.WriteLine("ExcelCS 导出成功!");
|
||
Console.WriteLine($"生成的CS文件位于: {Path.GetFullPath(ClassDir)}");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Console.WriteLine($"导出失败: {e}");
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
tables.Clear();
|
||
foreach (var kv in packages)
|
||
{
|
||
kv.Value.Dispose();
|
||
}
|
||
packages.Clear();
|
||
}
|
||
}
|
||
|
||
// 开始导Bytes
|
||
public static void ExportBytes()
|
||
{
|
||
try
|
||
{
|
||
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
|
||
|
||
// 清理旧的生成目录
|
||
CleanDirectory(BytesDir);
|
||
|
||
// 扫描所有Excel文件,收集表头信息
|
||
List<string> files = FileHelper.GetAllFiles(excelDir, "*.xlsx");
|
||
foreach (string path in files)
|
||
{
|
||
string fileName = Path.GetFileName(path);
|
||
if (!fileName.EndsWith(".xlsx") || fileName.StartsWith("~$") || fileName.Contains("#"))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
|
||
|
||
ExcelPackage p = GetPackage(Path.GetFullPath(path));
|
||
|
||
string protoName = fileNameWithoutExtension;
|
||
if (fileNameWithoutExtension.Contains('_'))
|
||
{
|
||
protoName = fileNameWithoutExtension.Substring(0, fileNameWithoutExtension.LastIndexOf('_'));
|
||
}
|
||
|
||
ExportClassBytes(protoName, p);
|
||
}
|
||
|
||
Console.WriteLine("ExcelBytes 导出成功!");
|
||
Console.WriteLine($"生成的二进制文件位于: {Path.GetFullPath(BytesDir)}");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Console.WriteLine($"导出失败: {e}");
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
tables.Clear();
|
||
foreach (var kv in packages)
|
||
{
|
||
kv.Value.Dispose();
|
||
}
|
||
packages.Clear();
|
||
}
|
||
}
|
||
|
||
// 文件移动
|
||
public static void MoveFile()
|
||
{
|
||
try
|
||
{
|
||
// 清空并拷贝CS文件
|
||
if (Directory.Exists(ClassDir))
|
||
{
|
||
CleanDirectory(TargetClassDir);
|
||
CopyDirectory(ClassDir, TargetClassDir);
|
||
Console.WriteLine($"成功拷贝CS文件: {ClassDir} -> {TargetClassDir}");
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine($"警告: 源目录不存在: {ClassDir}");
|
||
}
|
||
|
||
// 清空并拷贝Bytes文件
|
||
if (Directory.Exists(BytesDir))
|
||
{
|
||
CleanDirectory(TargetBytesDir);
|
||
CopyDirectory(BytesDir, TargetBytesDir);
|
||
Console.WriteLine($"成功拷贝Bytes文件: {BytesDir} -> {TargetBytesDir}");
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine($"警告: 源目录不存在: {BytesDir}");
|
||
}
|
||
|
||
Console.WriteLine("文件移动完成!");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Console.WriteLine($"文件移动失败: {e}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
private static void CopyDirectory(string sourceDir, string targetDir)
|
||
{
|
||
if (!Directory.Exists(targetDir))
|
||
{
|
||
Directory.CreateDirectory(targetDir);
|
||
}
|
||
|
||
// 拷贝所有文件
|
||
string[] files = Directory.GetFiles(sourceDir);
|
||
foreach (string file in files)
|
||
{
|
||
string fileName = Path.GetFileName(file);
|
||
string targetFile = Path.Combine(targetDir, fileName);
|
||
File.Copy(file, targetFile, true);
|
||
}
|
||
|
||
// 递归拷贝子目录
|
||
string[] subDirs = Directory.GetDirectories(sourceDir);
|
||
foreach (string subDir in subDirs)
|
||
{
|
||
string dirName = Path.GetFileName(subDir);
|
||
string targetSubDir = Path.Combine(targetDir, dirName);
|
||
CopyDirectory(subDir, targetSubDir);
|
||
}
|
||
}
|
||
|
||
private static void CleanDirectory(string dir)
|
||
{
|
||
if (Directory.Exists(dir))
|
||
{
|
||
Directory.Delete(dir, true);
|
||
}
|
||
Directory.CreateDirectory(dir);
|
||
}
|
||
|
||
|
||
#region 导出class
|
||
|
||
static void ExportExcelClass(ExcelPackage p, string name, Table table)
|
||
{
|
||
ExportSheetClass(p.Workbook.Worksheets.First(), table);
|
||
}
|
||
|
||
static void ExportSheetClass(ExcelWorksheet worksheet, Table table)
|
||
{
|
||
const int row = 2;
|
||
|
||
if (worksheet.Name.StartsWith("#"))
|
||
{
|
||
return;
|
||
}
|
||
|
||
for (int col = 3; col <= worksheet.Dimension.End.Column; ++col)
|
||
{
|
||
string fieldName = worksheet.Cells[row + 2, col].Text.Trim();
|
||
if (string.IsNullOrEmpty(fieldName))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (table.HeadInfos.ContainsKey(fieldName))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
string fieldCheck = worksheet.Cells[row, col].Text.Trim();
|
||
if (fieldCheck.Contains("#"))
|
||
{
|
||
table.HeadInfos[fieldName] = null;
|
||
continue;
|
||
}
|
||
|
||
string fieldDesc = worksheet.Cells[row + 1, col].Text.Trim();
|
||
string fieldType = worksheet.Cells[row + 3, col].Text.Trim();
|
||
|
||
table.HeadInfos[fieldName] = new HeadInfo(fieldDesc, fieldName, fieldType, ++table.Index);
|
||
}
|
||
}
|
||
|
||
static void ExportClass(string protoName, Dictionary<string, HeadInfo?> classField)
|
||
{
|
||
if (!Directory.Exists(ClassDir))
|
||
{
|
||
Directory.CreateDirectory(ClassDir);
|
||
}
|
||
|
||
if (!Directory.Exists(ClassPartialDir))
|
||
{
|
||
Directory.CreateDirectory(ClassPartialDir);
|
||
}
|
||
|
||
StringBuilder sbFields = new StringBuilder();
|
||
StringBuilder sbConstructor = new StringBuilder();
|
||
|
||
foreach ((string _, HeadInfo? headInfo) in classField)
|
||
{
|
||
if (headInfo == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// 生成字段
|
||
sbFields.Append($"\t\t/// <summary>{headInfo.FieldDesc}</summary>\n");
|
||
sbFields.Append($"\t\t[MemoryPackInclude]\n");
|
||
string fieldType = headInfo.FieldType;
|
||
sbFields.Append($"\t\tpublic {fieldType} {headInfo.FieldName} {{ get; set; }}\n");
|
||
|
||
// 生成构造函数体
|
||
int colIndex = headInfo.FieldIndex + 2; // 列索引从3开始,FieldIndex从1开始,所以+2
|
||
sbConstructor.Append($"\t\t\t{headInfo.FieldName} = ({fieldType})ExcelExporter.ConvertValue(cells[row, {colIndex}].Text.Trim(), typeof({fieldType}));\n");
|
||
}
|
||
|
||
// 生成主文件 (GenerateCS目录)
|
||
string mainExportPath = Path.Combine(ClassDir, $"{protoName}.cs");
|
||
using (FileStream txt = new FileStream(mainExportPath, FileMode.Create))
|
||
using (StreamWriter sw = new StreamWriter(txt))
|
||
{
|
||
string mainContent = templateMain
|
||
.Replace("(ConfigName)", protoName)
|
||
.Replace("(Fields)", sbFields.ToString());
|
||
sw.Write(mainContent);
|
||
}
|
||
|
||
// 生成Partial文件 (GenerateCSPartial目录)
|
||
string partialExportPath = Path.Combine(ClassPartialDir, $"{protoName}.cs");
|
||
using (FileStream txt = new FileStream(partialExportPath, FileMode.Create))
|
||
using (StreamWriter sw = new StreamWriter(txt))
|
||
{
|
||
string partialContent = templatePartial
|
||
.Replace("(ConfigName)", protoName)
|
||
.Replace("(ConstructorBody)", sbConstructor.ToString());
|
||
sw.Write(partialContent);
|
||
}
|
||
}
|
||
|
||
static void ExportClassBytes(string name, ExcelPackage p)
|
||
{
|
||
try
|
||
{
|
||
// 1. 构造完整类型名 (包含命名空间)
|
||
string categoryTypeName = $"ExcelConfig.{name}Category";
|
||
|
||
// 2. 从所有已加载的程序集中查找类型
|
||
Type? categoryType = AppDomain.CurrentDomain.GetAssemblies()
|
||
.SelectMany(a => a.GetTypes())
|
||
.FirstOrDefault(t => t.FullName == categoryTypeName);
|
||
|
||
if (categoryType == null)
|
||
{
|
||
Console.WriteLine($"警告: 未找到类型 {categoryTypeName},跳过序列化");
|
||
return;
|
||
}
|
||
|
||
// 3. 创建实例并序列化
|
||
object? categoryInstance = Activator.CreateInstance(categoryType);
|
||
if (categoryInstance is ExcelConfigBase excelConfig)
|
||
{
|
||
excelConfig.Serialization(p);
|
||
|
||
// 4. 写入文件
|
||
string exportPath = Path.Combine(BytesDir, $"{name}.bytes");
|
||
File.WriteAllBytes(exportPath, excelConfig.GetData());
|
||
|
||
Console.WriteLine($"成功生成: {exportPath}");
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine($"警告: {categoryTypeName} 未实现 IExcelConfig 接口");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"导出 {name} 失败: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
public static object ConvertValue(string value, Type targetType)
|
||
{
|
||
value = value?.Trim() ?? string.Empty;
|
||
|
||
// 处理数组类型
|
||
if (targetType.IsArray)
|
||
{
|
||
return ConvertArrayValue(value, targetType);
|
||
}
|
||
|
||
// 处理基础类型
|
||
return ConvertSingleValue(value, targetType);
|
||
}
|
||
|
||
private static object ConvertArrayValue(string value, Type arrayType)
|
||
{
|
||
Type? elementType = arrayType.GetElementType();
|
||
if (elementType == null)
|
||
{
|
||
throw new InvalidOperationException("无法获取数组的元素类型");
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(value))
|
||
{
|
||
return Array.CreateInstance(elementType, 0);
|
||
}
|
||
|
||
string[] parts = value.Split([','], StringSplitOptions.RemoveEmptyEntries);
|
||
Array array = Array.CreateInstance(elementType, parts.Length);
|
||
|
||
for (int i = 0; i < parts.Length; i++)
|
||
{
|
||
array.SetValue(ConvertSingleValue(parts[i].Trim(), elementType), i);
|
||
}
|
||
|
||
return array;
|
||
}
|
||
|
||
private static object ConvertSingleValue(string value, Type targetType)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(value))
|
||
{
|
||
if (targetType == typeof(string))
|
||
return string.Empty;
|
||
if (targetType == typeof(bool))
|
||
return false;
|
||
return 0; // 数值类型默认返回0
|
||
}
|
||
try
|
||
{
|
||
if (targetType == typeof(int))
|
||
return int.Parse(value);
|
||
if (targetType == typeof(uint))
|
||
return uint.Parse(value);
|
||
if (targetType == typeof(long))
|
||
return long.Parse(value);
|
||
if (targetType == typeof(float))
|
||
return float.Parse(value);
|
||
if (targetType == typeof(double))
|
||
return double.Parse(value);
|
||
if (targetType == typeof(bool))
|
||
return bool.Parse(value.ToLower());
|
||
if (targetType == typeof(string))
|
||
return value;
|
||
|
||
throw new NotSupportedException($"不支持的类型: {targetType.Name}");
|
||
}
|
||
catch (FormatException ex)
|
||
{
|
||
throw new FormatException($"无法将 '{value}' 转换为 {targetType.Name} 类型", ex);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
public static class FileHelper
|
||
{
|
||
public static List<string> GetAllFiles(string dir, string searchPattern = "*")
|
||
{
|
||
List<string> list = new List<string>();
|
||
GetAllFiles(list, dir, searchPattern);
|
||
return list;
|
||
}
|
||
|
||
public static void GetAllFiles(List<string> files, string dir, string searchPattern = "*")
|
||
{
|
||
if (!Directory.Exists(dir))
|
||
{
|
||
return;
|
||
}
|
||
|
||
string[] fls = Directory.GetFiles(dir, searchPattern);
|
||
foreach (string fl in fls)
|
||
{
|
||
files.Add(fl);
|
||
}
|
||
|
||
string[] subDirs = Directory.GetDirectories(dir);
|
||
foreach (string subDir in subDirs)
|
||
{
|
||
GetAllFiles(files, subDir, searchPattern);
|
||
}
|
||
}
|
||
}
|
||
} |