ExcelExporter.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Text;
  7. using Microsoft.CodeAnalysis;
  8. using Microsoft.CodeAnalysis.CSharp;
  9. using Microsoft.CodeAnalysis.Emit;
  10. using MongoDB.Bson;
  11. using MongoDB.Bson.Serialization;
  12. using OfficeOpenXml;
  13. using ProtoBuf;
  14. using LicenseContext = OfficeOpenXml.LicenseContext;
  15. namespace ET
  16. {
  17. public enum ConfigType
  18. {
  19. c,
  20. s,
  21. }
  22. class HeadInfo
  23. {
  24. public string FieldAttribute;
  25. public string FieldDesc;
  26. public string FieldName;
  27. public string FieldType;
  28. public int FieldIndex;
  29. public HeadInfo(string cs, string desc, string name, string type, int index)
  30. {
  31. this.FieldAttribute = cs;
  32. this.FieldDesc = desc;
  33. this.FieldName = name;
  34. this.FieldType = type;
  35. this.FieldIndex = index;
  36. }
  37. }
  38. public static class ExcelExporter
  39. {
  40. private static string template;
  41. private const string clientClassDir = "../Unity/Codes/Model/Generate/Config";
  42. private const string serverClassDir = "../Apps/Model/Generate/Config";
  43. private const string excelDir = "../Excel";
  44. private const string jsonDir = "./Json/{0}";
  45. private const string clientProtoDir = "../Unity/Assets/Bundles/Config";
  46. private const string serverProtoDir = "../Config";
  47. public static void Export()
  48. {
  49. try
  50. {
  51. template = File.ReadAllText("Template.txt");
  52. ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
  53. if (Directory.Exists(clientClassDir))
  54. {
  55. Directory.Delete(clientClassDir, true);
  56. }
  57. if (Directory.Exists(serverClassDir))
  58. {
  59. Directory.Delete(serverClassDir, true);
  60. }
  61. foreach (string path in Directory.GetFiles(excelDir))
  62. {
  63. string fileName = Path.GetFileName(path);
  64. if (!fileName.EndsWith(".xlsx") || fileName.StartsWith("~$"))
  65. {
  66. continue;
  67. }
  68. using Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
  69. using ExcelPackage p = new ExcelPackage(stream);
  70. string name = Path.GetFileNameWithoutExtension(path);
  71. string cs = p.Workbook.Worksheets[0].Cells[1, 1].Text.Trim();
  72. if (cs.Contains("#"))
  73. {
  74. continue;
  75. }
  76. if (cs == "")
  77. {
  78. cs = "cs";
  79. }
  80. if (cs.Contains("c"))
  81. {
  82. ExportExcelClass(p, name, ConfigType.c);
  83. }
  84. if (cs.Contains("s"))
  85. {
  86. ExportExcelClass(p, name, ConfigType.s);
  87. }
  88. }
  89. ExportExcelProtobuf(ConfigType.c);
  90. ExportExcelProtobuf(ConfigType.s);
  91. Log.Console("Export Excel Sucess!");
  92. }
  93. catch (Exception e)
  94. {
  95. Log.Console(e.ToString());
  96. }
  97. }
  98. private static string GetProtoDir(ConfigType configType)
  99. {
  100. if (configType == ConfigType.c)
  101. {
  102. return clientProtoDir;
  103. }
  104. return serverProtoDir;
  105. }
  106. private static string GetClassDir(ConfigType configType)
  107. {
  108. if (configType == ConfigType.c)
  109. {
  110. return clientClassDir;
  111. }
  112. return serverClassDir;
  113. }
  114. #region 导出class
  115. static void ExportExcelClass(ExcelPackage p, string name, ConfigType configType)
  116. {
  117. Dictionary<string, HeadInfo> classField = new Dictionary<string, HeadInfo>();
  118. foreach (ExcelWorksheet worksheet in p.Workbook.Worksheets)
  119. {
  120. ExportSheetClass(worksheet, classField, configType);
  121. }
  122. ExportClass(name, classField, configType);
  123. ExportExcelJson(p, name, classField, configType);
  124. }
  125. static void ExportSheetClass(ExcelWorksheet worksheet, Dictionary<string, HeadInfo> classField, ConfigType configType)
  126. {
  127. string configTypeStr = configType.ToString();
  128. const int row = 2;
  129. for (int col = 3; col <= worksheet.Dimension.End.Column; ++col)
  130. {
  131. if (worksheet.Name.StartsWith("#"))
  132. {
  133. continue;
  134. }
  135. string fieldName = worksheet.Cells[row + 2, col].Text.Trim();
  136. if (fieldName == "")
  137. {
  138. continue;
  139. }
  140. if (classField.ContainsKey(fieldName))
  141. {
  142. continue;
  143. }
  144. string fieldCS = worksheet.Cells[row, col].Text.Trim().ToLower();
  145. if (fieldCS.Contains("#"))
  146. {
  147. classField[fieldName] = null;
  148. continue;
  149. }
  150. if (fieldCS == "")
  151. {
  152. fieldCS = "cs";
  153. }
  154. if (!fieldCS.Contains(configTypeStr))
  155. {
  156. classField[fieldName] = null;
  157. continue;
  158. }
  159. string fieldDesc = worksheet.Cells[row + 1, col].Text.Trim();
  160. string fieldType = worksheet.Cells[row + 3, col].Text.Trim();
  161. classField[fieldName] = new HeadInfo(fieldCS, fieldDesc, fieldName, fieldType, col);
  162. }
  163. }
  164. static void ExportClass(string protoName, Dictionary<string, HeadInfo> classField, ConfigType configType)
  165. {
  166. string dir = GetClassDir(configType);
  167. if (!Directory.Exists(dir))
  168. {
  169. Directory.CreateDirectory(dir);
  170. }
  171. string exportPath = Path.Combine(dir, $"{protoName}.cs");
  172. using FileStream txt = new FileStream(exportPath, FileMode.Create);
  173. using StreamWriter sw = new StreamWriter(txt);
  174. StringBuilder sb = new StringBuilder();
  175. foreach ((string _, HeadInfo headInfo) in classField)
  176. {
  177. if (headInfo == null)
  178. {
  179. continue;
  180. }
  181. sb.Append($"\t\t[ProtoMember({headInfo.FieldIndex - 2})]\n");
  182. sb.Append($"\t\tpublic {headInfo.FieldType} {headInfo.FieldName} {{ get; set; }}\n");
  183. }
  184. string content = template.Replace("(ConfigName)", protoName).Replace(("(Fields)"), sb.ToString());
  185. sw.Write(content);
  186. }
  187. #endregion
  188. #region 导出json
  189. static void ExportExcelJson(ExcelPackage p, string name, Dictionary<string, HeadInfo> classField, ConfigType configType)
  190. {
  191. StringBuilder sb = new StringBuilder();
  192. sb.AppendLine("{\"list\":[");
  193. foreach (ExcelWorksheet worksheet in p.Workbook.Worksheets)
  194. {
  195. if (worksheet.Name.StartsWith("#"))
  196. {
  197. continue;
  198. }
  199. ExportSheetJson(worksheet, name, classField, configType, sb);
  200. }
  201. sb.AppendLine("]}");
  202. string dir = string.Format(jsonDir, configType.ToString());
  203. if (!Directory.Exists(dir))
  204. {
  205. Directory.CreateDirectory(dir);
  206. }
  207. string jsonPath = Path.Combine(dir, $"{name}.txt");
  208. using FileStream txt = new FileStream(jsonPath, FileMode.Create);
  209. using StreamWriter sw = new StreamWriter(txt);
  210. sw.Write(sb.ToString());
  211. }
  212. static void ExportSheetJson(ExcelWorksheet worksheet, string name, Dictionary<string, HeadInfo> classField, ConfigType configType, StringBuilder sb)
  213. {
  214. string configTypeStr = configType.ToString();
  215. for (int row = 6; row <= worksheet.Dimension.End.Row; ++row)
  216. {
  217. string prefix = worksheet.Cells[row, 2].Text.Trim();
  218. if (prefix.Contains("#"))
  219. {
  220. continue;
  221. }
  222. if (prefix == "")
  223. {
  224. prefix = "cs";
  225. }
  226. if (!prefix.Contains(configTypeStr))
  227. {
  228. continue;
  229. }
  230. if (worksheet.Cells[row, 3].Text.Trim() == "")
  231. {
  232. continue;
  233. }
  234. sb.Append("{");
  235. sb.Append($"\"_t\":\"{name}\"");
  236. for (int col = 3; col <= worksheet.Dimension.End.Column; ++col)
  237. {
  238. string fieldName = worksheet.Cells[4, col].Text.Trim();
  239. if (!classField.ContainsKey(fieldName))
  240. {
  241. continue;
  242. }
  243. HeadInfo headInfo = classField[fieldName];
  244. if (headInfo == null)
  245. {
  246. continue;
  247. }
  248. string fieldN = headInfo.FieldName;
  249. if (fieldN == "Id")
  250. {
  251. fieldN = "_id";
  252. }
  253. sb.Append($",\"{fieldN}\":{Convert(headInfo.FieldType, worksheet.Cells[row, col].Text.Trim())}");
  254. }
  255. sb.Append("},\n");
  256. }
  257. }
  258. private static string Convert(string type, string value)
  259. {
  260. switch (type)
  261. {
  262. case "int[]":
  263. case "int32[]":
  264. case "long[]":
  265. return $"[{value}]";
  266. case "string[]":
  267. return $"[{value}]";
  268. case "int":
  269. case "int32":
  270. case "int64":
  271. case "long":
  272. case "float":
  273. case "double":
  274. if (value == "")
  275. {
  276. return "0";
  277. }
  278. return value;
  279. case "string":
  280. return $"\"{value}\"";
  281. default:
  282. throw new Exception($"不支持此类型: {type}");
  283. }
  284. }
  285. #endregion
  286. // 根据生成的类,动态编译把json转成protobuf
  287. private static void ExportExcelProtobuf(ConfigType configType)
  288. {
  289. string classPath = GetClassDir(configType);
  290. List<SyntaxTree> syntaxTrees = new List<SyntaxTree>();
  291. List<string> protoNames = new List<string>();
  292. foreach (string classFile in Directory.GetFiles(classPath, "*.cs"))
  293. {
  294. protoNames.Add(Path.GetFileNameWithoutExtension(classFile));
  295. syntaxTrees.Add(CSharpSyntaxTree.ParseText(File.ReadAllText(classFile)));
  296. }
  297. List<PortableExecutableReference> references = new List<PortableExecutableReference>();
  298. Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
  299. foreach (Assembly assembly in assemblies)
  300. {
  301. try
  302. {
  303. if (assembly.IsDynamic)
  304. {
  305. continue;
  306. }
  307. if (assembly.Location == "")
  308. {
  309. continue;
  310. }
  311. }
  312. catch (Exception e)
  313. {
  314. Console.WriteLine(e);
  315. throw;
  316. }
  317. PortableExecutableReference reference = MetadataReference.CreateFromFile(assembly.Location);
  318. references.Add(reference);
  319. }
  320. CSharpCompilation compilation = CSharpCompilation.Create(
  321. null,
  322. syntaxTrees.ToArray(),
  323. references.ToArray(),
  324. new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
  325. using MemoryStream memSteam = new MemoryStream();
  326. EmitResult emitResult = compilation.Emit(memSteam);
  327. if (!emitResult.Success)
  328. {
  329. StringBuilder stringBuilder = new StringBuilder();
  330. foreach (Diagnostic t in emitResult.Diagnostics)
  331. {
  332. stringBuilder.AppendLine(t.GetMessage());
  333. }
  334. throw new Exception($"动态编译失败:\n{stringBuilder}");
  335. }
  336. memSteam.Seek(0, SeekOrigin.Begin);
  337. Assembly ass = Assembly.Load(memSteam.ToArray());
  338. string dir = GetProtoDir(configType);
  339. if (!Directory.Exists(dir))
  340. {
  341. Directory.CreateDirectory(dir);
  342. }
  343. foreach (string protoName in protoNames)
  344. {
  345. Type type = ass.GetType($"ET.{protoName}Category");
  346. Type subType = ass.GetType($"ET.{protoName}");
  347. Serializer.NonGeneric.PrepareSerializer(type);
  348. Serializer.NonGeneric.PrepareSerializer(subType);
  349. string json = File.ReadAllText(Path.Combine(string.Format(jsonDir, configType), $"{protoName}.txt"));
  350. object deserialize = BsonSerializer.Deserialize(json, type);
  351. string path = Path.Combine(dir, $"{protoName}Category.bytes");
  352. using FileStream file = File.Create(path);
  353. Serializer.Serialize(file, deserialize);
  354. }
  355. }
  356. }
  357. }