今天来介绍一下Framework系列的配置部分,这一部分归属于Framework-Design之中。读过《Framework系列介绍》的小伙伴应该了解整个Framework框架是由多个工程项目组成,没看过的小伙伴可以点击链接了解一下。
Framework-Design设计的初衷是给策划同学用的,工程包含文档(Documents)和配置(Configs)两个目录。配置目录则包含配表工具(ToolExcel)和导表工具工程(ToolProject)两个部分。
配表工具
我们先来介绍配表工具部分的内容。配表工具目录下包含配表目录(Excels)、客户端类目录(ClientClasses)、客户端Json目录(ClientJsons)、服务器类目录(ServerClasses)、服务器Json目录(ServerJsons)和导表工具(ExcelToJson.exe)
配表目录(Excels)中放置的是Excel配置文件,每个配置文件会导出对应名称的类文件(.cs文件)和Json文件(.json文件)。当不需要导出某个配置的时候可以在文件名前面添加~符号,文件名包含~符号则可以忽略不导出。
配表第一行为备注说明,在导出类文件时会成为成员变量的备注。
配表第二行为字段名,在导出类文件时会用于生成成员变量名,在导出Json文件时会用于生成数据的Key。
配表第三行为数据类型,包含bool,bool[],int,int[],float,float[],string,string[] 8种类型,下拉框选择无需手动填写。
配表第四行为导出类型,包含null,client/server,client,server 4种类型,下拉框选择无需手动填写。null为不导出,client/server为导出前后端,client单导出前端,server单导出后端。
配表第五行一下为数据, 数组数据用,符号分隔。
配表完成之后可以运行ExcelToJson.exe控制台程序导出配置,前后端的类文件和Json文件会分别导出到对应目录,目录不存在会自动创建,无需手动创建。文件夹内的文件也会先清空在生产新的文件。
导表工具工程
接下来介绍一下导表工具的具体实现。之前也有写过导表工具的文章,之前的导表工具是作为Unity的插件来设计的。由于在实际工作中配表工作大部分是有策划同学完成,并且配表数据并不单单只是前端使用,后端也需要用到配置数据,所以配表工具依附于Unity的设计并不合理。这一次的导表工具完全独立出来,作为一个单独的工程项目。
导表工具的实现主要分为三个部分,第一是解析Excel文件,第二是生成类文件,第三是生成Json文件。由于代码比较多,文章里只截取重要部分,完整代码可以在文章最后的工程连接获取。代码中还有许多c#的文件操作方法,不太了解的同学可以自行查阅c#官方文档。
using ExcelDataReader;
private string[] mDescriptionArray;
private string[] mFieldArray;
private string[] mTypeArray;
private string[] mPlatformArray;
/// <summary>Excel结构枚举</summary>
public enum EnumExcelStruct
{
/// <summary>描述</summary>
Description = 0,
/// <summary>字段名</summary>
Field,
/// <summary>类型</summary>
Type,
/// <summary>平台</summary>
Platform,
/// <summary>数据</summary>
Data,
}
/// <summary>解析配置</summary>
/// <param name="configPath">配置路径</param>
private void ParseConfig(string pConfigPath)
{
FileStream stream = File.OpenRead(pConfigPath);
IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream);
while (excelReader.Read())
{
if (excelReader.Depth == (int)EnumExcelStruct.Description)
{
mDescriptionArray = GetDatas(excelReader);
}
else if (excelReader.Depth == (int)EnumExcelStruct.Field)
{
mFieldArray = GetDatas(excelReader);
}
else if (excelReader.Depth == (int)EnumExcelStruct.Type)
{
mTypeArray = GetDatas(excelReader);
}
else if(excelReader.Depth == (int)EnumExcelStruct.Platform)
{
mPlatformArray = GetDatas(excelReader);
}
else
{
break;
}
}
excelReader.Close();
}
/// <summary>获取数据</summary>
/// <param name="excelReader">excelReader</param>
private string[] GetDatas(IExcelDataReader excelReader)
{
string[] datas = new string[excelReader.FieldCount];
for (int i = 0; i < excelReader.FieldCount; i++)
{
datas[i] = excelReader.GetString(i);
}
return datas;
}
这里主要展示的是解析Excel数据的代码。读取Excel数据需要用c#自带的ExcelDataReader库,通过using包含就可以使用了。通过ExcelReaderFactory.CreateOpenXmlReader接口获取IExcelDataReader对象,通过调用IExcelDataReader对象的Read接口会逐行读取Excel中的数据。这里为了后续生成类文件和Json文件做准备,所以将数据存入了不同的string[]中。
IExcelDataReader在读取Excel数据时可以理解成在读取一个二维数组,每调用一次Read接口会读取一行里的数据,然后通过列索引获取对应数据。
using System.IO;
using System.Text;
private string className;
private string[] mDescriptionArray;
private string[] mFieldArray;
private string[] mTypeArray;
private string[] mPlatformArray;
private StringBuilder stringBuilder = new StringBuilder();
/// <summary>生成代码</summary>
private void GenerateCode()
{
stringBuilder.Clear();
stringBuilder.Append("/*Auto-create script.\n");
stringBuilder.Append(" * Don't Edit it. */\n");
stringBuilder.Append("\n");
stringBuilder.Append("using System.Collections.Generic;\n");
stringBuilder.Append("using Framework.Data;\n");
stringBuilder.Append("\n");
stringBuilder.Append("namespace Game.Config\n");
stringBuilder.Append("{\n");
stringBuilder.AppendFormat("\tpublic class {0} : ConfigBase\n", className);
stringBuilder.Append("\t{\n");
for (int i = 0; i < mDescriptionArray.Length; i++)
{
if (CheckPlatform(mPlatformArray[i]))
{
stringBuilder.AppendFormat("\t\t/// <summary>{0}</summary>\n", mDescriptionArray[i]);
if (i == 0)
stringBuilder.Append("\t\tpublic object id;\n");
else
stringBuilder.AppendFormat("\t\tpublic {0} {1};\n", mTypeArray[i], mFieldArray[i]);
}
}
stringBuilder.Append("\t}\n");
stringBuilder.Append("}");
}
/// <summary>输出文件</summary>
private void ExportFile()
{
string path = string.Format("{0}/{1}.cs", mExportPath, className);
string content = stringBuilder.ToString();
File.WriteAllText(path, content);
}
生成类文件的本质其实就是生成后缀名为.cs的文本文件,文本的编辑我们则使用到的是StringBuilder,文本编辑我们则使用到之前Excel解析出来的数据。编辑完成后将StringBuilder转换成string,最后通过File库将文件生成出来。
using System;
using System.IO;
using System.Text;
using ExcelDataReader;
using Newtonsoft.Json;
namespace ExcelToJson
{
public class JsonGenerator
{
private string mExportPath = "";
private PlatformType mPlatformType = PlatformType.None;
private string className;
private string[] mFieldArray;
private string[] mTypeArray;
private string[] mPlatformArray;
private string[] mDataArray;
private StringBuilder stringBuilder;
private JsonTextWriter jsonTextWriter;
public void Init(string pExportPath, PlatformType pPlatformType)
{
mExportPath = pExportPath;
mPlatformType = pPlatformType;
if (!Directory.Exists(mExportPath))
{
Directory.CreateDirectory(mExportPath);
}
}
private void Reset()
{
stringBuilder = new StringBuilder();
StringWriter stringWriter = new StringWriter(stringBuilder);
jsonTextWriter = new JsonTextWriter(stringWriter);
jsonTextWriter.Formatting = Formatting.Indented;
}
/// <summary>生成Json</summary>
/// <param name="pConfigPath">配置路径</param>
public void GenerateJson(FileSystemInfo pFileSystemInfo)
{
if (File.Exists(pFileSystemInfo.FullName))
{
int index = pFileSystemInfo.Name.IndexOf(".");
className = pFileSystemInfo.Name.Substring(0, index);
Reset();
GenerateJsonCode(pFileSystemInfo.FullName);
ExportFile();
}
}
/// <summary>生成Json代码</summary>
/// <param name="pConfigPath">配置路径</param>
private void GenerateJsonCode(string pConfigPath)
{
FileStream stream = File.OpenRead(pConfigPath);
IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream);
while (excelReader.Read())
{
if (excelReader.Depth == (int)EnumExcelStruct.Field)
{
mFieldArray = GetDatas(excelReader);
}
else if (excelReader.Depth == (int)EnumExcelStruct.Type)
{
mTypeArray = GetDatas(excelReader);
}
else if (excelReader.Depth == (int)EnumExcelStruct.Platform)
{
mPlatformArray = GetDatas(excelReader);
}
else if (excelReader.Depth == (int)EnumExcelStruct.Data)
{
if (!CheckPlatform(mPlatformArray[0]))
return;
WriteStart();
mDataArray = GetDatas(excelReader);
AppendJson(mTypeArray, mFieldArray, mPlatformArray, mDataArray);
}
else if (excelReader.Depth > (int)EnumExcelStruct.Data)
{
mDataArray = GetDatas(excelReader);
AppendJson(mTypeArray, mFieldArray, mPlatformArray, mDataArray);
}
}
jsonTextWriter.WriteEnd();
excelReader.Close();
}
/// <summary>写入开始</summary>
private void WriteStart()
{
string type = mTypeArray[0];
if (type == "int")
jsonTextWriter.WriteStartArray();
else if (type == "string")
jsonTextWriter.WriteStartObject();
}
/// <summary>获取数据</summary>
/// <param name="excelReader">excelReader</param>
/// <returns></returns>
private string[] GetDatas(IExcelDataReader excelReader)
{
string[] datas = new string[excelReader.FieldCount];
for (int i = 0; i < excelReader.FieldCount; i++)
{
datas[i] = excelReader.GetString(i);
}
return datas;
}
private bool CheckPlatform(string pPlatformKey)
{
if (mPlatformType == PlatformType.Client && (pPlatformKey == PlatformDefine.cCSKey || pPlatformKey == PlatformDefine.cCKey))
return true;
else if (mPlatformType == PlatformType.Server && (pPlatformKey == PlatformDefine.cCSKey || pPlatformKey == PlatformDefine.cSKey))
return true;
return false;
}
/// <summary>添加Json</summary>
/// <param name="types">类型</param>
/// <param name="fields">字段名</param>
/// <param name="datas">数据</param>
private void AppendJson(string[] types, string[] fields, string[] platform, string[] datas)
{
if (types[0] == "string")
jsonTextWriter.WritePropertyName(datas[0]);
jsonTextWriter.WriteStartObject();
for (int i = 0; i < types.Length; i++)
{
if (!CheckPlatform(platform[i]))
continue;
switch (types[i])
{
case TypeDefine.cBool:
AppendBool(fields[i], datas[i]);
break;
case TypeDefine.cBoolArray:
AppendBoolArray(fields[i], datas[i]);
break;
case TypeDefine.cInt:
AppendInt(fields[i], datas[i]);
break;
case TypeDefine.cIntArray:
AppendIntArray(fields[i], datas[i]);
break;
case TypeDefine.cFloat:
AppendFloat(fields[i], datas[i]);
break;
case TypeDefine.cFloatArray:
AppendFloatArray(fields[i], datas[i]);
break;
case TypeDefine.cString:
AppendString(fields[i], datas[i]);
break;
case TypeDefine.cStringArray:
AppendStringArray(fields[i], datas[i]);
break;
}
}
jsonTextWriter.WriteEndObject();
}
private void AppendBool(string field, string data)
{
jsonTextWriter.WritePropertyName(field);
if (data == "true")
jsonTextWriter.WriteValue(true);
else if (data == "false")
jsonTextWriter.WriteValue(false);
}
private void AppendBoolArray(string field, string data)
{
jsonTextWriter.WritePropertyName(field);
jsonTextWriter.WriteStartArray();
data = data.Replace("[", "");
data = data.Replace("]", "");
string[] dataArray = data.Split(',');
for (int i = 0; i < dataArray.Length; i++)
{
if (dataArray[i] == "true")
jsonTextWriter.WriteValue(true);
else if (dataArray[i] == "false")
jsonTextWriter.WriteValue(false);
}
jsonTextWriter.WriteEndArray();
}
private void AppendInt(string field, string data)
{
jsonTextWriter.WritePropertyName(field);
jsonTextWriter.WriteValue(int.Parse(data));
}
private void AppendIntArray(string field, string data)
{
jsonTextWriter.WritePropertyName(field);
jsonTextWriter.WriteStartArray();
data = data.Replace("[", "");
data = data.Replace("]", "");
string[] dataArray = data.Split(',');
for (int i = 0; i < dataArray.Length; i++)
{
int value = int.Parse(dataArray[i]);
jsonTextWriter.WriteValue(value);
}
jsonTextWriter.WriteEndArray();
}
private void AppendFloat(string field, string data)
{
jsonTextWriter.WritePropertyName(field);
jsonTextWriter.WriteValue(float.Parse(data));
}
private void AppendFloatArray(string field, string data)
{
jsonTextWriter.WritePropertyName(field);
jsonTextWriter.WriteStartArray();
data = data.Replace("[", "");
data = data.Replace("]", "");
string[] dataArray = data.Split(',');
for (int i = 0; i < dataArray.Length; i++)
{
float value = float.Parse(dataArray[i]);
jsonTextWriter.WriteValue(value);
}
jsonTextWriter.WriteEndArray();
}
private void AppendString(string field, string data)
{
jsonTextWriter.WritePropertyName(field);
jsonTextWriter.WriteValue(data);
}
private void AppendStringArray(string field, string data)
{
jsonTextWriter.WritePropertyName(field);
jsonTextWriter.WriteStartArray();
data = data.Replace("[", "");
data = data.Replace("]", "");
string[] dataArray = data.Split(',');
for (int i = 0; i < dataArray.Length; i++)
{
jsonTextWriter.WriteValue(dataArray[i]);
}
jsonTextWriter.WriteEndArray();
}
/// <summary>输出文件</summary>
private void ExportFile()
{
if (CheckPlatform(mPlatformArray[0]))
{
string path = string.Format("{0}/{1}.json", mExportPath, className);
string content = stringBuilder.ToString();
File.WriteAllText(path, content);
}
}
}
}
与类文件相同,Json文件生成的实质也是生成一个后缀名为.json的文件。除了StringBuilder之外生成json文件还需要用到JsonTextWriter类,使用using包含Newtonsoft.Json库就能使用了。关于JsonTextWriter中的接口小伙伴可以自行查阅接口。
官方文档链接
ExcelDataReader链接:https://github.com/ExcelDataReader/ExcelDataReader
Newtonsoft.Json链接:https://www.newtonsoft.com/json
Newtonsoft.Json文档链接:https://www.newtonsoft.com/json/help/html/Introduction.htm
C#相关类文档链接:文件和流 I/O - .NET | Microsoft Learn、File 类 (System.IO) | Microsoft Learn、Directory 类 (System.IO) | Microsoft Learn