【特性】支持从excel中读入Table列表定义。这样大多数情况下,只通过修改excel文件就能完成添加新表。

main
walon 2021-08-02 12:46:11 +08:00
parent 0e872b5610
commit 0628dd1e6f
2 changed files with 141 additions and 33 deletions

View File

@ -13,9 +13,13 @@ namespace Luban.Job.Cfg.DataSources.Excel
{ {
private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger(); private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger();
private bool OrientRow { get; set; } = true; // 以行为数据读取方向 private const int TITLE_MIN_ROW_NUM = 2;
private const int TITLE_MAX_ROW_NUM = 10;
private const int TITLE_DEFAULT_ROW_NUM = 3;
private int TitleRows { get; set; } = 3; // 默认有三行是标题行. 第一行是字段名,第二行是中文描述,第三行是注释 private bool IsOrientRow { get; set; } = true; // 以行为数据读取方向
public int TitleRows { get; private set; } = TITLE_DEFAULT_ROW_NUM; // 默认有三行是标题行. 第一行是字段名,第二行是中文描述,第三行是注释
public string RawUrl { get; } public string RawUrl { get; }
@ -266,11 +270,11 @@ namespace Luban.Job.Cfg.DataSources.Excel
case "r": case "r":
case "row": case "row":
case "l": case "l":
case "landscape": OrientRow = true; break; case "landscape": IsOrientRow = true; break;
case "c": case "c":
case "column": case "column":
case "p": case "p":
case "portrait": OrientRow = false; break; case "portrait": IsOrientRow = false; break;
default: default:
{ {
throw new Exception($"单元薄 meta 定义 row:{value} 属性值只能为landscape(l,row,r)或portrait(p,row,r)"); throw new Exception($"单元薄 meta 定义 row:{value} 属性值只能为landscape(l,row,r)或portrait(p,row,r)");
@ -282,11 +286,11 @@ namespace Luban.Job.Cfg.DataSources.Excel
{ {
if (!int.TryParse(value, out var v)) if (!int.TryParse(value, out var v))
{ {
throw new Exception($"单元薄 meta 定义 title_rows:{value} 属性值只能为整数[1,10]"); throw new Exception($"单元薄 meta 定义 title_rows:{value} 属性值只能为整数[{TITLE_MIN_ROW_NUM},{TITLE_MAX_ROW_NUM}]");
} }
if (v < 2 || v > 10) if (v < TITLE_MIN_ROW_NUM || v > TITLE_MAX_ROW_NUM)
{ {
throw new Exception($"单元薄 title_rows 应该在 [1,10] 范围内,默认是3"); throw new Exception($"单元薄 title_rows 应该在 [{TITLE_MIN_ROW_NUM},{TITLE_MAX_ROW_NUM}] 范围内,默认是{TITLE_DEFAULT_ROW_NUM}");
} }
TitleRows = v; TitleRows = v;
break; break;
@ -388,7 +392,7 @@ namespace Luban.Job.Cfg.DataSources.Excel
{ {
++rowIndex; // 第1行是 meta 标题及数据行从第2行开始 ++rowIndex; // 第1行是 meta 标题及数据行从第2行开始
// 重点优化横表的headerOnly模式 此模式下只读前几行标题行,不读数据行 // 重点优化横表的headerOnly模式 此模式下只读前几行标题行,不读数据行
if (headerOnly && this.OrientRow && rowIndex >= 10) if (headerOnly && this.IsOrientRow && rowIndex >= 10)
{ {
break; break;
} }
@ -400,7 +404,7 @@ namespace Luban.Job.Cfg.DataSources.Excel
rows.Add(row); rows.Add(row);
} }
if (OrientRow) if (IsOrientRow)
{ {
this._rowColumns = rows; this._rowColumns = rows;
} }
@ -430,7 +434,7 @@ namespace Luban.Job.Cfg.DataSources.Excel
int titleRowNum = 1; int titleRowNum = 1;
if (reader.MergeCells != null) if (reader.MergeCells != null)
{ {
if (OrientRow) if (IsOrientRow)
{ {
foreach (var mergeCell in reader.MergeCells) foreach (var mergeCell in reader.MergeCells)
{ {
@ -444,7 +448,7 @@ namespace Luban.Job.Cfg.DataSources.Excel
foreach (var mergeCell in reader.MergeCells) foreach (var mergeCell in reader.MergeCells)
{ {
if (OrientRow) if (IsOrientRow)
{ {
//if (mergeCell.FromRow <= 1 && mergeCell.ToRow >= 1) //if (mergeCell.FromRow <= 1 && mergeCell.ToRow >= 1)
if (mergeCell.FromRow == 1) if (mergeCell.FromRow == 1)

View File

@ -1,9 +1,11 @@
using Luban.Common.Utils; using Luban.Common.Utils;
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources.Excel; using Luban.Job.Cfg.DataSources.Excel;
using Luban.Job.Cfg.RawDefs; using Luban.Job.Cfg.RawDefs;
using Luban.Job.Cfg.Utils; using Luban.Job.Cfg.Utils;
using Luban.Job.Common.Defs; using Luban.Job.Common.Defs;
using Luban.Job.Common.RawDefs; using Luban.Job.Common.RawDefs;
using Luban.Job.Common.Types;
using Luban.Server.Common; using Luban.Server.Common;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -18,6 +20,8 @@ namespace Luban.Job.Cfg.Defs
{ {
private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger(); private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger();
private readonly List<string> _importExcels = new List<string>();
private readonly List<Branch> _branches = new(); private readonly List<Branch> _branches = new();
private readonly List<Table> _cfgTables = new List<Table>(); private readonly List<Table> _cfgTables = new List<Table>();
@ -30,6 +34,7 @@ namespace Luban.Job.Cfg.Defs
public CfgDefLoader(RemoteAgent agent) : base(agent) public CfgDefLoader(RemoteAgent agent) : base(agent)
{ {
RegisterRootDefineHandler("importexcel", AddImportExcel);
RegisterRootDefineHandler("branch", AddBranch); RegisterRootDefineHandler("branch", AddBranch);
RegisterRootDefineHandler("service", AddService); RegisterRootDefineHandler("service", AddService);
RegisterRootDefineHandler("group", AddGroup); RegisterRootDefineHandler("group", AddGroup);
@ -55,10 +60,22 @@ namespace Luban.Job.Cfg.Defs
}; };
} }
private static readonly List<string> _excelImportRequireAttrs = new List<string> { "name" };
private void AddImportExcel(XElement e)
{
ValidAttrKeys(e, null, _excelImportRequireAttrs);
var importName = e.Attribute("name").Value;
if (string.IsNullOrWhiteSpace(importName))
{
throw new Exception("importexcel 属性name不能为空");
}
this._importExcels.Add(importName);
}
private static readonly List<string> _branchRequireAttrs = new List<string> { "name" }; private static readonly List<string> _branchRequireAttrs = new List<string> { "name" };
private void AddBranch(XElement e) private void AddBranch(XElement e)
{ {
ValidAttrKeys(e, null, _branchRequireAttrs);
var branchName = e.Attribute("name").Value; var branchName = e.Attribute("name").Value;
if (string.IsNullOrWhiteSpace(branchName)) if (string.IsNullOrWhiteSpace(branchName))
{ {
@ -194,10 +211,10 @@ namespace Luban.Job.Cfg.Defs
} }
case "map": case "map":
{ {
if ((string.IsNullOrWhiteSpace(indexStr) || indexStr.Split(',').Length != 1)) //if ((string.IsNullOrWhiteSpace(indexStr) || indexStr.Split(',').Length != 1))
{ //{
throw new Exception($"定义文件:{CurImportFile} table:{tableName} 是单键表必须在index属性里指定1个key"); // throw new Exception($"定义文件:{CurImportFile} table:{tableName} 是单键表必须在index属性里指定1个key");
} //}
mode = ETableMode.MAP; mode = ETableMode.MAP;
break; break;
} }
@ -220,18 +237,33 @@ namespace Luban.Job.Cfg.Defs
private void AddTable(XElement e) private void AddTable(XElement e)
{ {
ValidAttrKeys(e, _tableOptionalAttrs, _tableRequireAttrs); ValidAttrKeys(e, _tableOptionalAttrs, _tableRequireAttrs);
string name = XmlUtil.GetRequiredAttribute(e, "name");
string module = CurNamespace;
string valueType = XmlUtil.GetRequiredAttribute(e, "value");
bool defineFromFile = XmlUtil.GetOptionBoolAttribute(e, "define_from_file");
string index = XmlUtil.GetOptionalAttribute(e, "index");
string group = XmlUtil.GetOptionalAttribute(e, "group");
string comment = XmlUtil.GetOptionalAttribute(e, "comment");
string input = XmlUtil.GetRequiredAttribute(e, "input");
string branchInput = XmlUtil.GetOptionalAttribute(e, "branch_input");
string mode = XmlUtil.GetOptionalAttribute(e, "mode");
AddTable(name, module, valueType, index, mode, group, comment, defineFromFile, input, branchInput);
}
private void AddTable(string name, string module, string valueType, string index, string mode, string group,
string comment, bool defineFromExcel, string input, string branchInput)
{
var p = new Table() var p = new Table()
{ {
Name = XmlUtil.GetRequiredAttribute(e, "name"), Name = name,
Namespace = CurNamespace, Namespace = module,
ValueType = XmlUtil.GetRequiredAttribute(e, "value"), ValueType = valueType,
LoadDefineFromFile = XmlUtil.GetOptionBoolAttribute(e, "define_from_file"), LoadDefineFromFile = defineFromExcel,
Index = XmlUtil.GetOptionalAttribute(e, "index"), Index = index,
Groups = CreateGroups(XmlUtil.GetOptionalAttribute(e, "group")), Groups = CreateGroups(group),
Comment = XmlUtil.GetOptionalAttribute(e, "comment"), Comment = comment,
Mode = ConvertMode(name, mode, index),
}; };
p.Mode = ConvertMode(p.Name, XmlUtil.GetOptionalAttribute(e, "mode"), p.Index);
if (p.Groups.Count == 0) if (p.Groups.Count == 0)
{ {
@ -241,12 +273,11 @@ namespace Luban.Job.Cfg.Defs
{ {
throw new Exception($"定义文件:{CurImportFile} table:{p.Name} group:{invalidGroup} 不存在"); throw new Exception($"定义文件:{CurImportFile} table:{p.Name} group:{invalidGroup} 不存在");
} }
p.InputFiles.AddRange(XmlUtil.GetRequiredAttribute(e, "input").Split(',')); p.InputFiles.AddRange(input.Split(','));
var branchInputAttr = e.Attribute("branch_input"); if (!string.IsNullOrWhiteSpace(branchInput))
if (branchInputAttr != null)
{ {
foreach (var subBranchStr in branchInputAttr.Value.Split('|').Select(s => s.Trim()).Where(s => !string.IsNullOrWhiteSpace(s))) foreach (var subBranchStr in branchInput.Split('|').Select(s => s.Trim()).Where(s => !string.IsNullOrWhiteSpace(s)))
{ {
var nameAndDirs = subBranchStr.Split(':'); var nameAndDirs = subBranchStr.Split(':');
if (nameAndDirs.Length != 2) if (nameAndDirs.Length != 2)
@ -261,6 +292,11 @@ namespace Luban.Job.Cfg.Defs
} }
} }
AddTableList(p);
}
private void AddTableList(Table p)
{
if (!_name2CfgTable.TryAdd(p.Name, p)) if (!_name2CfgTable.TryAdd(p.Name, p))
{ {
var exist = _name2CfgTable[p.Name]; var exist = _name2CfgTable[p.Name];
@ -269,9 +305,7 @@ namespace Luban.Job.Cfg.Defs
_cfgTables.Add(p); _cfgTables.Add(p);
} }
private async Task<CfgBean> LoadTableRecordDefineFromFileAsync(Table table, string dataDir)
private async Task<CfgBean> LoadDefineFromFileAsync(Table table, string dataDir)
{ {
var inputFileInfos = await DataLoaderUtil.CollectInputFilesAsync(this.Agent, table.InputFiles, dataDir); var inputFileInfos = await DataLoaderUtil.CollectInputFilesAsync(this.Agent, table.InputFiles, dataDir);
var file = inputFileInfos[0]; var file = inputFileInfos[0];
@ -285,7 +319,8 @@ namespace Luban.Job.Cfg.Defs
var rc = sheet.RowColumns; var rc = sheet.RowColumns;
var attrRow = sheet.RowColumns[0]; var attrRow = sheet.RowColumns[0];
var titleRow = sheet.RowColumns[1]; var titleRow = sheet.RowColumns[1];
var descRow = sheet.RowColumns[2]; // 有可能没有注释行,此时使用标题行,这个是必须有的
var descRow = sheet.TitleRows >= 3 ? sheet.RowColumns[2] : titleRow;
foreach (var f in sheet.RootFields) foreach (var f in sheet.RootFields)
{ {
var cf = new CfgField() { Name = f.Name, Id = 0 }; var cf = new CfgField() { Name = f.Name, Id = 0 };
@ -374,12 +409,12 @@ namespace Luban.Job.Cfg.Defs
return cb; return cb;
} }
public async Task LoadDefinesFromFileAsync(string dataDir) private async Task LoadTableRecordDefinesFromFileAsync(string dataDir)
{ {
var loadTasks = new List<Task<CfgBean>>(); var loadTasks = new List<Task<CfgBean>>();
foreach (var table in this._cfgTables.Where(t => t.LoadDefineFromFile)) foreach (var table in this._cfgTables.Where(t => t.LoadDefineFromFile))
{ {
loadTasks.Add(Task.Run(async () => await this.LoadDefineFromFileAsync(table, dataDir))); loadTasks.Add(Task.Run(async () => await this.LoadTableRecordDefineFromFileAsync(table, dataDir)));
} }
foreach (var task in loadTasks) foreach (var task in loadTasks)
@ -388,6 +423,75 @@ namespace Luban.Job.Cfg.Defs
} }
} }
private async Task LoadTableListFromFileAsync(string dataDir)
{
if (this._importExcels.Count == 0)
{
return;
}
var inputFileInfos = await DataLoaderUtil.CollectInputFilesAsync(this.Agent, this._importExcels, dataDir);
var defTableRecordType = new DefBean(new CfgBean()
{
Namespace = "__intern__",
Name = "__TextInfo__",
Parent = "",
Alias = "",
IsValueType = false,
Sep = "",
TypeId = 0,
IsSerializeCompatible = false,
Fields = new List<Common.RawDefs.Field>
{
new CfgField() { Name = "name", Type = "string" },
new CfgField() { Name = "module", Type = "string" },
new CfgField() { Name = "value_type", Type = "string" },
new CfgField() { Name = "index", Type = "string" },
new CfgField() { Name = "mode", Type = "string" },
new CfgField() { Name = "group", Type = "string" },
new CfgField() { Name = "comment", Type = "string" },
new CfgField() { Name = "define_from_excel", Type = "bool" },
new CfgField() { Name = "input", Type = "string" },
new CfgField() { Name = "branch_input", Type = "string" },
}
})
{
AssemblyBase = new DefAssembly("", null, true, Agent),
};
defTableRecordType.PreCompile();
defTableRecordType.Compile();
defTableRecordType.PostCompile();
var tableRecordType = new TBean(defTableRecordType, false);
foreach (var file in inputFileInfos)
{
var source = new ExcelDataSource();
var bytes = await this.Agent.GetFromCacheOrReadAllBytesAsync(file.ActualFile, file.MD5);
var records = DataLoaderUtil.LoadCfgRecords(tableRecordType, file.OriginFile, null, bytes, true, false);
foreach (var r in records)
{
DBean data = r.Data;
//s_logger.Info("== read text:{}", r.Data);
string name = (data.GetField("name") as DString).Value;
string module = (data.GetField("module") as DString).Value;
string valueType = (data.GetField("value_type") as DString).Value;
string index = (data.GetField("index") as DString).Value;
string mode = (data.GetField("mode") as DString).Value;
string group = (data.GetField("group") as DString).Value;
string comment = (data.GetField("commnet") as DString).Value;
bool isDefineFromExcel = (data.GetField("define_from_excel") as DBool).Value;
string inputFile = (data.GetField("input") as DString).Value;
string branchInput = (data.GetField("branch_input") as DString).Value;
AddTable(name, module, valueType, index, mode, group, comment, isDefineFromExcel, inputFile, branchInput);
};
}
}
public async Task LoadDefinesFromFileAsync(string dataDir)
{
await LoadTableListFromFileAsync(dataDir);
await LoadTableRecordDefinesFromFileAsync(dataDir);
}
private static readonly List<string> _fieldOptionalAttrs = new List<string> { private static readonly List<string> _fieldOptionalAttrs = new List<string> {
"index", "sep", "validator", "key_validator", "value_validator", "index", "sep", "validator", "key_validator", "value_validator",