一、背景:
如果存在由于不同的业务,不同业务的API接口的报文格式要求不一致,层级和字段名称同样存在多样的需求,每个业务都针对性去开发设计报文生成的代码,重复且工作量大,效率低下。这个时候就需要思考是否可以实现一个可配置,然后根据配置去动态生成想要的报文格式。
二、假设场景:
假设想要生成一个下面的报文格式数据:
{
"reqData": [
{
"A1": "A1",
"A2": "A2",
"A3": "A3",
"A_Line": [
{
"B1": "B11",
"B2": "B22",
"B3": "B31",
"B_Line": [
{
"C1": "C1",
"C2": "C2001",
"C_Line": [
{
"D1": "153",
"D2": "003"
}
]
},
{
"C1": "DC1N",
"C2": "C2002",
"C_Line": []
}
]
},
{
"B1": "2B1",
"B2": "2B2",
"B3": "1B3",
"B_Line": [
{
"C1": "DC1",
"C2": "003",
"C_Line": []
},
{
"C1": "C1N",
"C2": "004",
"C_Line": []
}
]
},
{
"B1": "4B1",
"B2": "B21",
"B3": "B31",
"B_Line": [
{
"C1": "",
"C2": "005",
"C_Line": []
}
]
}
]
},
{
"A1": "A11",
"A2": "A22",
"A3": "A33",
"A_Line": [
{
"B1": "5B1",
"B2": "5B2",
"B3": "1",
"B_Line": [
{
"C1": "C1N",
"C2": "0066",
"C_Line": [
{
"D1": "DD1",
"D2": "D1001"
},
{
"D1": "D",
"D2": "002"
}
]
}
]
}
]
}
]
}
报文reqData节点下是多阶层的数组,A层下面挂B层,B层下面挂着C层,存在多笔数据,每笔数据都有关联关系
三、设计实现:
1、如何设计报文层级(A、B、C)和层级名称(A_Line、B_Line、C_Line)可配置
1.1 配置信息一般配置在数据库DB中,由此设计一个表,可根据业务名称或者id获取报文层级配置信息:
CREATE TABLE LayerSetting(
ID INT NOT NULL,
LayerNo INT NOT NULL,
LayerName VARCHAR(100) NOT NULL,
CommonQueryID VARCHAR(100) NOT NULL
)
LayerNo :层数
LayerName :层级的名称
CommonQueryID :层级之间关联的查询字段
1.2 新建一个实体类,与DB的配置表一致
public class LayerSettingEntity
{
public int LayerNo { get; set; }
public string LayerName { get; set; }
public string CommonQueryID { get; set; }
}
2、如何设计根据配置和数据动态生成报文
2.1 假设业务数据是从DB获取,则需要按照配置了报文几个层级,就返回几个table的数据集,而且table的字段名称需要与报文要求的字段一致(需要将表字段转成报文字段)
2.2 需要动态生成报文样式,这里涉及实现的思想,采用了递归调用,通过递归调用逐层凭拼接封装报文样式,新增方法AutoFormatGenerated,详细思路看代码注释
public class AutoLayerFromat
{
/// <summary>
/// 使用配置生成封装报文样式
/// </summary>
/// <param name="rawData">数据集</param>
/// <param name="sysJobLayerSetting">层级配置信息</param>
/// <returns></returns>
public static RequestDataFrom AutoFormatGenerated(DataSet rawData, List<LayerSettingEntity> sysJobLayerSetting)
{
if (rawData == null || rawData.Tables.Count == 0 || sysJobLayerSetting == null || sysJobLayerSetting.Count == 0)
return null;
var layerSettings = sysJobLayerSetting.OrderBy(x => x.LayerNo).ToList();
//获取需要屏蔽的字段:比如HeaderID、LineID,这些只是不同阶层的关联查询条件,不需要在报文中呈现
var forbidKeyList = layerSettings.Select(s => s.CommonQueryID).ToList();
var dic_header = new Dictionary<string, object>();
//递归调用:从索引值为0开始
ProcessLayer(rawData, dic_header, layerSettings, forbidKeyList, 0);
//排查第一次层的名称reqData(new LayerSettingEntity { LayerNo = 1, LayerName = "reqData",CommonQueryID = "HeaderID" })因为第一层使用了RequestDataFrom实体类字段接收
var layname = layerSettings[0].LayerName;
var data = (List<Dictionary<string, object>>)dic_header[layname];
if (data != null)
{
RequestDataFrom req = new RequestDataFrom()
{
reqData = data
};
return req;
}
return null;
}
/// <summary>
/// 递归方法:生成拼接报文
/// </summary>
/// <param name="rawData">数据集</param>
/// <param name="parentDic">报文数组</param>
/// <param name="layerSettings">层级配置信息</param>
/// <param name="forbidKeyList">屏蔽字段</param>
/// <param name="currentLayerNo">当前层级</param>
/// <param name="filterExpression">查询表达式</param>
private static void ProcessLayer(DataSet rawData, Dictionary<string, object> parentDic, List<LayerSettingEntity> layerSettings, List<string> forbidKeyList, int currentLayerNo, string filterExpression = "")
{
//判断层级信息,根据层数循环
if (currentLayerNo >= layerSettings.Count)
return;
var currentLayerSetting = layerSettings[currentLayerNo];
var currentTable = rawData.Tables[currentLayerNo];
var childList = new List<Dictionary<string, object>>();
//使用查询表达式,查询相互关联的阶层的数据
var childRows = currentTable.Select(filterExpression);
foreach (var childRow in childRows)
{
//组合查询条件成查询表达式
string queryStr = string.Empty;
for (int i = 1; i<= currentLayerNo+1; i++)
{
string querykey = layerSettings.Where(x => x.LayerNo==i).ToList().First().CommonQueryID;
if (string.IsNullOrEmpty(queryStr))
{
queryStr = $"{querykey}='{childRow[querykey]}'";
}
else
{
queryStr += $" AND {querykey}='{childRow[querykey]}'";
}
}
//将行数据转成Dictionary<string, object>的键值对
var childDic = CreateDictionaryFromDataRow(childRow, forbidKeyList);
//递归调用
ProcessLayer(rawData, childDic, layerSettings, forbidKeyList, currentLayerNo + 1, queryStr);
childList.Add(childDic);
}
//拼接数组数据到对应的层级名称下
parentDic[currentLayerSetting.LayerName] = childList;
}
/// <summary>
/// 将table行数据转成字典
/// </summary>
/// <param name="row"></param>
/// <param name="forbidKeyList"></param>
/// <returns></returns>
private static Dictionary<string, object> CreateDictionaryFromDataRow(DataRow row, List<string> forbidKeyList)
{
var dic = new Dictionary<string, object>();
foreach (DataColumn column in row.Table.Columns)
{
if (!forbidKeyList.Contains(column.ColumnName))//排除需要屏蔽的字段
{
dic[column.ColumnName] = row[column] ?? "";
}
}
return dic;
}
}
public class RequestDataFrom
{
public object reqData { get; set; }
}
四、模拟数据调试
1、模拟数据集,根据假设的场景,需要展开4层的报文结构,所以模拟数据集返回了4个DataTable数据
// 模拟数据集
DataSet rawData = new DataSet();
// 表1
DataTable table1 = new DataTable();
table1.Columns.Add("HeaderID");
table1.Columns.Add("A1");
table1.Columns.Add("A2");
table1.Columns.Add("A3");
DataRow row1 = table1.NewRow();
row1["HeaderID"] = "1";
row1["A1"] = "A1";
row1["A2"] = "A2";
row1["A3"] = "A3";
table1.Rows.Add(row1);
DataRow row2 = table1.NewRow();
row2["HeaderID"] = "2";
row2["A1"] = "A11";
row2["A2"] = "A22";
row2["A3"] = "A33";
table1.Rows.Add(row2);
rawData.Tables.Add(table1);
// 表2
DataTable table2 = new DataTable();
table2.Columns.Add("HeaderID");
table2.Columns.Add("LineID");
table2.Columns.Add("B1");
table2.Columns.Add("B2");
table2.Columns.Add("B3");
DataRow row3 = table2.NewRow();
row3["HeaderID"] = "1";
row3["LineID"] = "1";
row3["B1"] = "B11";
row3["B2"] = "B22";
row3["B3"] = "B31";
table2.Rows.Add(row3);
DataRow row4 = table2.NewRow();
row4["HeaderID"] = "1";
row4["LineID"] = "2";
row4["B1"] = "2B1";
row4["B2"] = "2B2";
row4["B3"] = "1B3";
table2.Rows.Add(row4);
DataRow row5 = table2.NewRow();
row5["HeaderID"] = "1";
row5["LineID"] = "3";
row5["B1"] = "4B1";
row5["B2"] = "B21";
row5["B3"] = "B31";
table2.Rows.Add(row5);
DataRow row6 = table2.NewRow();
row6["HeaderID"] = "2";
row6["LineID"] = "1";
row6["B1"] = "5B1";
row6["B2"] = "5B2";
row6["B3"] = "1";
table2.Rows.Add(row6);
rawData.Tables.Add(table2);
// 表3
DataTable table3 = new DataTable();
table3.Columns.Add("HeaderID");
table3.Columns.Add("LineID");
table3.Columns.Add("SublineID");
table3.Columns.Add("C1");
table3.Columns.Add("C2");
DataRow row7 = table3.NewRow();
row7["HeaderID"] = "1";
row7["LineID"] = "1";
row7["SublineID"] = "1";
row7["C1"] = "C1";
row7["C2"] = "C2001";
table3.Rows.Add(row7);
DataRow row8 = table3.NewRow();
row8["HeaderID"] = "1";
row8["LineID"] = "1";
row8["SublineID"] = "2";
row8["C1"] = "DC1N";
row8["C2"] = "C2002";
table3.Rows.Add(row8);
DataRow row9 = table3.NewRow();
row9["HeaderID"] = "1";
row9["LineID"] = "2";
row9["SublineID"] = "1";
row9["C1"] = "DC1";
row9["C2"] = "003";
table3.Rows.Add(row9);
DataRow row10 = table3.NewRow();
row10["HeaderID"] = "1";
row10["LineID"] = "2";
row10["SublineID"] = "2";
row10["C1"] = "C1N";
row10["C2"] = "004";
table3.Rows.Add(row10);
DataRow row11 = table3.NewRow();
row11["HeaderID"] = "1";
row11["LineID"] = "3";
row11["SublineID"] = "1";
row11["C1"] = "";
row11["C2"] = "005";
table3.Rows.Add(row11);
DataRow row12 = table3.NewRow();
row12["HeaderID"] = "2";
row12["LineID"] = "1";
row12["SublineID"] = "1";
row12["C1"] = "C1N";
row12["C2"] = "0066";
table3.Rows.Add(row12);
rawData.Tables.Add(table3);
//表4
DataTable table4 = new DataTable();
table4.Columns.Add("HeaderID");
table4.Columns.Add("LineID");
table4.Columns.Add("SublineID");
table4.Columns.Add("ExtralineID");
table4.Columns.Add("D1");
table4.Columns.Add("D2");
DataRow row13 = table4.NewRow();
row13["HeaderID"] = "2";
row13["LineID"] = "1";
row13["SublineID"] = "1";
row13["ExtralineID"] = "1";
row13["D1"] = "DD1";
row13["D2"] = "D1001";
table4.Rows.Add(row13);
DataRow row14 = table4.NewRow();
row14["HeaderID"] = "2";
row14["LineID"] = "1";
row14["SublineID"] = "1";
row14["ExtralineID"] = "2";
row14["D1"] = "D";
row14["D2"] = "002";
table4.Rows.Add(row14);
DataRow row15 = table4.NewRow();
row15["HeaderID"] = "1";
row15["LineID"] = "1";
row15["SublineID"] = "1";
row15["ExtralineID"] = "1";
row15["D1"] = "153";
row15["D2"] = "003";
table4.Rows.Add(row15);
rawData.Tables.Add(table4);
2、模拟配置的层级信息
// 配置层级字段名
List<LayerSettingEntity> LayerSetting = new List<LayerSettingEntity>
{
new LayerSettingEntity { LayerNo = 1, LayerName = "ESBREQUEST",CommonQueryID = "HeaderID" },
new LayerSettingEntity { LayerNo = 2, LayerName = "A_Line", CommonQueryID = "LineID" },
new LayerSettingEntity { LayerNo = 3, LayerName = "B_Line", CommonQueryID = "SublineID"},
new LayerSettingEntity { LayerNo = 4, LayerName = "C_Line", CommonQueryID = "ExtralineID" }
};
3、测试调用
图片展示不完整,实现输出的报文与需求的报文格式一致。
五、多种不同的报文层级和名称场景测试
1、一层的使用不到配置,故不作测试
2、两层测试,模拟数据
// 模拟数据集
DataSet rawData = new DataSet();
// 表1
DataTable table1 = new DataTable();
table1.Columns.Add("HeaderID");
table1.Columns.Add("A1");
table1.Columns.Add("A2");
table1.Columns.Add("A3");
DataRow row1 = table1.NewRow();
row1["HeaderID"] = "1";
row1["A1"] = "A1";
row1["A2"] = "A2";
row1["A3"] = "A3";
table1.Rows.Add(row1);
DataRow row2 = table1.NewRow();
row2["HeaderID"] = "2";
row2["A1"] = "A11";
row2["A2"] = "A22";
row2["A3"] = "A33";
table1.Rows.Add(row2);
rawData.Tables.Add(table1);
// 表2
DataTable table2 = new DataTable();
table2.Columns.Add("HeaderID");
table2.Columns.Add("LineID");
table2.Columns.Add("B1");
table2.Columns.Add("B2");
table2.Columns.Add("B3");
DataRow row3 = table2.NewRow();
row3["HeaderID"] = "1";
row3["LineID"] = "1";
row3["B1"] = "B11";
row3["B2"] = "B22";
row3["B3"] = "B31";
table2.Rows.Add(row3);
DataRow row4 = table2.NewRow();
row4["HeaderID"] = "1";
row4["LineID"] = "2";
row4["B1"] = "2B1";
row4["B2"] = "2B2";
row4["B3"] = "1B3";
table2.Rows.Add(row4);
DataRow row5 = table2.NewRow();
row5["HeaderID"] = "1";
row5["LineID"] = "3";
row5["B1"] = "4B1";
row5["B2"] = "B21";
row5["B3"] = "B31";
table2.Rows.Add(row5);
DataRow row6 = table2.NewRow();
row6["HeaderID"] = "2";
row6["LineID"] = "1";
row6["B1"] = "5B1";
row6["B2"] = "5B2";
row6["B3"] = "1";
table2.Rows.Add(row6);
rawData.Tables.Add(table2);
// 配置层级字段名
List<LayerSettingEntity> LayerSetting = new List<LayerSettingEntity>
{
new LayerSettingEntity { LayerNo = 1, LayerName = "reqData",CommonQueryID = "HeaderID" },
new LayerSettingEntity { LayerNo = 2, LayerName = "两层_Line", CommonQueryID = "LineID" }
};
3、三层测试 ,模拟数据
// 模拟数据集
DataSet rawData = new DataSet();
// 表1
DataTable table1 = new DataTable();
table1.Columns.Add("HeaderID");
table1.Columns.Add("A1");
table1.Columns.Add("A2");
table1.Columns.Add("A3");
DataRow row1 = table1.NewRow();
row1["HeaderID"] = "1";
row1["A1"] = "A1";
row1["A2"] = "A2";
row1["A3"] = "A3";
table1.Rows.Add(row1);
DataRow row2 = table1.NewRow();
row2["HeaderID"] = "2";
row2["A1"] = "A11";
row2["A2"] = "A22";
row2["A3"] = "A33";
table1.Rows.Add(row2);
rawData.Tables.Add(table1);
// 表2
DataTable table2 = new DataTable();
table2.Columns.Add("HeaderID");
table2.Columns.Add("LineID");
table2.Columns.Add("B1");
table2.Columns.Add("B2");
table2.Columns.Add("B3");
DataRow row3 = table2.NewRow();
row3["HeaderID"] = "1";
row3["LineID"] = "1";
row3["B1"] = "B11";
row3["B2"] = "B22";
row3["B3"] = "B31";
table2.Rows.Add(row3);
DataRow row4 = table2.NewRow();
row4["HeaderID"] = "1";
row4["LineID"] = "2";
row4["B1"] = "2B1";
row4["B2"] = "2B2";
row4["B3"] = "1B3";
table2.Rows.Add(row4);
DataRow row5 = table2.NewRow();
row5["HeaderID"] = "1";
row5["LineID"] = "3";
row5["B1"] = "4B1";
row5["B2"] = "B21";
row5["B3"] = "B31";
table2.Rows.Add(row5);
DataRow row6 = table2.NewRow();
row6["HeaderID"] = "2";
row6["LineID"] = "1";
row6["B1"] = "5B1";
row6["B2"] = "5B2";
row6["B3"] = "1";
table2.Rows.Add(row6);
rawData.Tables.Add(table2);
// 表3
DataTable table3 = new DataTable();
table3.Columns.Add("HeaderID");
table3.Columns.Add("LineID");
table3.Columns.Add("SublineID");
table3.Columns.Add("C1");
table3.Columns.Add("C2");
DataRow row7 = table3.NewRow();
row7["HeaderID"] = "1";
row7["LineID"] = "1";
row7["SublineID"] = "1";
row7["C1"] = "C1";
row7["C2"] = "C2001";
table3.Rows.Add(row7);
DataRow row8 = table3.NewRow();
row8["HeaderID"] = "1";
row8["LineID"] = "1";
row8["SublineID"] = "2";
row8["C1"] = "DC1N";
row8["C2"] = "C2002";
table3.Rows.Add(row8);
DataRow row9 = table3.NewRow();
row9["HeaderID"] = "1";
row9["LineID"] = "2";
row9["SublineID"] = "1";
row9["C1"] = "DC1";
row9["C2"] = "003";
table3.Rows.Add(row9);
DataRow row10 = table3.NewRow();
row10["HeaderID"] = "1";
row10["LineID"] = "2";
row10["SublineID"] = "2";
row10["C1"] = "C1N";
row10["C2"] = "004";
table3.Rows.Add(row10);
DataRow row11 = table3.NewRow();
row11["HeaderID"] = "1";
row11["LineID"] = "3";
row11["SublineID"] = "1";
row11["C1"] = "";
row11["C2"] = "005";
table3.Rows.Add(row11);
DataRow row12 = table3.NewRow();
row12["HeaderID"] = "2";
row12["LineID"] = "1";
row12["SublineID"] = "1";
row12["C1"] = "C1N";
row12["C2"] = "0066";
table3.Rows.Add(row12);
rawData.Tables.Add(table3);
// 配置层级字段名
List<LayerSettingEntity> LayerSetting = new List<LayerSettingEntity>
{
new LayerSettingEntity { LayerNo = 1, LayerName = "reqData",CommonQueryID = "HeaderID" },
new LayerSettingEntity { LayerNo = 2, LayerName = "两层_Line", CommonQueryID = "LineID" },
new LayerSettingEntity { LayerNo = 3, LayerName = "三层_Line", CommonQueryID = "SublineID"}
};
六、总结
使用可配置和递归调用,实现了一个公共的方法可以支持动态生成不同业务需求的报文格式,减少的重复代码的开发,提升了开发效率,实现代码优化和提升代码的可维护性。