123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- using System.Text;
- using System.Threading.Tasks;
- using Microsoft.CodeAnalysis;
- using Microsoft.CodeAnalysis.CSharp;
- using Microsoft.CodeAnalysis.Emit;
- using MongoDB.Bson.Serialization;
- using OfficeOpenXml;
- using ProtoBuf;
- using LicenseContext = OfficeOpenXml.LicenseContext;
- namespace ET
- {
- public enum ConfigType
- {
- c = 0,
- s = 1,
- cs = 2,
- }
- class HeadInfo
- {
- public string FieldCS;
- public string FieldDesc;
- public string FieldName;
- public string FieldType;
- public int FieldIndex;
- public HeadInfo(string cs, string desc, string name, string type, int index)
- {
- this.FieldCS = cs;
- this.FieldDesc = desc;
- this.FieldName = name;
- this.FieldType = type;
- this.FieldIndex = index;
- }
- }
- // 这里加个标签是为了防止编译时裁剪掉protobuf,因为整个tool工程没有用到protobuf,编译会去掉引用,然后动态编译就会出错
- [ProtoContract]
- class Table
- {
- public bool C;
- public bool S;
- public int Index;
- public Dictionary<string, HeadInfo> HeadInfos = new Dictionary<string, HeadInfo>();
- }
-
- public static class ExcelExporter
- {
- private static string template;
- private const string ClientClassDir = "../Unity/Assets/Scripts/Codes/Model/Client/Generate/Config";
- // 服务端因为机器人的存在必须包含客户端所有配置,所以单独的c字段没有意义,单独的c就表示cs
- private const string ServerClassDir = "../DotNet/Model/Generate/Config";
- private const string CSClassDir = "../Unity/Assets/Scripts/Codes/Model/Generate/ClientServer/Config";
- private const string excelDir = "../Config/Excel/";
- private const string jsonDir = "../Config/GenJson/{0}/{1}";
- private const string clientProtoDir = "../Unity/Assets/Bundles/Config";
- private const string serverProtoDir = "../Config/GenFromExcel/{0}/{1}";
- private static Assembly[] configAssemblies = new Assembly[3];
- 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;
- }
- public static void Export()
- {
- try
- {
- //防止编译时裁剪掉protobuf
- ProtoBuf.WireType.Fixed64.ToString();
-
- template = File.ReadAllText("Template.txt");
- ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
- if (Directory.Exists(ClientClassDir))
- {
- Directory.Delete(ClientClassDir, true);
- }
- if (Directory.Exists(ServerClassDir))
- {
- Directory.Delete(ServerClassDir, true);
- }
- List<string> files = FileHelper.GetAllFiles(excelDir);
- foreach (string path in files)
- {
- string fileName = Path.GetFileName(path);
- if (!fileName.EndsWith(".xlsx") || fileName.StartsWith("~$") || fileName.Contains("#"))
- {
- continue;
- }
- string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
- string fileNameWithoutCS = fileNameWithoutExtension;
- string cs = "cs";
- if (fileNameWithoutExtension.Contains("@"))
- {
- string[] ss = fileNameWithoutExtension.Split("@");
- fileNameWithoutCS = ss[0];
- cs = ss[1];
- }
- if (cs == "")
- {
- cs = "cs";
- }
- ExcelPackage p = GetPackage(Path.GetFullPath(path));
- string protoName = fileNameWithoutCS;
- if (fileNameWithoutCS.Contains('_'))
- {
- protoName = fileNameWithoutCS.Substring(0, fileNameWithoutCS.LastIndexOf('_'));
- }
- Table table = GetTable(protoName);
- if (cs.Contains("c"))
- {
- table.C = true;
- }
- if (cs.Contains("s"))
- {
- table.S = true;
- }
- ExportExcelClass(p, protoName, table);
- }
- foreach (var kv in tables)
- {
- if (kv.Value.C)
- {
- ExportClass(kv.Key, kv.Value.HeadInfos, ConfigType.c);
- }
- if (kv.Value.S)
- {
- ExportClass(kv.Key, kv.Value.HeadInfos, ConfigType.s);
- }
- // ExportClass(kv.Key, kv.Value.HeadInfos, ConfigType.cs);
- }
- // 动态编译生成的配置代码
- configAssemblies[(int) ConfigType.c] = DynamicBuild(ConfigType.c);
- configAssemblies[(int) ConfigType.s] = DynamicBuild(ConfigType.s);
- // configAssemblies[(int) ConfigType.cs] = DynamicBuild(ConfigType.cs);
- List<string> excels = FileHelper.GetAllFiles(excelDir, "*.xlsx");
-
- foreach (string path in excels)
- {
- ExportExcel(path);
- }
-
- if (Directory.Exists(clientProtoDir))
- {
- Directory.Delete(clientProtoDir, true);
- }
- FileHelper.CopyDirectory(GetProtoDir(ConfigType.c, ""), clientProtoDir);
-
- Log.Console("Export Excel Sucess!");
- }
- catch (Exception e)
- {
- Log.Console(e.ToString());
- }
- finally
- {
- tables.Clear();
- foreach (var kv in packages)
- {
- kv.Value.Dispose();
- }
- packages.Clear();
- }
- }
- private static void ExportExcel(string path)
- {
- string dir = Path.GetDirectoryName(path);
- string relativePath = Path.GetRelativePath(excelDir, dir);
- string fileName = Path.GetFileName(path);
- if (!fileName.EndsWith(".xlsx") || fileName.StartsWith("~$") || fileName.Contains("#"))
- {
- return;
- }
- string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
- string fileNameWithoutCS = fileNameWithoutExtension;
- string cs = "cs";
- if (fileNameWithoutExtension.Contains("@"))
- {
- string[] ss = fileNameWithoutExtension.Split("@");
- fileNameWithoutCS = ss[0];
- cs = ss[1];
- }
-
- if (cs == "")
- {
- cs = "cs";
- }
- string protoName = fileNameWithoutCS;
- if (fileNameWithoutCS.Contains('_'))
- {
- protoName = fileNameWithoutCS.Substring(0, fileNameWithoutCS.LastIndexOf('_'));
- }
- Table table = GetTable(protoName);
- ExcelPackage p = GetPackage(Path.GetFullPath(path));
- if (cs.Contains("c"))
- {
- ExportExcelJson(p, fileNameWithoutCS, table, ConfigType.c, relativePath);
- ExportExcelProtobuf(ConfigType.c, protoName, relativePath);
- }
- if (cs.Contains("s"))
- {
- ExportExcelJson(p, fileNameWithoutCS, table, ConfigType.s, relativePath);
- ExportExcelProtobuf(ConfigType.s, protoName, relativePath);
- }
- // ExportExcelJson(p, fileNameWithoutCS, table, ConfigType.cs, relativePath);
- // ExportExcelProtobuf(ConfigType.cs, protoName, relativePath);
- }
- private static string GetProtoDir(ConfigType configType, string relativeDir)
- {
- return string.Format(serverProtoDir, configType.ToString(), relativeDir);
- }
- private static Assembly GetAssembly(ConfigType configType)
- {
- return configAssemblies[(int) configType];
- }
- private static string GetClassDir(ConfigType configType)
- {
- return configType switch
- {
- ConfigType.c => ClientClassDir,
- ConfigType.s => ServerClassDir,
- _ => CSClassDir
- };
- }
-
- // 动态编译生成的cs代码
- private static Assembly DynamicBuild(ConfigType configType)
- {
- string classPath = GetClassDir(configType);
- if (!Directory.Exists(classPath))
- {
- Directory.CreateDirectory(classPath);
- }
-
- List<SyntaxTree> syntaxTrees = new List<SyntaxTree>();
- List<string> protoNames = new List<string>();
- foreach (string classFile in Directory.GetFiles(classPath, "*.cs"))
- {
- protoNames.Add(Path.GetFileNameWithoutExtension(classFile));
- syntaxTrees.Add(CSharpSyntaxTree.ParseText(File.ReadAllText(classFile)));
- }
- List<PortableExecutableReference> references = new List<PortableExecutableReference>();
- Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
- foreach (Assembly assembly in assemblies)
- {
- try
- {
- if (assembly.IsDynamic)
- {
- continue;
- }
- if (assembly.Location == "")
- {
- continue;
- }
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- throw;
- }
- PortableExecutableReference reference = MetadataReference.CreateFromFile(assembly.Location);
- references.Add(reference);
- }
- CSharpCompilation compilation = CSharpCompilation.Create(null,
- syntaxTrees.ToArray(),
- references.ToArray(),
- new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
- using MemoryStream memSteam = new MemoryStream();
- EmitResult emitResult = compilation.Emit(memSteam);
- if (!emitResult.Success)
- {
- StringBuilder stringBuilder = new StringBuilder();
- foreach (Diagnostic t in emitResult.Diagnostics)
- {
- stringBuilder.Append($"{t.GetMessage()}\n");
- }
- throw new Exception($"动态编译失败:\n{stringBuilder}");
- }
- memSteam.Seek(0, SeekOrigin.Begin);
- Assembly ass = Assembly.Load(memSteam.ToArray());
- return ass;
- }
- #region 导出class
- static void ExportExcelClass(ExcelPackage p, string name, Table table)
- {
- foreach (ExcelWorksheet worksheet in p.Workbook.Worksheets)
- {
- ExportSheetClass(worksheet, table);
- }
- }
- static void ExportSheetClass(ExcelWorksheet worksheet, Table table)
- {
- const int row = 2;
- for (int col = 3; col <= worksheet.Dimension.End.Column; ++col)
- {
- if (worksheet.Name.StartsWith("#"))
- {
- continue;
- }
- string fieldName = worksheet.Cells[row + 2, col].Text.Trim();
- if (fieldName == "")
- {
- continue;
- }
- if (table.HeadInfos.ContainsKey(fieldName))
- {
- continue;
- }
- string fieldCS = worksheet.Cells[row, col].Text.Trim().ToLower();
- if (fieldCS.Contains("#"))
- {
- table.HeadInfos[fieldName] = null;
- continue;
- }
-
- if (fieldCS == "")
- {
- fieldCS = "cs";
- }
- if (table.HeadInfos.TryGetValue(fieldName, out var oldClassField))
- {
- if (oldClassField.FieldCS != fieldCS)
- {
- Log.Console($"field cs not same: {worksheet.Name} {fieldName} oldcs: {oldClassField.FieldCS} {fieldCS}");
- }
- continue;
- }
- string fieldDesc = worksheet.Cells[row + 1, col].Text.Trim();
- string fieldType = worksheet.Cells[row + 3, col].Text.Trim();
- table.HeadInfos[fieldName] = new HeadInfo(fieldCS, fieldDesc, fieldName, fieldType, ++table.Index);
- }
- }
- static void ExportClass(string protoName, Dictionary<string, HeadInfo> classField, ConfigType configType)
- {
- string dir = GetClassDir(configType);
- if (!Directory.Exists(dir))
- {
- Directory.CreateDirectory(dir);
- }
- string exportPath = Path.Combine(dir, $"{protoName}.cs");
- using FileStream txt = new FileStream(exportPath, FileMode.Create);
- using StreamWriter sw = new StreamWriter(txt);
- StringBuilder sb = new StringBuilder();
- foreach ((string _, HeadInfo headInfo) in classField)
- {
- if (headInfo == null)
- {
- continue;
- }
- if (configType != ConfigType.cs && !headInfo.FieldCS.Contains(configType.ToString()))
- {
- continue;
- }
- sb.Append($"\t\t/// <summary>{headInfo.FieldDesc}</summary>\n");
- sb.Append($"\t\t[ProtoMember({headInfo.FieldIndex})]\n");
- string fieldType = headInfo.FieldType;
- sb.Append($"\t\tpublic {fieldType} {headInfo.FieldName} {{ get; set; }}\n");
- }
- string content = template.Replace("(ConfigName)", protoName).Replace(("(Fields)"), sb.ToString());
- sw.Write(content);
- }
- #endregion
- #region 导出json
- static void ExportExcelJson(ExcelPackage p, string name, Table table, ConfigType configType, string relativeDir)
- {
- StringBuilder sb = new StringBuilder();
- sb.Append("{\"list\":[\n");
- foreach (ExcelWorksheet worksheet in p.Workbook.Worksheets)
- {
- if (worksheet.Name.StartsWith("#"))
- {
- continue;
- }
- ExportSheetJson(worksheet, name, table.HeadInfos, configType, sb);
- }
- sb.Append("]}\n");
- string dir = string.Format(jsonDir, configType.ToString(), relativeDir);
- if (!Directory.Exists(dir))
- {
- Directory.CreateDirectory(dir);
- }
- string jsonPath = Path.Combine(dir, $"{name}.txt");
- using FileStream txt = new FileStream(jsonPath, FileMode.Create);
- using StreamWriter sw = new StreamWriter(txt);
- sw.Write(sb.ToString());
- }
- static void ExportSheetJson(ExcelWorksheet worksheet, string name,
- Dictionary<string, HeadInfo> classField, ConfigType configType, StringBuilder sb)
- {
- string configTypeStr = configType.ToString();
- for (int row = 6; row <= worksheet.Dimension.End.Row; ++row)
- {
- string prefix = worksheet.Cells[row, 2].Text.Trim();
- if (prefix.Contains("#"))
- {
- continue;
- }
- if (prefix == "")
- {
- prefix = "cs";
- }
-
- if (configType != ConfigType.cs && !prefix.Contains(configTypeStr))
- {
- continue;
- }
- if (worksheet.Cells[row, 3].Text.Trim() == "")
- {
- continue;
- }
- sb.Append("{");
- sb.Append($"\"_t\":\"{name}\"");
- for (int col = 3; col <= worksheet.Dimension.End.Column; ++col)
- {
- string fieldName = worksheet.Cells[4, col].Text.Trim();
- if (!classField.ContainsKey(fieldName))
- {
- continue;
- }
- HeadInfo headInfo = classField[fieldName];
- if (headInfo == null)
- {
- continue;
- }
- if (configType != ConfigType.cs && !headInfo.FieldCS.Contains(configTypeStr))
- {
- continue;
- }
- string fieldN = headInfo.FieldName;
- if (fieldN == "Id")
- {
- fieldN = "_id";
- }
- sb.Append($",\"{fieldN}\":{Convert(headInfo.FieldType, worksheet.Cells[row, col].Text.Trim())}");
- }
- sb.Append("},\n");
- }
- }
- private static string Convert(string type, string value)
- {
- switch (type)
- {
- case "uint[]":
- case "int[]":
- case "int32[]":
- case "long[]":
- return $"[{value}]";
- case "string[]":
- case "int[][]":
- return $"[{value}]";
- case "int":
- case "uint":
- case "int32":
- case "int64":
- case "long":
- case "float":
- case "double":
- if (value == "")
- {
- return "0";
- }
- return value;
- case "string":
- value = value.Replace("\\", "\\\\");
- value = value.Replace("\"", "\\\"");
- return $"\"{value}\"";
- default:
- throw new Exception($"不支持此类型: {type}");
- }
- }
- #endregion
- // 根据生成的类,把json转成protobuf
- private static void ExportExcelProtobuf(ConfigType configType, string protoName, string relativeDir)
- {
- string dir = GetProtoDir(configType, relativeDir);
- if (!Directory.Exists(dir))
- {
- Directory.CreateDirectory(dir);
- }
- Assembly ass = GetAssembly(configType);
- Type type = ass.GetType($"ET.{protoName}Category");
- Type subType = ass.GetType($"ET.{protoName}");
- Serializer.NonGeneric.PrepareSerializer(type);
- Serializer.NonGeneric.PrepareSerializer(subType);
- IMerge final = Activator.CreateInstance(type) as IMerge;
- string p = Path.Combine(string.Format(jsonDir, configType, relativeDir));
- string[] ss = Directory.GetFiles(p, $"{protoName}*.txt");
- List<string> jsonPaths = ss.ToList();
- jsonPaths.Sort();
- jsonPaths.Reverse();
- foreach (string jsonPath in jsonPaths)
- {
- string json = File.ReadAllText(jsonPath);
- try
- {
- object deserialize = BsonSerializer.Deserialize(json, type);
- final.Merge(deserialize);
- }
- catch
- {
- #region 为了定位该文件中具体那一行出现了异常
- List<string> list = new List<string>(json.Split('\n'));
- if (list.Count > 0)
- list.RemoveAt(0);
- if (list.Count > 0)
- list.RemoveAt(list.Count-1);
- foreach (string s in list)
- {
- try
- {
- BsonSerializer.Deserialize(s.Substring(0, s.Length-1), subType);
- }
- catch (Exception)
- {
- Log.Console($"json : {s}");
- throw;
- }
- }
- #endregion
- }
- }
- string path = Path.Combine(dir, $"{protoName}Category.bytes");
- using FileStream file = File.Create(path);
- Serializer.Serialize(file, final);
- }
- }
- }
|