using System.Linq; namespace etoy { class CmdCheckDataIntegrity : Command { public override string Description => "数据完整性检验"; List _errors = new List(); const string FIELD_REPEATED = "[]"; readonly string[] PRIMITIVE_TYPES = { FieldTypeDefine.Int, FieldTypeDefine.Long, FieldTypeDefine.Boolean, FieldTypeDefine.String, FieldTypeDefine.Float, FieldTypeDefine.Double, }; protected override void OnProcess() { SetProgress(0.1f); foreach (var table in Context.Blackboard.KeyValueTables) CheckKeyValueTable(table); SetProgress(0.3f); foreach (var table in Context.Blackboard.MetadataTables) CheckMetadataTable(table); SetProgress(0.6f); foreach (var table in Context.Blackboard.Tables) CheckTable(table); if (_errors.Count > 0) { SetException(new Exception(string.Join('\n', (from e in _errors select $"## ERROR: {e.Message}").ToArray()))); } else { Completed(); } } void CheckMetadataTable(MetadataTable table) { foreach (var row in table.Rows) { if (row.Struct.StructType == MetadataStructType.Struct) continue; // 检测枚举值 foreach (var field in row.Struct.Fields) { if (!string.IsNullOrEmpty(field.EnumValue)) { if (!int.TryParse(field.EnumValue, out var _)) throw new Exception($"枚举({row.Struct.Name}) 枚举值转int失败 {table.Path}"); } } } } void AddException(Exception e) { _errors.Add(e); } void CheckKeyValueTable(KeyValueTable table) { foreach (var row in table.Rows) { if (string.IsNullOrEmpty(row.Key)) AddException(new Exception($"配置表({table.Name}) Key为空, 不合法。{table.Path}, (行号: {row.Row + 1})")); if (string.IsNullOrEmpty(row.Value)) AddException(new Exception($"配置表({table.Name}) Value为空, 不合法。 Key: ({row.Key}), {table.Path}, (行号: {row.Row + 1})")); var fieldType = row.Type; var repeadIdx = fieldType.IndexOf(FIELD_REPEATED); if (repeadIdx > 0) { // 是数组类型 fieldType = fieldType.Substring(0, repeadIdx).Trim(); var repeatedType = fieldType switch { FieldTypeDefine.Boolean => typeof(bool[]), FieldTypeDefine.Int => typeof(int[]), FieldTypeDefine.Long => typeof(long[]), FieldTypeDefine.String => typeof(string[]), _ => default, }; if (repeatedType == null) { AddException(new Exception($"配置表({table.Name}) 不是支持类型: ({row.Type}), Key: ({row.Key}) {table.Path}, (行号: {row.Row + 1})")); } else { if (!row.Value.JsonToObject(repeatedType, out var result)) { AddException(new Exception($"配置表({table.Name}) 字段内容: ({row.Value}) 与 字段类型: ({row.Type}) 转换失败。{table.Path}, (行号: {row.Row + 1})")); } } continue; } switch (fieldType) { case FieldTypeDefine.Boolean: if (!int.TryParse(row.Value, out var boolValue) || (boolValue != 0 && boolValue != 1)) AddException(new Exception($"配置表({table.Name}) 字段内容: ({row.Value}) 与 字段类型: ({row.Type}) 转换失败。{table.Path}, (行号: {row.Row + 1})")); break; case FieldTypeDefine.Int: if (!int.TryParse(row.Value, out _)) AddException(new Exception($"配置表({table.Name}) 字段内容: ({row.Value}) 与 字段类型: ({row.Type}) 转换失败。{table.Path}, (行号: {row.Row + 1})")); break; case FieldTypeDefine.Long: if (!long.TryParse(row.Value, out _)) AddException(new Exception($"配置表({table.Name}) 字段内容: ({row.Value}) 与 字段类型: ({row.Type}) 转换失败。{table.Path}, (行号: {row.Row + 1})")); break; case FieldTypeDefine.String: // Nothing to check break; default: AddException(new Exception($"配置表({table.Name}) 不是支持类型: ({row.Type}), Key: ({row.Key}) {table.Path}, (行号: {row.Row + 1})")); break; } } } void CheckTable(Table table) { if (string.IsNullOrEmpty(table.Name)) AddException(new Exception($"配置表的名字为null, Path: {table.Path}")); if (table.FieldInfos == null || table.FieldInfos.Length == 0) AddException(new Exception($"配置表结构错误, Path: {table.Path}")); // 表结构类型检测 foreach (var fieldInfo in table.FieldInfos) { if (fieldInfo.Isi18nField) { if (fieldInfo.FieldType != FieldTypeDefine.String) AddException(new Exception($"配置表字段类型({fieldInfo.FieldType})不是string还想搞i18n, 疯了吧, 字段: ({fieldInfo.FieldName}), {table.Path}")); } // 基础类型过滤 if (PRIMITIVE_TYPES.Contains(fieldInfo.FieldType)) continue; foreach (var metadataTable in Context.Blackboard.MetadataTables) { if (!metadataTable.Structs.ContainsKey(fieldInfo.FieldType)) AddException(new Exception($"Metadata不存在类型{fieldInfo.FieldType}, 所在表: {table.Path}")); } } // PrimaryKey检测 if (table.GetPKFieldInfo() == null) AddException(new Exception($"配置表({table.Name}), 表结构未含PrimaryKey的字段. {table.Path}")); // Cells 合法性检测 var primaryKeys = new HashSet(); for (int i = 0, rowCount = table.Rows.Count; i < rowCount; i++) { var row = table.Rows[i]; for (int j = 0, colCount = row.Cells.Count; j < colCount; j++) { var cell = row.Cells[j]; int excelRow = cell.Row + 1; int excelCol = cell.Column + 1; string fieldType = cell.FieldInfo.FieldType; if (cell.FieldInfo.IsPrimaryKey) { // 主键 if (!primaryKeys.Add(cell.Value)) { AddException(new Exception($"配置表({table.Name}) 主键重复 字段内容: ({cell.Value}) 与 字段类型: ({fieldType})。 {table.Path} (行: {excelRow}, 列: {excelCol})")); continue; } } bool valid; if (cell.FieldInfo.IsRepeated) { valid = !string.IsNullOrEmpty(cell.Value) && cell.Value.IsJsonString(); } else if (Context.Blackboard.TryGetMetadata(fieldType, out var metadata) && metadata.StructType == MetadataStructType.Enum) { // 枚举值 if (string.IsNullOrEmpty(cell.Value)) { valid = false; } else if (int.TryParse(cell.Value, out var intVal)) { // 存的是数字 var field = metadata.Fields.First(a => a.EnumIntVal == intVal); valid = field != null; } else { // 存的是枚举的名字 var field = metadata.Fields.First(a => a.Name.Equals(cell.Value, StringComparison.OrdinalIgnoreCase)); valid = field != null; } } else { valid = fieldType switch { // 基础类型转换检测 FieldTypeDefine.Int => int.TryParse(cell.Value, out _), FieldTypeDefine.Long => long.TryParse(cell.Value, out _), FieldTypeDefine.Float => float.TryParse(cell.Value, out _), FieldTypeDefine.Double => double.TryParse(cell.Value, out _), // 字符串可能有空的情况,不需要进行格子内容判空检测 FieldTypeDefine.String => true, // 这里只做了非空metadata kson检测 _ => !string.IsNullOrEmpty(cell.Value) && cell.Value.IsJsonString(),// metadata }; } if (!valid) AddException(new Exception($"配置表({table.Name}) 字段内容: ({cell.Value}) 与 字段类型: ({fieldType}) 转换失败。 {table.Path} (行: {excelRow}, 列: {excelCol})")); } } primaryKeys.Clear(); } } }