前言


上一篇简单的制作了导出工具的UI界面,那么本篇就来解决excel读取到DataTable后,怎么把这些数据序列化为二进制,以及序列化成功后在游戏中怎么再还原回对应的数据类型的对象这两个问题,完成C#代码和数据的生成。


数据序列化和反序列化

关于什么是序列化什么是反序列化属于常识性问题,这里就不进行解释了(实在不知道就baidu一下、google一下)下面就详细的阐述从策划配置完excel到在游戏中应用这些数据的整个过程。


序列化


使用MemoryStream进行流式的数据读写显然是简单明了且效率极高的一种做法。比起一个一个的手动转换类型然后再进行繁琐的数组写入,这种方式只需要逐个把数据写进流中,写完后调用ToArray()就得到了一个转换好的bytes数组,再把得到的数组直接用文件流写入文件就完成了整个工作。要实现这种序列化方法,需要扩展MemoryStream把数据类型转化的代码封装在使用MemoryStream进行读写之前,即:先把数据转为byte[],再写入流中。于是这里要实现一个MemoryStreamEx,它可以用于各种场景的数据二进制序列化和反序列化。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
public class MemoryStreamEx : MemoryStream
{
public MemoryStreamEx()
{

}

public MemoryStreamEx(byte[] buffer) : base(buffer)
{

}

/// <summary>
/// 从流中读取一个short数据
/// </summary>
/// <returns></returns>
public short ReadShort()
{
byte[] arr = new byte[2];
base.Read(arr, 0, 2);
return BitConverter.ToInt16(arr, 0);
}

/// <summary>
/// 把一个short数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteShort(short value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}

/// <summary>
/// 从流中读取一个ushort数据
/// </summary>
/// <returns></returns>
public ushort ReadUShort()
{
byte[] arr = new byte[2];
base.Read(arr, 0, 2);
return BitConverter.ToUInt16(arr, 0);
}

/// <summary>
/// 把一个ushort数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteUShort(ushort value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}

/// <summary>
/// 从流中读取一个int数据
/// </summary>
/// <returns></returns>
public int ReadInt()
{
byte[] arr = new byte[4];
base.Read(arr, 0, 4);
return BitConverter.ToInt32(arr, 0);
}

/// <summary>
/// 把一个int数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteInt(int value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}

/// <summary>
/// 从流中读取一个uint数据
/// </summary>
/// <returns></returns>
public uint ReadUInt()
{
byte[] arr = new byte[4];
base.Read(arr, 0, 4);
return BitConverter.ToUInt32(arr, 0);
}

/// <summary>
/// 把一个uint数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteUInt(uint value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}

/// <summary>
/// 从流中读取一个long数据
/// </summary>
/// <returns></returns>
public long ReadLong()
{
byte[] arr = new byte[8];
base.Read(arr, 0, 8);
return BitConverter.ToInt64(arr, 0);
}

/// <summary>
/// 把一个long数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteLong(long value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}

/// <summary>
/// 从流中读取一个ulong数据
/// </summary>
/// <returns></returns>
public ulong ReadULong()
{
byte[] arr = new byte[4];
base.Read(arr, 0, 4);
return BitConverter.ToUInt64(arr, 0);
}

/// <summary>
/// 把一个ulong数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteULong(ulong value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}

/// <summary>
/// 从流中读取一个float数据
/// </summary>
/// <returns></returns>
public float ReadFloat()
{
byte[] arr = new byte[4];
base.Read(arr, 0, 4);
return BitConverter.ToSingle(arr, 0);
}

/// <summary>
/// 把一个float数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteFloat(float value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}

/// <summary>
/// 从流中读取一个double数据
/// </summary>
/// <returns></returns>
public double ReadDouble()
{
byte[] arr = new byte[8];
base.Read(arr, 0, 8);
return BitConverter.ToDouble(arr, 0);
}

/// <summary>
/// 把一个double数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteDouble(double value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}

/// <summary>
/// 从流中读取一个bool数据
/// </summary>
/// <returns></returns>
public bool ReadBool()
{
return base.ReadByte() == 1;
}

/// <summary>
/// 把一个bool数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteBool(bool value)
{
base.WriteByte((byte)(value == true ? 1 : 0));
}

/// <summary>
/// 从流中读取一个sting
/// </summary>
/// <returns></returns>
public string ReadUTF8String()
{
ushort len = this.ReadUShort();
byte[] arr = new byte[len];
base.Read(arr, 0, len);
return Encoding.UTF8.GetString(arr);
}

/// <summary>
/// 把一个string数据写入流
/// </summary>
/// <param name="str"></param>
public void WriteUTF8String(string str)
{
byte[] arr = Encoding.UTF8.GetBytes(str);
if (arr.Length > 65535)
{
throw new InvalidCastException("字符串超出范围");
}
WriteUShort((ushort)arr.Length);
base.Write(arr, 0, arr.Length);
}
}

可以看到,不同类型反序列化的时候读取的字节长度都不相同,其实这些计算机基础里都有讲过:int(int32)占4个字节=32个二进制位,short(int16)占2个字节=16个二进制位,long(int64)占8个二进制位=64个二进制位,bool占一个字节=8个二进制位,byte=一个字节=8个二进制位……等等,而这里的数据都是以byte(字节)作为单位的,而像读取int的代码中base.Read(arr, 0, 4);长度为什么是4就不难理解了。




经过上面操作,数据序列化为二进制已经解决了,接下来还要再写入些什么才能在游戏中把它们顺利的读取出来呢?在TCP通信的文章中有介绍过,解决粘包的方法是在包头写入包体长度,那这里是否也可以计算一个数据的总长把它写入到包头呢,甚至把它的类型也写入?其实只要认真观察一下excel的配置格式就看得出,每一列的数据类型都是一样的,而所有的数据类型的声明都在第二行,如果每个数据都写入长度和类型难免会显得有些冗余,所以只需要计算第四行以后的包含真实数据的总行数总列数写入包头,再把数据全部写入,读取的时候先读出行列数,根据这个行列数逐行逐列读取就可以了。

PS:包含数据的总行数=excel表总行数 - 3,前四行除了第一行的字段名其他的都不需要写入;总列数=excel表总列数 - 1,第一列不需要写入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
protected override void ExportData(DataTable dt, string excelName, string sheetName)
{
    byte[] buffer = null;
    string dataTableName = dt.Rows[1][0].ToString();//第二行第一列为表名

    using (MemoryStreamEx mse = new MemoryStreamEx())
    {
        mse.WriteInt(dt.Rows.Count - 3);//1.写入行数
        mse.WriteInt(dt.Columns.Count - 1);//2.写入列数

        for (int i = 1; i < dt.Columns.Count; i++)//3.写入字段名
        {
            mse.WriteUTF8String(dt.Rows[0][i].ToString().Trim());
        }

        for (int i = 4; i < dt.Rows.Count; i++)//4.把所有的数据全部转换为string并写入
        {
            for (int j = 1; j < dt.Columns.Count; j++)
            {
                mse.WriteUTF8String(dt.Rows[i][j].ToString().Trim());
            }
        }

        buffer = mse.ToArray();
    }

    //压缩
    buffer = ZlibHelper.CompressBytes(buffer);

    //写入文件
    FileStream fs = new FileStream(string.Format("{0}/C#/Data/{1}ConfigData.bytes", m_ExportPath, dataTableName), FileMode.Create);
    fs.Write(buffer, 0, buffer.Length);
    fs.Close();

    string[,] dataArr = new string[dt.Columns.Count - 1, 3];

    for (int i = 0; i < 3; i++)
    {
        for (int j = 1; j < dt.Columns.Count; j++)
        {
            dataArr[j - 1, i] = dt.Rows[i][j].ToString().Trim();
        }
    }
    
    CreateDataScript(dt, excelName, sheetName, dataTableName);
}

序列化的核心代码就是这些,整个流程为:

  1. 写入行数

  2. 写入列数

  3. 写入字段名

  4. 把所有的数据全部转换为string并写入

  5. 压缩

  6. 写入到文件中


反序列化


经过上面序列化操作后就得到一个写满二进制数据的bytes文件,而想要在游戏中使用它就需要把它的数据逐个读取出来。有了上面序列化的基础,反序列化只需要把上面序列化的操作倒过来即可。这里就可以再实现一个ConfigDataParser用于游戏中的反序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
public class ConfigDataParser : IDisposable
{
    /// <summary>
    /// 行数
    /// </summary>
    public int row { get; private set; }

    /// <summary>
    /// 列数
    /// </summary>
    public int column { get; private set; }

    /// <summary>
    /// 字段名称
    /// </summary>
    public string[] fieldName
    {
        get { return m_FieldName; }
    }

    /// <summary>
    /// 是否结束
    /// </summary>
    public bool eof
    {
        get
        {
            return m_CurrRow == row - 1;
        }
    }

    /// <summary>
    /// 构造函数
    /// </summary>
    public ConfigDataParser(byte[] bytes)
    {
        if (bytes == null)
        {
            return;
        }

        m_FieldNameDic = new Dictionary<string, int>();
        byte[] buffer = ZlibHelper.DeCompressBytes(bytes);//1解压缩

        using (MemoryStreamEx mse = new MemoryStreamEx(buffer))//2解析数据到数组
        {
            row = mse.ReadInt();
            column = mse.ReadInt();

            m_GameData = new String[row - 1, column];
            m_FieldName = new string[column];

            for (int i = 0; i < row; i++)
            {
                for (int j = 0; j < column; j++)
                {
                    string str = mse.ReadUTF8String();

                    if (i == 0)//表示读取的是字段
                    {
                        m_FieldName[j] = str;
                        m_FieldNameDic[str] = j;
                    }
                    else//表示读取的是数据
                    {
                        m_GameData[i - 1, j] = str;
                    }
                }
            }
        }
    }

    /// <summary>
    /// 转到下一条
    /// </summary>
    public void Next()
    {
        if (eof)
        {
            return;
        }
        m_CurrRow++;
    }

    /// <summary>
    /// 获取字段值
    /// </summary>
    /// <returns></returns>
    public string GetFieldValue(string fieldName)
    {
        try
        {
            if (m_CurrRow < 0 || m_CurrRow >= row)
            {
                return null;
            }

            return m_GameData[m_CurrRow, m_FieldNameDic[fieldName]];
        }
        catch
        {
            return null;
        }
    }

    /// <summary>
    /// 释放
    /// </summary>
    public void Dispose()
    {
        m_FieldNameDic.Clear();
        m_FieldNameDic = null;

        m_FieldName = null;
        m_GameData = null;
    }

    /// <summary>
    /// 字段名称
    /// </summary>
    private string[] m_FieldName;

    /// <summary>
    /// 游戏数据
    /// </summary>
    private string[,] m_GameData;

    /// <summary>
    /// 当前行号
    /// </summary>
    private int m_CurrRow = 0;

    /// <summary>
    /// 字段名称字典
    /// </summary>
    private Dictionary<string, int> m_FieldNameDic;
}

整个反序列化核心逻辑都在构造函数里面,过程为:

  1. 解压缩

  2. 读取行数

  3. 读取列数

  4. 读取字段名

  5. 读取数据

使用的时候先读取出文件内容,然后创建ConfigDataParser对象并传入数据,用eof字段判断是否到达文件结尾,未到达则循环创建数据对象,类似下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static T[] LoadConfigData<T>(string filePath, string fileName) where T : BaseConfigData, new()
{
    string path = string.Format(filePath + "{0}", fileName);

    T[] t = null;

    TextAsset txt = AssetDatabase.LoadAssetAtPath<TextAsset>(path + ".bytes");

    if (txt == null || txt.bytes == null)
    {
        Debug.LogError("Config data not found: ", path);
        return null;
    }

    using (ConfigDataParser parser = new ConfigDataParser(txt.bytes))
    {
        t = new T[parser.row - 1];
        int index = 0;
        while (!parser.eof)
        {
            t[index] = new T();
            t[index].Read(parser);
            parser.Next();
            index++; ;
        }
    }
    return t;
}

这里的BaseConfigData就是一个抽象类,定义了一个id字段方法Read(),工具生成的代码都要继承它并重写Read方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class BaseConfigData
{
    public int id
    {
        get
        {
            return m_Id;
        }
        set
        {
            m_Id = value;
        }
    }

    public abstract void Read(ConfigDataParser parser);

    private int m_Id;
}

代码生成


有了数据后还需要把这些数据还原成对应的类的对象才能应用到游戏中。实际上,策划在excel中配置的数据类型是多种多样的,几乎会涵盖int,float,double,long,string,bool等所有类型,而一旦把一个数据转为二进制,那它原来是什么类型就不知道了,这样就很难还原回去。

此时再回过头去观察excel,又不难发现,其实在excel中前三行已经分别标注了数据的字段名、类型、注释。上面在序列化的时候把所有的数据都转为了string类型,那么它原本是什么类型,只需要在生成的代码中加入一个把string转为excel中标注的类型的方法就解决了这个问题。这个功能的实现其实还是比较简单的,直接贴代码吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
private void CreateDataScript(DataTable dt, string excelName, string sheetName, string dataTableName)
{
    string dataTableName = dt.Rows[1][0].ToString();
    string[,] dataArr = new string[dt.Columns.Count - 1, 3];

    for (int i = 0; i < 3; i++)
    {
        for (int j = 1; j < dt.Columns.Count; j++)
        {
            dataArr[j - 1, i] = dt.Rows[i][j].ToString().Trim();
        }
    }

    StringBuilder sb = new StringBuilder();
    sb.Append("\r\n");
    sb.Append("//===================================================\r\n");
    sb.Append("//作者:WuWu                                          \r\n");
    sb.AppendFormat("//创建时间:{0}\r\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    sb.Append("//备注:此代码为工具生成 请勿手工修改\r\n");
    sb.Append("//===================================================\r\n");
    sb.Append("using GameFrameWork;\r\n");
    sb.Append("using GameFrameWork.ConfigData;\r\n");
    sb.Append("using LitJson;\r\n");
    sb.Append("using System;\r\n");
    sb.Append("using System.Collections;\r\n");
    sb.Append("using UnityEngine;\r\n");
    sb.Append("\r\n");
    sb.Append("/// <summary>\r\n");
    sb.AppendFormat("/// {0}数据表\r\n", excelName);
    sb.AppendFormat("/// SheetName:{0}\r\n", sheetName);
    sb.Append("/// </summary>\r\n");
    sb.AppendFormat("public class {0}ConfigData : BaseConfigData\r\n", dataTableName);
    sb.Append("{\r\n");
    
    //生成Json实体类代码
    Dictionary<string, string> jsonDic = new Dictionary<string, string>();

    for (int i = 1; i < dataArr.GetLength(0); i++)
    {
        string typeName = GetTypeName(dataArr[i, 1]);

        if (typeName.Equals("json"))
        {
            typeName = dataArr[i, 0].Substring(0, 1).ToUpper() + dataArr[i, 0].Substring(1);

            for (int j = 4; j < dt.Rows.Count; j++)
            {
                string jsonStr = dt.Rows[j][i + 1].ToString();

                if (!string.IsNullOrEmpty(jsonStr))
                {
                    jsonDic.Add(typeName, jsonStr);
                    break;
                }
            }
        }
    }

    foreach (KeyValuePair<string, string> kvp in jsonDic)
    {
        LitJson.JsonData obj = LitJson.JsonMapper.ToObject(kvp.Value);
        JsonStruct jsonStruct = new JsonStruct();
        jsonStruct.className = kvp.Key;
        ParseJson(obj, jsonStruct);
        CreateJsonCode(jsonStruct, sb);
    }

    //生成字段代码
    for (int i = 1; i < dataArr.GetLength(0); i++)
    {
        string typeName = GetTypeName(dataArr[i, 1]);

        if (typeName.Equals("json"))
        {
            typeName = dataArr[i, 0].Substring(0, 1).ToUpper() + dataArr[i, 0].Substring(1);
        }

        sb.Append("\t/// <summary>\r\n");
        sb.AppendFormat("\t/// {0}\r\n", dataArr[i, 2]);
        sb.Append("\t/// </summary>\r\n");
        sb.AppendFormat("\tpublic {0} {1} {{ get; private set; }}\r\n", typeName, dataArr[i, 0]);
        sb.Append("\r\n");
    }

    //生成克隆代码

    string variableName = dataTableName.Substring(0, 1).ToLower() + dataTableName.Substring(1);

    sb.AppendFormat("\tpublic {0}ConfigData Clone()\r\n", dataTableName);
    sb.Append("\t{\r\n");
    sb.AppendFormat("\t\t{0}ConfigData {1}ConfigData = new {2}ConfigData();\r\n", dataTableName, variableName, dataTableName);

    for (int i = 1; i < dataArr.GetLength(0); i++)
    {
        sb.AppendFormat("\t\t{0}ConfigData.{1} = this.{2};", variableName, dataArr[i, 0], dataArr[i, 0]);
        sb.Append("\r\n");
    }

    sb.AppendFormat("\t\treturn {0}ConfigData;\r\n", variableName);
    sb.Append("\t}\r\n");
    sb.Append("\r\n");

    //生成解析代码
    sb.AppendFormat("\tpublic override void Read(ConfigDataParser parser)\r\n");
    sb.Append("\t{\r\n");

    for (int i = 0; i < dataArr.GetLength(0); i++)
    {
        if (string.IsNullOrEmpty(dataArr[i, 0]))
        {
            continue;
        }

        string fieldName = dataArr[i, 0].Substring(0, 1).ToLower() + dataArr[i, 0].Substring(1);
        string typeName = GetTypeName(dataArr[i, 1]);

        if (typeName.Equals("json"))
        {
            typeName = dataArr[i, 0].Substring(0, 1).ToUpper() + dataArr[i, 0].Substring(1);
            sb.AppendFormat("\t\tthis.{0} = JsonMapper.ToObject<{1}>(parser.GetFieldValue(\"{0}\"));\r\n", fieldName, typeName);
        }
        else
        {
            sb.AppendFormat("\t\tthis.{0} = parser.GetFieldValue(\"{0}\"){1};\r\n", fieldName, GetTypeParseStr(typeName));
        }
    }

    sb.Append("\t}\r\n");
    sb.Append("}\r\n");

    //写入文件
    using (FileStream fs = new FileStream(string.Format("{0}/C#/Script/{1}ConfigData.cs", m_ExportPath, dataTableName), FileMode.Create))
    {
        using (StreamWriter sw = new StreamWriter(fs))
        {
            sw.Write(sb.ToString());
        }
    }
}

private string GetTypeName(string typeName)
{
    return typeName.ToLower() switch
    {
        "int" => "int",
        "long" => "long",
        "float" => "float",
        "double" => "double",
        "bool" => "bool",
        "string" => "string",
        "vector2" => "Vector2",
        "vector3" => "Vector3",
        "int[]" => "int[]",
        "long[]" => "long[]",
        "float[]" => "float[]",
        "double[]" => "double[]",
        "bool[]" => "bool[]",
        "string[]" => "string[]",
        "json" => "json",
        _ => string.Empty,
    };
}

private string GetTypeParseStr(string typeName)
{
    return typeName.ToLower() switch
    {
        "int" => ".ToInt()",
        "long" => ".ToLong()",
        "float" => ".ToFloat()",
        "double" => ".ToDouble()",
        "bool" => ".ToBool()",
        "string" => string.Empty,
        "vector2" => ".ToVector2()",
        "vector3" => ".ToVector3()",
        "int[]" => ".ToIntArray()",
        "long[]" => ".ToLongArray()",
        "float[]" => ".ToFloatArray()",
        "double[]" => ".ToDoubleArray()",
        "bool[]" => ".ToBoolArray()",
        "string[]" => ".ToStringArray()",
        _ => string.Empty,
    };
}

另外json的解析简单写一下,先把整个表的数据遍历一次过滤出json的字符串加入到一个字典中,再遍历这些json字符串把他们逐个反序列化为对象,然后判断反序列化出来的对象如果是数组就再继续遍历并逐个递归,如果是键值对先判断子阶段是否存在同名字段,不存在就读出key和value生成代码。具体的不多写了,这个功能用的不多,而且太复杂的树、图结构也无法正确解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//json解析代码
private void ParseJson(LitJson.JsonData jsonData, JsonStruct jsonStruct)
{
if (jsonData.IsArray)//如果是数组,进行递归
{
for (int i = 0; i < jsonData.Count; i++)
{
ParseJson(jsonData[i], jsonStruct);
}
}
else if (jsonData.Keys.Count > 0)//否则开始解析字段
{
foreach (KeyValuePair<string, LitJson.JsonData> kvp in jsonData)
{
string key = kvp.Key.Trim();
LitJson.JsonData val = kvp.Value;
string fieldType = JsonFieldType(val);
string fieldName = key.Substring(0, 1).ToLower() + key.Substring(1);

if (string.IsNullOrEmpty(fieldType))//不是基本类型则开始解析类名
{
fieldType = key.Substring(0, 1).ToUpper() + key.Substring(1);

if (jsonStruct.jsonStructList == null)
{
jsonStruct.jsonStructList = new List<JsonStruct>();
}

JsonStruct childJsonStruct = null;

for (int i = 0; i < jsonStruct.jsonStructList.Count; i++)
{
if (jsonStruct.jsonStructList[i].className.Equals(fieldType))
{
childJsonStruct = jsonStruct.jsonStructList[i];
break;
}
}

if (childJsonStruct == null)
{
childJsonStruct = new JsonStruct();
childJsonStruct.className = fieldType;
jsonStruct.jsonStructList.Add(childJsonStruct);
}

if (!jsonStruct.fields.ContainsKey(fieldName))
{
jsonStruct.fields.Add(fieldName, fieldType + (val.IsArray ? "[]" : string.Empty));
ParseJson(val, childJsonStruct);
}
}
else
{
if (!jsonStruct.fields.ContainsKey(fieldName))
{
jsonStruct.fields.Add(fieldName, fieldType);
}
}
}
}
}

private string JsonFieldType(LitJson.JsonData fieldValue)
{
string fieldType = string.Empty;

if (fieldValue.IsInt)
{
fieldType = "int";
}
else if (fieldValue.IsLong)
{
fieldType = "long";
}
else if (fieldValue.IsDouble)
{
if (fieldValue.ToString().Split('.')[1].Length < 7)
{
fieldType = "float";
}
else
{
fieldType = "double";
}
}
else if (fieldValue.IsBoolean)
{
fieldType = "bool";
}
else if (fieldValue.IsString)
{
fieldType = "string";
}
else if (fieldValue.IsArray && fieldValue.Count > 0)
{
string typeTemp = JsonFieldType(fieldValue[0]);
if (!string.IsNullOrEmpty(typeTemp))
{
return typeTemp + "[]";
}
}

return fieldType;
}

private void CreateJsonCode(JsonStruct jsonStruct, StringBuilder sb, int tCount = 1)
{
for (int i = 0; i < tCount; i++)
{
sb.Append("\t");
}
sb.AppendFormat("public class {0}\r\n", jsonStruct.className);

for (int i = 0; i < tCount; i++)
{
sb.Append("\t");
}

sb.Append("{\r\n");

if (jsonStruct.jsonStructList != null && jsonStruct.jsonStructList.Count > 0)
{
for (int i = 0; i < jsonStruct.jsonStructList.Count; i++)
{
CreateJsonCode(jsonStruct.jsonStructList[i], sb, tCount + 1);
}

sb.Append("\r\n");
}

foreach (KeyValuePair<string, string> kvp in jsonStruct.fields)
{
for (int i = 0; i < tCount + 1; i++)
{
sb.Append("\t");
}

sb.AppendFormat("public {0} {1} {{ get; set; }}\r\n", kvp.Value, kvp.Key);
}

for (int i = 0; i < tCount; i++)
{
sb.Append("\t");
}

sb.Append("}\r\n");
}

class JsonStruct
{
public string className;
public Dictionary<string, string> fields = new Dictionary<string, string>();
public List<JsonStruct> jsonStructList;
}

导出屏蔽


现在可以在导出之前加入屏蔽功能了,在上一篇说过:

  • 第一行第一列留空,其后为字段名

  • 第二行第一列为配置表名,其后为字段类型

  • 第三行第一列留空,其后可以留空或者填入字段含义

  • 第四行第一列填入“BAN”则整张表不导出,其后填入“BAN”则对应列整列不导出

  • 第五行开始,每行第一列填入“BAN”则整行不导出,其后为具体数据

  • 以上填入“BAN”的位置若没有不导出的需求则留空

要实现这个BAN的功能,只需在导出时进行过滤即可。导出时,若表的“第四行第一列填入BAN”则直接跳出导出方法不进行导出,否则先逐列遍历,把“第四行第二列及以后填入BAN”的列全部移除,再逐行遍历,把“第五行开始,每行第一列填入BAN”的行全部移除。经过这几次筛选,剩下的就是需要导出的表了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
private void Export(string filePath)
{
DataTable[] dts = ExcelHelper.ExcelToTable(filePath);

if (dts == null || dts.Length < 1)
{
return;
}

for (int i = 0; i < dts.Length; i++)
{
DataTable dt = dts[i];

if (dt.Rows.Count < 4 || dt.Columns.Count < 1)
{
continue;
}


if (dt.Rows[3][0].ToString().Contains("BAN"))//第四行第一列填入BAN则整张表不导出
{
continue;
}

string excelName = Path.GetFileName(filePath);
string sheetName = dt.TableName;
string dataTableName = dt.Rows[1][0].ToString();

m_DataTableNameList.Add(dataTableName);

//第五行开始,每行第一列如果填入BAN则此行不导出
for (int row = dt.Rows.Count - 1; row > 3; row--)
{
if (dt.Rows[row][0].ToString().Contains("BAN"))
{
dt.Rows.RemoveAt(row);
}
}

//第四行第二列及以后的列如果填入BAN则此列不导出(第一列为id,强制导出)
for (int col = dt.Columns.Count - 1; col > 0; col--)
{
if (col > 1 && dt.Rows[3][col].ToString().Contains("BAN"))
{
dt.Columns.RemoveAt(col);
}
}

ExportData(dt, excelName, sheetName);
}
}

结语


C#的数据生成和代码生成就写完了,下一篇实现Lua的代码生成然后进行测试。