CmdCheckDataIntegrity.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. 
  2. using System.Linq;
  3. namespace etoy
  4. {
  5. class CmdCheckDataIntegrity : Command
  6. {
  7. public override string Description => "数据完整性检验";
  8. List<Exception> _errors = new List<Exception>();
  9. const string FIELD_REPEATED = "[]";
  10. readonly string[] PRIMITIVE_TYPES =
  11. {
  12. FieldTypeDefine.Int,
  13. FieldTypeDefine.Long,
  14. FieldTypeDefine.Boolean,
  15. FieldTypeDefine.String,
  16. FieldTypeDefine.Float,
  17. FieldTypeDefine.Double,
  18. };
  19. protected override void OnProcess()
  20. {
  21. SetProgress(0.1f);
  22. foreach (var table in Context.Blackboard.KeyValueTables)
  23. CheckKeyValueTable(table);
  24. SetProgress(0.3f);
  25. foreach (var table in Context.Blackboard.MetadataTables)
  26. CheckMetadataTable(table);
  27. SetProgress(0.6f);
  28. foreach (var table in Context.Blackboard.Tables)
  29. CheckTable(table);
  30. if (_errors.Count > 0)
  31. {
  32. SetException(new Exception(string.Join('\n', (from e in _errors select $"## ERROR: {e.Message}").ToArray())));
  33. }
  34. else
  35. {
  36. Completed();
  37. }
  38. }
  39. void CheckMetadataTable(MetadataTable table)
  40. {
  41. foreach (var row in table.Rows)
  42. {
  43. if (row.Struct.StructType == MetadataStructType.Struct)
  44. continue;
  45. // 检测枚举值
  46. foreach (var field in row.Struct.Fields)
  47. {
  48. if (!string.IsNullOrEmpty(field.EnumValue))
  49. {
  50. if (!int.TryParse(field.EnumValue, out var _))
  51. throw new Exception($"枚举({row.Struct.Name}) 枚举值转int失败 {table.Path}");
  52. }
  53. }
  54. }
  55. }
  56. void AddException(Exception e)
  57. {
  58. _errors.Add(e);
  59. }
  60. void CheckKeyValueTable(KeyValueTable table)
  61. {
  62. foreach (var row in table.Rows)
  63. {
  64. if (string.IsNullOrEmpty(row.Key))
  65. AddException(new Exception($"配置表({table.Name}) Key为空, 不合法。{table.Path}, (行号: {row.Row + 1})"));
  66. if (string.IsNullOrEmpty(row.Value))
  67. AddException(new Exception($"配置表({table.Name}) Value为空, 不合法。 Key: ({row.Key}), {table.Path}, (行号: {row.Row + 1})"));
  68. var fieldType = row.Type;
  69. var repeadIdx = fieldType.IndexOf(FIELD_REPEATED);
  70. if (repeadIdx > 0)
  71. { // 是数组类型
  72. fieldType = fieldType.Substring(0, repeadIdx).Trim();
  73. var repeatedType = fieldType switch
  74. {
  75. FieldTypeDefine.Boolean => typeof(bool[]),
  76. FieldTypeDefine.Int => typeof(int[]),
  77. FieldTypeDefine.Long => typeof(long[]),
  78. FieldTypeDefine.String => typeof(string[]),
  79. _ => default,
  80. };
  81. if (repeatedType == null)
  82. {
  83. AddException(new Exception($"配置表({table.Name}) 不是支持类型: ({row.Type}), Key: ({row.Key}) {table.Path}, (行号: {row.Row + 1})"));
  84. }
  85. else
  86. {
  87. if (!row.Value.JsonToObject(repeatedType, out var result))
  88. {
  89. AddException(new Exception($"配置表({table.Name}) 字段内容: ({row.Value}) 与 字段类型: ({row.Type}) 转换失败。{table.Path}, (行号: {row.Row + 1})"));
  90. }
  91. }
  92. continue;
  93. }
  94. switch (fieldType)
  95. {
  96. case FieldTypeDefine.Boolean:
  97. if (!int.TryParse(row.Value, out var boolValue) || (boolValue != 0 && boolValue != 1))
  98. AddException(new Exception($"配置表({table.Name}) 字段内容: ({row.Value}) 与 字段类型: ({row.Type}) 转换失败。{table.Path}, (行号: {row.Row + 1})"));
  99. break;
  100. case FieldTypeDefine.Int:
  101. if (!int.TryParse(row.Value, out _))
  102. AddException(new Exception($"配置表({table.Name}) 字段内容: ({row.Value}) 与 字段类型: ({row.Type}) 转换失败。{table.Path}, (行号: {row.Row + 1})"));
  103. break;
  104. case FieldTypeDefine.Long:
  105. if (!long.TryParse(row.Value, out _))
  106. AddException(new Exception($"配置表({table.Name}) 字段内容: ({row.Value}) 与 字段类型: ({row.Type}) 转换失败。{table.Path}, (行号: {row.Row + 1})"));
  107. break;
  108. case FieldTypeDefine.String:
  109. // Nothing to check
  110. break;
  111. default:
  112. AddException(new Exception($"配置表({table.Name}) 不是支持类型: ({row.Type}), Key: ({row.Key}) {table.Path}, (行号: {row.Row + 1})"));
  113. break;
  114. }
  115. }
  116. }
  117. void CheckTable(Table table)
  118. {
  119. if (string.IsNullOrEmpty(table.Name))
  120. AddException(new Exception($"配置表的名字为null, Path: {table.Path}"));
  121. if (table.FieldInfos == null || table.FieldInfos.Length == 0)
  122. AddException(new Exception($"配置表结构错误, Path: {table.Path}"));
  123. // 表结构类型检测
  124. foreach (var fieldInfo in table.FieldInfos)
  125. {
  126. if (fieldInfo.Isi18nField)
  127. {
  128. if (fieldInfo.FieldType != FieldTypeDefine.String)
  129. AddException(new Exception($"配置表字段类型({fieldInfo.FieldType})不是string还想搞i18n, 疯了吧, 字段: ({fieldInfo.FieldName}), {table.Path}"));
  130. }
  131. // 基础类型过滤
  132. if (PRIMITIVE_TYPES.Contains(fieldInfo.FieldType))
  133. continue;
  134. foreach (var metadataTable in Context.Blackboard.MetadataTables)
  135. {
  136. if (!metadataTable.Structs.ContainsKey(fieldInfo.FieldType))
  137. AddException(new Exception($"Metadata不存在类型{fieldInfo.FieldType}, 所在表: {table.Path}"));
  138. }
  139. }
  140. // PrimaryKey检测
  141. if (table.GetPKFieldInfo() == null)
  142. AddException(new Exception($"配置表({table.Name}), 表结构未含PrimaryKey的字段. {table.Path}"));
  143. // Cells 合法性检测
  144. var primaryKeys = new HashSet<string>();
  145. for (int i = 0, rowCount = table.Rows.Count; i < rowCount; i++)
  146. {
  147. var row = table.Rows[i];
  148. for (int j = 0, colCount = row.Cells.Count; j < colCount; j++)
  149. {
  150. var cell = row.Cells[j];
  151. int excelRow = cell.Row + 1;
  152. int excelCol = cell.Column + 1;
  153. string fieldType = cell.FieldInfo.FieldType;
  154. if (cell.FieldInfo.IsPrimaryKey)
  155. { // 主键
  156. if (!primaryKeys.Add(cell.Value))
  157. {
  158. AddException(new Exception($"配置表({table.Name}) 主键重复 字段内容: ({cell.Value}) 与 字段类型: ({fieldType})。 {table.Path} (行: {excelRow}, 列: {excelCol})"));
  159. continue;
  160. }
  161. }
  162. bool valid;
  163. if (cell.FieldInfo.IsRepeated)
  164. {
  165. valid = !string.IsNullOrEmpty(cell.Value) && cell.Value.IsJsonString();
  166. }
  167. else if (Context.Blackboard.TryGetMetadata(fieldType, out var metadata) && metadata.StructType == MetadataStructType.Enum)
  168. { // 枚举值
  169. if (string.IsNullOrEmpty(cell.Value))
  170. {
  171. valid = false;
  172. }
  173. else if (int.TryParse(cell.Value, out var intVal))
  174. { // 存的是数字
  175. var field = metadata.Fields.First(a => a.EnumIntVal == intVal);
  176. valid = field != null;
  177. }
  178. else
  179. {
  180. // 存的是枚举的名字
  181. var field = metadata.Fields.First(a => a.Name.Equals(cell.Value, StringComparison.OrdinalIgnoreCase));
  182. valid = field != null;
  183. }
  184. }
  185. else
  186. {
  187. valid = fieldType switch
  188. {
  189. // 基础类型转换检测
  190. FieldTypeDefine.Int => int.TryParse(cell.Value, out _),
  191. FieldTypeDefine.Long => long.TryParse(cell.Value, out _),
  192. FieldTypeDefine.Float => float.TryParse(cell.Value, out _),
  193. FieldTypeDefine.Double => double.TryParse(cell.Value, out _),
  194. // 字符串可能有空的情况,不需要进行格子内容判空检测
  195. FieldTypeDefine.String => true,
  196. // 这里只做了非空metadata kson检测
  197. _ => !string.IsNullOrEmpty(cell.Value) && cell.Value.IsJsonString(),// metadata
  198. };
  199. }
  200. if (!valid)
  201. AddException(new Exception($"配置表({table.Name}) 字段内容: ({cell.Value}) 与 字段类型: ({fieldType}) 转换失败。 {table.Path} (行: {excelRow}, 列: {excelCol})"));
  202. }
  203. }
  204. primaryKeys.Clear();
  205. }
  206. }
  207. }