提交导表相关功能

This commit is contained in:
2025-09-27 17:53:39 +08:00
parent f899a55769
commit c1a3df2192
79 changed files with 4365 additions and 1 deletions

View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36408.4 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NBConfigBuilder", "NBConfigBuilder\NBConfigBuilder.csproj", "{A6264F0E-870D-401D-B1BF-698695AC674C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A6264F0E-870D-401D-B1BF-698695AC674C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6264F0E-870D-401D-B1BF-698695AC674C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6264F0E-870D-401D-B1BF-698695AC674C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6264F0E-870D-401D-B1BF-698695AC674C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F223241C-FF74-4903-8F47-8A5A5A0FAC99}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALogManager_002Ecs_002Fl_003AC_0021_003FUsers_003FFIREBAT_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc5462be99aec4b8dbd0143c73a99d4d1e0000_003F10_003Fa592ee39_003FLogManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANativeWindow_002Ecs_002Fl_003AC_0021_003FUsers_003FFIREBAT_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F257c19112c5249f9bf699efc998471b6cef910_003F44_003Fcc810871_003FNativeWindow_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>

View File

@@ -0,0 +1,20 @@
namespace NBConfigBuilder;
// 添加配置类
public class AppConfig
{
public string ExcelPath { get; set; }
public string ClientPath { get; set; }
public string ClientJsonPath { get; set; }
public string ServerPath { get; set; }
public string ServerJsonPath { get; set; }
public bool GenClient { get; set; }
public bool GenServer { get; set; }
public string ExcelVersionPath => Path.Combine(ExcelPath, "Version.txt");
}
public static class App
{
public static AppConfig Config;
}

View File

@@ -0,0 +1,17 @@
namespace NBConfigBuilder;
/// <summary>
/// 实现了这个接口代表支持对象池
/// </summary>
public interface IPool
{
/// <summary>
/// 是否从池里创建的
/// </summary>
bool IsPool();
/// <summary>
/// 设置是否从池里创建的
/// </summary>
/// <param name="isPool"></param>
void SetIsPool(bool isPool);
}

View File

@@ -0,0 +1,25 @@
namespace NBConfigBuilder;
public partial class IntDictionaryConfig
{
public Dictionary<int, int> Dic;
public int this[int key] => GetValue(key);
public bool TryGetValue(int key, out int value)
{
value = default;
if (!Dic.ContainsKey(key))
{
return false;
}
value = Dic[key];
return true;
}
private int GetValue(int key)
{
return Dic.TryGetValue(key, out var value) ? value : 0;
}
}

View File

@@ -0,0 +1,77 @@
using System.Buffers;
namespace NBConfigBuilder;
public enum MemoryStreamBufferSource
{
None = 0,
Pack = 1,
UnPack = 2,
}
public sealed class MemoryStreamBuffer : MemoryStream, IBufferWriter<byte>
{
public MemoryStreamBufferSource MemoryStreamBufferSource;
public MemoryStreamBuffer()
{
}
public MemoryStreamBuffer(MemoryStreamBufferSource memoryStreamBufferSource, int capacity) : base(capacity)
{
MemoryStreamBufferSource = memoryStreamBufferSource;
}
public MemoryStreamBuffer(byte[] buffer) : base(buffer)
{
}
public void Advance(int count)
{
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count), count, "The value of 'count' cannot be negative.");
}
var newLength = Position + count;
if (newLength != Length)
{
SetLength(newLength);
}
Position = newLength;
}
public Memory<byte> GetMemory(int sizeHint = 0)
{
if (sizeHint < 0)
{
throw new ArgumentOutOfRangeException(nameof(sizeHint), sizeHint,
"The value of 'count' cannot be negative.");
}
if (Length - Position <= sizeHint)
{
SetLength(Position + sizeHint);
}
return new Memory<byte>(GetBuffer(), (int)Position, (int)(Length - Position));
}
public Span<byte> GetSpan(int sizeHint = 0)
{
if (sizeHint < 0)
{
throw new ArgumentOutOfRangeException(nameof(sizeHint), sizeHint,
"The value of 'count' cannot be negative.");
}
if (Length - Position <= sizeHint)
{
SetLength(Position + sizeHint);
}
return new Span<byte>(GetBuffer(), (int)Position, (int)(Length - Position));
}
}

View File

@@ -0,0 +1,217 @@
namespace NBConfigBuilder;
/// <summary>
/// 可回收的、一对多关系的列表池。
/// </summary>
/// <typeparam name="TKey">键的类型。</typeparam>
/// <typeparam name="TValue">值的类型。</typeparam>
public class OneToManyListPool<TKey, TValue> : OneToManyList<TKey, TValue>, IDisposable, IPool where TKey : notnull
{
private bool _isPool;
private bool _isDispose;
/// <summary>
/// 创建一个 <see cref="OneToManyListPool{TKey, TValue}"/> 一对多关系的列表池的实例。
/// </summary>
/// <returns>创建的实例。</returns>
public static OneToManyListPool<TKey, TValue> Create()
{
var list = Pool<OneToManyListPool<TKey, TValue>>.Rent();
list._isDispose = false;
list._isPool = true;
return list;
}
/// <summary>
/// 释放当前对象所占用的资源,并将对象回收到对象池中。
/// </summary>
public void Dispose()
{
if (_isDispose)
{
return;
}
_isDispose = true;
Clear();
Pool<OneToManyListPool<TKey, TValue>>.Return(this);
}
/// <summary>
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
/// </summary>
/// <returns></returns>
public bool IsPool()
{
return _isPool;
}
/// <summary>
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
/// </summary>
/// <param name="isPool"></param>
public void SetIsPool(bool isPool)
{
_isPool = isPool;
}
}
/// <summary>
/// 一对多关系的列表字典。
/// </summary>
/// <typeparam name="TKey">键的类型。</typeparam>
/// <typeparam name="TValue">值的类型。</typeparam>
public class OneToManyList<TKey, TValue> : Dictionary<TKey, List<TValue>> where TKey : notnull
{
private readonly int _recyclingLimit = 120;
private static readonly List<TValue> Empty = new List<TValue>();
private readonly Queue<List<TValue>> _queue = new Queue<List<TValue>>();
/// <summary>
/// 初始化一个新的 <see cref="OneToManyList{TKey, TValue}"/> 实例。
/// </summary>
public OneToManyList()
{
}
/// <summary>
/// 设置最大缓存数量
/// </summary>
/// <param name="recyclingLimit">
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
/// 2:设置成0不控制数量全部缓存
/// </param>
public OneToManyList(int recyclingLimit)
{
_recyclingLimit = recyclingLimit;
}
/// <summary>
/// 判断给定的键和值是否存在于列表中。
/// </summary>
/// <param name="key">要搜索的键。</param>
/// <param name="value">要搜索的值。</param>
/// <returns>如果存在则为 <see langword="true"/>,否则为 <see langword="false"/>。</returns>
public bool Contains(TKey key, TValue value)
{
TryGetValue(key, out var list);
return list != null && list.Contains(value);
}
/// <summary>
/// 向列表中添加指定键和值。
/// </summary>
/// <param name="key">要添加值的键。</param>
/// <param name="value">要添加的值。</param>
public void Add(TKey key, TValue value)
{
if (!TryGetValue(key, out var list))
{
list = Fetch();
list.Add(value);
Add(key, list);
return;
}
list.Add(value);
}
/// <summary>
/// 获取指定键对应的列表中的第一个值。
/// </summary>
/// <param name="key">要获取值的键。</param>
/// <returns>键对应的列表中的第一个值。</returns>
public TValue First(TKey key)
{
return !TryGetValue(key, out var list) ? default : list.FirstOrDefault();
}
/// <summary>
/// 从列表中移除指定键和值。
/// </summary>
/// <param name="key">要移除值的键。</param>
/// <param name="value">要移除的值。</param>
/// <returns>如果成功移除则为 <see langword="true"/>,否则为 <see langword="false"/>。</returns>
public bool RemoveValue(TKey key, TValue value)
{
if (!TryGetValue(key, out var list))
{
return true;
}
var isRemove = list.Remove(value);
if (list.Count == 0)
{
isRemove = RemoveByKey(key);
}
return isRemove;
}
/// <summary>
/// 从列表中移除指定键及其关联的所有值。
/// </summary>
/// <param name="key">要移除的键。</param>
/// <returns>如果成功移除则为 <see langword="true"/>,否则为 <see langword="false"/>。</returns>
public bool RemoveByKey(TKey key)
{
if (!TryGetValue(key, out var list))
{
return false;
}
Remove(key);
Recycle(list);
return true;
}
/// <summary>
/// 获取指定键关联的所有值的列表。
/// </summary>
/// <param name="key">要获取值的键。</param>
/// <returns>键关联的所有值的列表。</returns>
public List<TValue> GetValues(TKey key)
{
if (TryGetValue(key, out List<TValue> list))
{
return list;
}
return Empty;
}
/// <summary>
/// 清除字典中的所有键值对,并回收相关的值集合。
/// </summary>
public new void Clear()
{
foreach (var keyValuePair in this) Recycle(keyValuePair.Value);
base.Clear();
}
/// <summary>
/// 从空闲值集合队列中获取一个值集合,如果队列为空则创建一个新的值集合。
/// </summary>
/// <returns>从队列中获取的值集合。</returns>
private List<TValue> Fetch()
{
return _queue.Count <= 0 ? new List<TValue>() : _queue.Dequeue();
}
/// <summary>
/// 回收一个不再使用的值集合到空闲值集合队列中。
/// </summary>
/// <param name="list">要回收的值集合。</param>
private void Recycle(List<TValue> list)
{
list.Clear();
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
_queue.Enqueue(list);
}
}

View File

@@ -0,0 +1,72 @@
namespace NBConfigBuilder;
/// <summary>
/// 静态的对象池系统,不支持多线程。
/// </summary>
/// <typeparam name="T"></typeparam>
public static class Pool<T> where T : IPool, new()
{
private static readonly Queue<T> PoolQueue = new Queue<T>();
/// <summary>
/// 池子里可用的数量
/// </summary>
public static int Count => PoolQueue.Count;
/// <summary>
/// 租借
/// </summary>
/// <returns></returns>
public static T Rent()
{
return PoolQueue.Count == 0 ? new T() : PoolQueue.Dequeue();
}
/// <summary>
/// 租借
/// </summary>
/// <param name="generator">如果池子里没有,会先执行这个委托。</param>
/// <returns></returns>
public static T Rent(Func<T> generator)
{
return PoolQueue.Count == 0 ? generator() : PoolQueue.Dequeue();
}
/// <summary>
/// 返还
/// </summary>
/// <param name="t"></param>
public static void Return(T t)
{
if (t == null)
{
return;
}
PoolQueue.Enqueue(t);
}
/// <summary>
/// 返还
/// </summary>
/// <param name="t">返还的东西</param>
/// <param name="reset">返还后执行的委托</param>
public static void Return(T t, Action<T> reset)
{
if (t == null)
{
return;
}
reset(t);
PoolQueue.Enqueue(t);
}
/// <summary>
/// 清空池子
/// </summary>
public static void Clear()
{
PoolQueue.Clear();
}
}

View File

@@ -0,0 +1,20 @@
using System.ComponentModel;
namespace Fantasy.Serialize;
public abstract class ASerialize : ISupportInitialize, IDisposable
{
public virtual void Dispose()
{
}
public virtual void BeginInit()
{
}
public virtual void EndInit()
{
}
public virtual void AfterDeserialization() => EndInit();
}

View File

@@ -0,0 +1,25 @@
namespace NBConfigBuilder;
public sealed partial class StringDictionaryConfig
{
public Dictionary<int, string> Dic;
public string this[int key] => GetValue(key);
public bool TryGetValue(int key, out string value)
{
value = default;
if (!Dic.ContainsKey(key))
{
return false;
}
value = Dic[key];
return true;
}
private string GetValue(int key)
{
return Dic.TryGetValue(key, out var value) ? value : null;
}
}

View File

@@ -0,0 +1,169 @@
using System.Reflection;
using Fantasy.Serialize;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using ProtoBuf;
namespace NBConfigBuilder;
/// <summary>
/// 动态程序集类,用于加载动态生成的程序集并获取动态信息。
/// </summary>
public static class DynamicAssembly
{
private static void MetadataReference(out string assemblyName, out List<MetadataReference> metadataReferenceList)
{
AssemblyMetadata assemblyMetadata;
MetadataReference metadataReference;
var currentDomain = AppDomain.CurrentDomain;
assemblyName = Path.GetRandomFileName();
var assemblyArray = currentDomain.GetAssemblies();
metadataReferenceList = new List<MetadataReference>();
// 注册引用
foreach (var domainAssembly in assemblyArray)
{
if (string.IsNullOrEmpty(domainAssembly.Location))
{
continue;
}
assemblyMetadata = AssemblyMetadata.CreateFromFile(domainAssembly.Location);
metadataReference = assemblyMetadata.GetReference();
metadataReferenceList.Add(metadataReference);
}
// 添加Proto支持
assemblyMetadata = AssemblyMetadata.CreateFromFile(typeof(ProtoMemberAttribute).Assembly.Location);
metadataReference = assemblyMetadata.GetReference();
metadataReferenceList.Add(metadataReference);
// 添加Fantasy支持
assemblyMetadata = AssemblyMetadata.CreateFromFile(typeof(ASerialize).Assembly.Location);
metadataReference = assemblyMetadata.GetReference();
metadataReferenceList.Add(metadataReference);
}
/// <summary>
/// 加载指定路径下的动态程序集。
/// </summary>
/// <param name="path">程序集文件路径。</param>
/// <returns>加载的动态程序集。</returns>
public static Assembly Load(string path)
{
var fileList = new List<string>();
// 找到所有需要加载的CS文件
foreach (string file in Directory.GetFiles(path))
{
if (Path.GetExtension(file) != ".cs")
{
continue;
}
fileList.Add(file);
}
var syntaxTreeList = new List<SyntaxTree>();
foreach (var file in fileList)
{
using var fileStream = new StreamReader(file);
var cSharp = CSharpSyntaxTree.ParseText(fileStream.ReadToEnd());
syntaxTreeList.Add(cSharp);
}
// 注册程序集
MetadataReference(out var assemblyName, out var metadataReferenceList);
var compilation = CSharpCompilation.Create(assemblyName, syntaxTreeList, metadataReferenceList, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using var ms = new MemoryStream();
var result = compilation.Emit(ms);
if (!result.Success)
{
foreach (var resultDiagnostic in result.Diagnostics)
{
Log.Error(resultDiagnostic.GetMessage());
}
throw new Exception("failures");
}
ms.Seek(0, SeekOrigin.Begin);
return Assembly.Load(ms.ToArray());
}
/// <summary>
/// 获取动态程序集中指定表格的动态信息。
/// </summary>
/// <param name="dynamicAssembly">动态程序集。</param>
/// <param name="tableName">表格名称。</param>
/// <returns>动态信息对象。</returns>
public static DynamicConfigDataType GetDynamicInfo(Assembly dynamicAssembly, string tableName)
{
var dynamicConfigDataType = new DynamicConfigDataType
{
ConfigDataType = GetConfigType(dynamicAssembly, $"{tableName}Data"),
ConfigType = GetConfigType(dynamicAssembly, $"{tableName}")
};
dynamicConfigDataType.ConfigData = CreateInstance(dynamicConfigDataType.ConfigDataType);
var listPropertyType = dynamicConfigDataType.ConfigDataType.GetProperty("List");
if (listPropertyType == null)
{
throw new Exception("No Property named Add was found");
}
dynamicConfigDataType.Obj = listPropertyType.GetValue(dynamicConfigDataType.ConfigData);
dynamicConfigDataType.Method = listPropertyType.PropertyType.GetMethod("Add");
if (dynamicConfigDataType.Method == null)
{
throw new Exception("No method named Add was found");
}
return dynamicConfigDataType;
}
/// <summary>
/// 根据类型名称获取动态类型。
/// </summary>
/// <param name="dynamicAssembly">动态程序集。</param>
/// <param name="typeName">类型名称。</param>
/// <returns>动态类型。</returns>
private static Type GetConfigType(Assembly dynamicAssembly, string typeName)
{
var configType = dynamicAssembly.GetType($"Fantasy.{typeName}");
if (configType == null)
{
throw new FileNotFoundException($"Fantasy.{typeName} not found");
}
return configType;
// return dynamicAssembly.GetType($"Fantasy.{typeName}");
}
/// <summary>
/// 创建动态实例。
/// </summary>
/// <param name="configType">动态类型。</param>
/// <returns>动态实例。</returns>
public static object CreateInstance(Type configType)
{
var config = Activator.CreateInstance(configType);
if (config == null)
{
throw new Exception($"{configType.Name} is Activator.CreateInstance error");
}
return config;
}
}

View File

@@ -0,0 +1,35 @@
using System.Reflection;
using System.Text;
namespace NBConfigBuilder;
/// <summary>
/// 动态配置数据类型类,用于存储动态配置数据的相关信息。
/// </summary>
public class DynamicConfigDataType
{
/// <summary>
/// 配置数据对象,继承自 AProto 基类。
/// </summary>
public object ConfigData;
/// <summary>
/// 配置数据类型。
/// </summary>
public Type ConfigDataType;
/// <summary>
/// 配置类型。
/// </summary>
public Type ConfigType;
/// <summary>
/// 反射方法信息,用于调用特定方法。
/// </summary>
public MethodInfo Method;
/// <summary>
/// 配置数据对象实例。
/// </summary>
public object Obj;
/// <summary>
/// 用于生成 JSON 格式数据的字符串构建器。
/// </summary>
public StringBuilder Json = new StringBuilder();
}

View File

@@ -0,0 +1,69 @@
using System.Reflection;
using Fantasy.Serialize;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using ProtoBuf;
namespace NBConfigBuilder;
public class OneDynamicAssembly
{
private readonly List<SyntaxTree> _syntaxTreeList = new List<SyntaxTree>();
public void Load(string file)
{
using var fileStream = new StreamReader(file);
var cSharp = CSharpSyntaxTree.ParseText(fileStream.ReadToEnd());
_syntaxTreeList.Add(cSharp);
}
public Assembly Assembly
{
get
{
AssemblyMetadata assemblyMetadata;
MetadataReference metadataReference;
var currentDomain = AppDomain.CurrentDomain;
var assemblyName = Path.GetRandomFileName();
var assemblyArray = currentDomain.GetAssemblies();
var metadataReferenceList = new List<MetadataReference>();
// 注册引用
foreach (var domainAssembly in assemblyArray)
{
if (string.IsNullOrEmpty(domainAssembly.Location))
{
continue;
}
assemblyMetadata = AssemblyMetadata.CreateFromFile(domainAssembly.Location);
metadataReference = assemblyMetadata.GetReference();
metadataReferenceList.Add(metadataReference);
}
// 添加ProtoEntity支持
assemblyMetadata = AssemblyMetadata.CreateFromFile(typeof(ASerialize).Assembly.Location);
metadataReference = assemblyMetadata.GetReference();
metadataReferenceList.Add(metadataReference);
// 添加MessagePack支持
assemblyMetadata = AssemblyMetadata.CreateFromFile(typeof(ProtoMemberAttribute).Assembly.Location);
metadataReference = assemblyMetadata.GetReference();
metadataReferenceList.Add(metadataReference);
CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, _syntaxTreeList, metadataReferenceList, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using var ms = new MemoryStream();
var result = compilation.Emit(ms);
if (!result.Success)
{
foreach (var resultDiagnostic in result.Diagnostics)
{
Log.Error(resultDiagnostic.GetMessage());
}
throw new Exception("failures");
}
ms.Seek(0, SeekOrigin.Begin);
return Assembly.Load(ms.ToArray());
}
}
}

View File

@@ -0,0 +1,950 @@
using System.Collections.Concurrent;
using System.Reflection;
using System.Runtime.Loader;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.VisualBasic.ApplicationServices;
using Newtonsoft.Json;
using OfficeOpenXml;
using static System.String;
namespace NBConfigBuilder;
using TableDictionary = SortedDictionary<string, List<int>>;
/// <summary>
/// Excel 数据导出器,用于从 Excel 文件导出数据到二进制格式和生成 C# 类文件。
/// </summary>
public sealed class ExcelExporter
{
public ExportType ExportType;
private readonly string _excelProgramPath;
private readonly string _versionFilePath;
// private readonly string _excelClientFileDirectory;
// private readonly string _excelServerFileDirectory;
// private readonly string _excelServerJsonDirectory;
// private readonly string _excelClientJsonDirectory;
public VersionInfo VersionInfo; // 存储 Excel 文件的版本信息。
private readonly Regex _regexName = new Regex("^[a-zA-Z][a-zA-Z0-9_]*$"); // 用于验证 Excel 表名的正则表达式。
private readonly HashSet<string> _loadFiles = [".xlsx", ".xlsm", ".csv"]; // 加载的支持文件扩展名。
private readonly OneToManyList<string, ExportInfo>
_tables = new OneToManyList<string, ExportInfo>(); // 存储 Excel 表及其导出信息。
private readonly ConcurrentDictionary<string, ExcelTable> _excelTables =
new ConcurrentDictionary<string, ExcelTable>(); // 存储解析后的 Excel 表。
public readonly ConcurrentDictionary<string, ExcelWorksheet> Worksheets =
new ConcurrentDictionary<string, ExcelWorksheet>(); // 存储已加载的 Excel 工作表。
public readonly Dictionary<string, string> IgnoreTable = new Dictionary<string, string>(); // 存储以#开头的的表和路径
/// <summary>
/// 导表支持的数据类型集合。
/// </summary>
public static readonly HashSet<string> ColTypeSet =
[
"", "0", "bool", "byte", "short", "ushort", "int", "uint", "long", "ulong", "float", "string",
"IntDictionaryConfig", "StringDictionaryConfig",
"short[]", "int[]", "long[]", "float[]", "string[]", "uint[]"
];
static ExcelExporter()
{
ExcelPackage.License.SetNonCommercialOrganization("Fantasy");
}
/// <summary>
/// 根据指定的 exportType 初始化 ExcelExporter 类的新实例。
/// </summary>
/// <param name="exportType">要执行的导出类型AllExcel 或 AllExcelIncrement。</param>
public ExcelExporter(ExportType exportType)
{
ExportType = exportType;
if (App.Config.ExcelVersionPath == null || App.Config.ExcelVersionPath.Trim() == "")
{
Log.Info($"ExcelVersionFile Can not be empty!");
return;
}
if (App.Config.ExcelPath == null || App.Config.ExcelPath.Trim() == "")
{
Log.Info($"ExcelProgramPath Can not be empty!");
return;
}
_excelProgramPath = FileHelper.GetFullPath(App.Config.ExcelPath);
_versionFilePath = FileHelper.GetFullPath(App.Config.ExcelVersionPath);
if (App.Config.GenClient)
{
if (App.Config.ClientJsonPath == null ||
App.Config.ClientJsonPath.Trim() == "")
{
Log.Info($"ExcelClientJsonDirectory Can not be empty!");
return;
}
if (App.Config.ClientPath == null ||
App.Config.ClientPath.Trim() == "")
{
Log.Info($"ExcelServerFileDirectory Can not be empty!");
return;
}
}
if (App.Config.GenServer)
{
if (App.Config.ServerPath == null ||
App.Config.ServerPath.Trim() == "")
{
Log.Info($"ExcelServerFileDirectory Can not be empty!");
return;
}
if (App.Config.ServerJsonPath == null ||
App.Config.ServerJsonPath.Trim() == "")
{
Log.Info($"ExcelServerJsonDirectory Can not be empty!");
return;
}
}
switch (ExportType)
{
case ExportType.AllExcelIncrement:
{
break;
}
case ExportType.AllExcel:
{
if (File.Exists(_versionFilePath))
{
File.Delete(_versionFilePath);
}
break;
}
}
// SerializerManager.Initialize();
}
public void Run()
{
Find();
Parsing();
ExportToBinary();
File.WriteAllText(_versionFilePath, JsonConvert.SerializeObject(VersionInfo));
}
/// <summary>
/// 查找配置文件
/// </summary>
private void Find()
{
VersionInfo = File.Exists(_versionFilePath)
? JsonConvert.DeserializeObject<VersionInfo>(File.ReadAllText(_versionFilePath))
: new VersionInfo();
var dir = new DirectoryInfo(_excelProgramPath);
var excelFiles = dir.GetFiles("*", SearchOption.AllDirectories);
if (excelFiles.Length <= 0)
{
return;
}
foreach (var excelFile in excelFiles)
{
// 过滤掉非指定后缀的文件
if (!_loadFiles.Contains(excelFile.Extension))
{
continue;
}
var lastIndexOf = excelFile.Name.LastIndexOf(".", StringComparison.Ordinal);
if (lastIndexOf < 0)
{
continue;
}
var fullName = excelFile.FullName;
var excelName = excelFile.Name.Substring(0, lastIndexOf);
var path = fullName.Substring(0, fullName.Length - excelFile.Name.Length);
// 过滤~开头文件
if (excelName.StartsWith("~", StringComparison.Ordinal))
{
continue;
}
// 如果文件名以#开头,那么这个文件夹下的所有文件都不导出
if (excelName.StartsWith("#", StringComparison.Ordinal))
{
IgnoreTable.Add(excelName, fullName);
continue;
}
// 如果文件夹名包含#,那么这个文件夹下的所有文件都不导出
if (path.Contains("#", StringComparison.Ordinal))
{
continue;
}
if (!_regexName.IsMatch(excelName))
{
Log.Info($"{excelName} 配置文件名非法");
continue;
}
var exportInfo = new ExportInfo()
{
Name = excelName, FileInfo = excelFile
};
_tables.Add(excelName.Split('_')[0], exportInfo);
}
var removeTables = new List<string>();
foreach (var (tableName, tableList) in _tables)
{
var isNeedExport = false;
foreach (var exportInfo in tableList)
{
var key = HashCodeHelper.ComputeHash64(exportInfo.Name);
var timer = TimeHelper.Transition(exportInfo.FileInfo.LastWriteTime);
if (!isNeedExport)
{
if (VersionInfo.Tables.TryGetValue(key, out var lastWriteTime))
{
isNeedExport = lastWriteTime != timer;
}
else
{
isNeedExport = true;
}
}
VersionInfo.Tables[key] = timer;
}
if (!isNeedExport)
{
removeTables.Add(tableName);
}
}
foreach (var removeTable in removeTables)
{
_tables.Remove(removeTable);
}
foreach (var (_, exportInfo) in _tables)
{
exportInfo.Sort((x, y) => Compare(x.Name, y.Name, StringComparison.Ordinal));
}
}
/// <summary>
/// 生成配置文件
/// </summary>
private void Parsing()
{
var generateTasks = new List<Task>();
foreach (var (tableName, tableList) in _tables)
{
var task = Task.Run(() =>
{
var writeToClassTask = new List<Task>();
var excelTable = new ExcelTable(tableName);
// 筛选需要导出的列
foreach (var exportInfo in tableList)
{
try
{
var serverColInfoList = new List<int>();
var clientColInfoList = new List<int>();
var worksheet = LoadExcel(exportInfo.FileInfo.FullName, true);
for (var col = 3; col <= worksheet.Columns.EndColumn; col++)
{
// 列名字第一个字符是#不参与导出
var colName = worksheet.GetCellValue(5, col);
if (colName.StartsWith("#", StringComparison.Ordinal))
{
continue;
}
// 数值列不参与导出
var numericalCol = worksheet.GetCellValue(3, col);
if (numericalCol != "" && numericalCol != "0")
{
continue;
}
var serverType = worksheet.GetCellValue(1, col);
var clientType = worksheet.GetCellValue(2, col);
var isExportServer = !IsNullOrEmpty(serverType) && serverType != "0";
var isExportClient = !IsNullOrEmpty(clientType) && clientType != "0";
if (!isExportServer && !isExportClient)
{
continue;
}
if (isExportServer && isExportClient & serverType != clientType)
{
Log.Info(
$"配置表 {exportInfo.Name} {col} 列 [{colName}] 客户端类型 {clientType} 和 服务端类型 {serverType} 不一致");
continue;
}
if (!ColTypeSet.Contains(serverType) || !ColTypeSet.Contains(clientType))
{
Log.Info(
$"配置表 {exportInfo.Name} {col} 列 [{colName}] 客户端类型 {clientType}, 服务端类型 {serverType} 不合法");
continue;
}
if (!_regexName.IsMatch(colName))
{
Log.Info($"配置表 {exportInfo.Name} {col} 列 [{colName}] 列名非法");
continue;
}
serverColInfoList.Add(col);
if (isExportClient)
{
clientColInfoList.Add(col);
}
}
if (clientColInfoList.Count > 0)
{
excelTable.ClientColInfos.Add(exportInfo.FileInfo.FullName, clientColInfoList);
}
if (serverColInfoList.Count > 0)
{
excelTable.ServerColInfos.Add(exportInfo.FileInfo.FullName, serverColInfoList);
}
}
catch (Exception e)
{
Log.Error($"Config : {tableName}, Name : {exportInfo.Name}, Error : {e}");
}
}
// 生成cs文件
if (App.Config.GenServer)
{
writeToClassTask.Add(Task.Run(() =>
{
WriteToClass(excelTable.ServerColInfos, App.Config.ServerPath, true);
}));
}
if (App.Config.GenClient)
{
writeToClassTask.Add(Task.Run(() =>
{
WriteToClass(excelTable.ClientColInfos, App.Config.ClientPath, false);
}));
}
Task.WaitAll(writeToClassTask.ToArray());
_excelTables.TryAdd(tableName, excelTable);
});
generateTasks.Add(task);
}
Task.WaitAll(generateTasks.ToArray());
Console.WriteLine("build success===");
}
/// <summary>
/// 把数据和实体类转换二进制导出到文件中
/// </summary>
private void ExportToBinary()
{
System.Reflection.Assembly dynamicServerAssembly = null;
System.Reflection.Assembly dynamicClientAssembly = null;
var exportToBinaryTasks = new List<Task>();
if (App.Config.GenServer &&
Directory.Exists(App.Config.ServerPath))
{
dynamicServerAssembly = DynamicAssembly.Load(App.Config.ServerPath);
}
if (App.Config.GenClient &&
Directory.Exists(App.Config.ClientPath))
{
dynamicClientAssembly = DynamicAssembly.Load(App.Config.ClientPath);
}
foreach (var (tableName, tableList) in _tables)
{
var task = Task.Run(() =>
{
DynamicConfigDataType serverDynamicInfo = null;
DynamicConfigDataType clientDynamicInfo = null;
var idCheck = new HashSet<string>();
var excelTable = _excelTables[tableName];
var csName = Path.GetFileNameWithoutExtension(tableName);
if (App.Config.GenServer)
{
var serverColInfoCount = excelTable.ServerColInfos.Sum(d => d.Value.Count);
serverDynamicInfo = serverColInfoCount == 0
? null
: DynamicAssembly.GetDynamicInfo(dynamicServerAssembly, csName);
}
if (App.Config.GenClient)
{
var clientColInfoCount = excelTable.ClientColInfos.Sum(d => d.Value.Count);
clientDynamicInfo = clientColInfoCount == 0
? null
: DynamicAssembly.GetDynamicInfo(dynamicClientAssembly, csName);
}
for (var i = 0; i < tableList.Count; i++)
{
var tableListName = tableList[i];
try
{
var fileInfoFullName = tableListName.FileInfo.FullName;
var excelWorksheet = LoadExcel(fileInfoFullName, false);
var rows = excelWorksheet.Dimension.Rows;
excelTable.ServerColInfos.TryGetValue(fileInfoFullName, out var serverCols);
excelTable.ClientColInfos.TryGetValue(fileInfoFullName, out var clientCols);
for (var row = 7; row <= rows; row++)
{
if (excelWorksheet.GetCellValue(row, 1).StartsWith("#", StringComparison.Ordinal))
{
continue;
}
var id = excelWorksheet.GetCellValue(row, 3);
if (idCheck.Contains(id))
{
Log.Info($"{tableListName.Name} 存在重复Id {id} 行号 {row}");
continue;
}
idCheck.Add(id);
var isLast = row == rows && (i == tableList.Count - 1);
if (App.Config.GenServer)
{
GenerateBinary(fileInfoFullName, excelWorksheet, serverDynamicInfo, serverCols, id, row,
isLast, true);
}
if (App.Config.GenClient)
{
GenerateBinary(fileInfoFullName, excelWorksheet, clientDynamicInfo, clientCols, id, row,
isLast, false);
}
}
}
catch (Exception e)
{
Log.Error($"Table:{tableListName} error! \n{e}");
throw;
}
}
if (serverDynamicInfo?.ConfigData != null)
{
// var memoryStream = new MemoryStreamBuffer();
// SerializerManager.GetSerializer(FantasySerializerType.ProtoBuf)
// .Serialize(serverDynamicInfo.ConfigData, memoryStream);
// if (!Directory.Exists(_excelServerBinaryDirectory))
// {
// Directory.CreateDirectory(_excelServerBinaryDirectory);
// }
// var asSpan = memoryStream.GetBuffer().AsSpan(0, (int)memoryStream.Position);
// File.WriteAllBytes(Path.Combine(_excelServerBinaryDirectory, $"{csName}Data.bytes"),
// asSpan.ToArray());
if (serverDynamicInfo.Json.Length > 0)
{
if (!Directory.Exists(App.Config.ServerJsonPath))
{
Directory.CreateDirectory(App.Config.ServerJsonPath);
}
using var sw = new StreamWriter(Path.Combine(App.Config.ServerJsonPath, $"{csName}Data.Json"));
sw.WriteLine("{\"List\":[");
sw.Write(serverDynamicInfo.Json.ToString());
sw.WriteLine("]}");
}
}
if (clientDynamicInfo?.ConfigData != null)
{
// var memoryStream = new MemoryStreamBuffer();
// SerializerManager.GetSerializer(FantasySerializerType.ProtoBuf)
// .Serialize(clientDynamicInfo.ConfigData, memoryStream);
// if (!Directory.Exists(_excelClientBinaryDirectory))
// {
// Directory.CreateDirectory(_excelClientBinaryDirectory);
// }
//
// var asSpan = memoryStream.GetBuffer().AsSpan(0, (int)memoryStream.Position);
// File.WriteAllBytes(Path.Combine(_excelClientBinaryDirectory, $"{csName}Data.bytes"),
// asSpan.ToArray());
if (clientDynamicInfo.Json.Length > 0)
{
if (!Directory.Exists(App.Config.ClientJsonPath))
{
Directory.CreateDirectory(App.Config.ClientJsonPath);
}
using var sw = new StreamWriter(Path.Combine(App.Config.ClientJsonPath, $"{csName}Data.Json"));
sw.WriteLine("{\"List\":[");
sw.Write(clientDynamicInfo.Json.ToString());
sw.WriteLine("]}");
}
}
});
exportToBinaryTasks.Add(task);
}
Task.WaitAll(exportToBinaryTasks.ToArray());
}
private void GenerateBinary(string fileInfoFullName, ExcelWorksheet excelWorksheet,
DynamicConfigDataType dynamicInfo, List<int> cols, string id, int row, bool isLast, bool isServer)
{
if (cols == null || IsNullOrEmpty(id) || cols.Count <= 0 || dynamicInfo?.ConfigType == null)
{
return;
}
var config = DynamicAssembly.CreateInstance(dynamicInfo.ConfigType);
for (var i = 0; i < cols.Count; i++)
{
string colType;
var colIndex = cols[i];
var colName = excelWorksheet.GetCellValue(5, colIndex);
var value = excelWorksheet.GetCellValue(row, colIndex);
if (isServer)
{
colType = excelWorksheet.GetCellValue(1, colIndex);
if (IsNullOrEmpty(colType) || colType == "0")
{
colType = excelWorksheet.GetCellValue(2, colIndex);
}
}
else
{
colType = excelWorksheet.GetCellValue(2, colIndex);
}
try
{
SetNewValue(dynamicInfo.ConfigType.GetProperty(colName), config, colType, value);
}
catch (Exception e)
{
Log.Error(
$"Error Table {fileInfoFullName} Col:{colName} colType:{colType} Row:{row} value:{value} {e}");
throw;
}
}
dynamicInfo.Method.Invoke(dynamicInfo.Obj, new object[] { config });
var json = JsonConvert.SerializeObject(config);
if (isLast)
{
dynamicInfo.Json.AppendLine(json);
}
else
{
dynamicInfo.Json.AppendLine($"{json},");
}
}
/// <summary>
/// 从 Excel 文件加载工作表并返回 ExcelWorksheet 对象。
/// </summary>
/// <param name="name">工作表的名称或文件路径。</param>
/// <param name="isAddToDic">是否将加载的工作表添加到缓存字典中。</param>
/// <returns>表示 Excel 工作表的 ExcelWorksheet 对象。</returns>
public ExcelWorksheet LoadExcel(string name, bool isAddToDic)
{
if (Worksheets.TryGetValue(name, out var worksheet))
{
return worksheet;
}
var workbookWorksheets = ExcelHelper.LoadExcel(name).Workbook.Worksheets;
worksheet = workbookWorksheets[0];
if (isAddToDic)
{
Worksheets.TryAdd(name, worksheet);
foreach (var workbookWorksheet in workbookWorksheets)
{
try
{
var hash = HashCodeHelper.ComputeHash64(workbookWorksheet.Name);
VersionInfo.WorksheetNames.Add(hash);
}
catch (Exception e)
{
Console.WriteLine(e);
}
Worksheets.TryAdd(workbookWorksheet.Name, workbookWorksheet);
}
}
Log.Info(name);
return workbookWorksheets[0];
}
/// <summary>
/// 写入到cs
/// </summary>
/// <param name="colInfos"></param>
/// <param name="exportPath"></param>
/// <param name="isServer"></param>
private void WriteToClass(TableDictionary colInfos, string exportPath, bool isServer)
{
if (colInfos.Count <= 0)
{
return;
}
var index = 0;
var fileBuilder = new StringBuilder();
var colNameSet = new HashSet<string>();
if (colInfos.Count == 0)
{
return;
}
var csName = Path.GetFileNameWithoutExtension(colInfos.First().Key)?.Split('_')[0];
foreach (var (tableName, cols) in colInfos)
{
if (cols == null || cols.Count == 0)
{
continue;
}
var excelWorksheet = LoadExcel(tableName, false);
foreach (var colIndex in cols)
{
var colName = excelWorksheet.GetCellValue(5, colIndex);
if (colNameSet.Contains(colName))
{
continue;
}
colNameSet.Add(colName);
string colType;
if (isServer)
{
colType = excelWorksheet.GetCellValue(1, colIndex);
if (IsNullOrEmpty(colType) || colType == "0")
{
colType = excelWorksheet.GetCellValue(2, colIndex);
}
}
else
{
colType = excelWorksheet.GetCellValue(2, colIndex);
}
var remarks = excelWorksheet.GetCellValue(4, colIndex);
// 解决不同平台换行符不一致的问题
switch (Environment.OSVersion.Platform)
{
case PlatformID.Win32NT:
case PlatformID.Win32S:
case PlatformID.Win32Windows:
case PlatformID.WinCE:
{
fileBuilder.Append($"\r\n\t\t[ProtoMember({++index})]\r\n");
break;
}
default:
{
fileBuilder.Append($"\n\t\t[ProtoMember({++index})]\n");
break;
}
}
fileBuilder.Append(
IsArray(colType, out var t)
? $"\t\tpublic {colType} {colName} {{ get; set; }} = Array.Empty<{t}>(); // {remarks}"
: $"\t\tpublic {colType} {colName} {{ get; set; }} // {remarks}");
}
}
var template = ExcelTemplate.Template;
if (fileBuilder.Length > 0)
{
if (!Directory.Exists(exportPath))
{
FileHelper.CreateDirectory(exportPath);
}
var content = template.Replace("(namespace)", "Fantasy")
.Replace("(ConfigName)", csName)
.Replace("(Fields)", fileBuilder.ToString());
File.WriteAllText(Path.Combine(exportPath, $"{csName}.cs"), content);
}
}
private void SetNewValue(PropertyInfo propertyInfo, object config, string type, string value)
{
if (IsNullOrWhiteSpace(value))
{
return;
}
switch (type)
{
case "short":
{
propertyInfo.SetValue(config, Convert.ToInt16(value));
return;
}
case "ushort":
{
propertyInfo.SetValue(config, Convert.ToUInt16(value));
return;
}
case "uint":
{
propertyInfo.SetValue(config, Convert.ToUInt32(value));
return;
}
case "int":
{
propertyInfo.SetValue(config, Convert.ToInt32(value));
return;
}
case "decimal":
{
propertyInfo.SetValue(config, Convert.ToDecimal(value));
return;
}
case "string":
{
try
{
propertyInfo.SetValue(config, value);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
return;
}
case "bool":
{
// 空字符串
value = value.ToLower();
if (IsNullOrEmpty(value))
{
propertyInfo.SetValue(config, false);
}
else if (bool.TryParse(value, out bool b))
{
propertyInfo.SetValue(config, b);
}
else if (int.TryParse(value, out int v))
{
propertyInfo.SetValue(config, v != 0);
}
else
{
propertyInfo.SetValue(config, false);
}
return;
}
case "ulong":
{
propertyInfo.SetValue(config, Convert.ToUInt64(value));
return;
}
case "long":
{
propertyInfo.SetValue(config, Convert.ToInt64(value));
return;
}
case "double":
{
propertyInfo.SetValue(config, Convert.ToDouble(value));
return;
}
case "float":
{
propertyInfo.SetValue(config, Convert.ToSingle(value));
return;
}
case "int32[]":
case "int[]":
{
if (value != "0")
{
propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToInt32(d)).ToArray());
}
return;
}
case "uint[]":
{
if (value != "0")
{
propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToUInt32(d)).ToArray());
}
return;
}
case "long[]":
{
if (value != "0")
{
propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToInt64(d)).ToArray());
}
return;
}
case "double[]":
{
if (value != "0")
{
propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToDouble(d)).ToArray());
}
return;
}
case "string[]":
{
if (value == "0")
{
return;
}
var list = value.Split(",").ToArray();
for (var i = 0; i < list.Length; i++)
{
list[i] = list[i].Replace("\"", "");
}
propertyInfo.SetValue(config, value.Split(",").ToArray());
return;
}
case "float[]":
{
if (value != "0")
{
propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToSingle(d)).ToArray());
}
return;
}
case "IntDictionaryConfig":
{
if (value.Trim() == "" || value.Trim() == "{}")
{
propertyInfo.SetValue(config, null);
return;
}
var attr = new IntDictionaryConfig { Dic = JsonConvert.DeserializeObject<Dictionary<int, int>>(value) };
propertyInfo.SetValue(config, attr);
return;
}
case "StringDictionaryConfig":
{
if (value.Trim() == "" || value.Trim() == "{}")
{
propertyInfo.SetValue(config, null);
return;
}
var attr = new StringDictionaryConfig
{ Dic = JsonConvert.DeserializeObject<Dictionary<int, string>>(value) };
propertyInfo.SetValue(config, attr);
return;
}
default:
throw new NotSupportedException($"不支持此类型: {type}");
}
}
private bool IsArray(string type, out string t)
{
t = null;
var index = type.IndexOf("[]", StringComparison.Ordinal);
if (index >= 0)
{
t = type.Remove(index, 2);
}
return index >= 0;
}
}

View File

@@ -0,0 +1,47 @@
using OfficeOpenXml;
namespace NBConfigBuilder;
/// <summary>
/// 提供操作 Excel 文件的辅助方法。
/// </summary>
public static class ExcelHelper
{
/// <summary>
/// 加载 Excel 文件并返回 ExcelPackage 实例。
/// </summary>
/// <param name="name">Excel 文件的路径。</param>
/// <returns>ExcelPackage 实例。</returns>
public static ExcelPackage LoadExcel(string name)
{
return new ExcelPackage(name);
}
/// <summary>
/// 获取指定工作表中指定行列位置的单元格值。
/// </summary>
/// <param name="sheet">Excel 工作表。</param>
/// <param name="row">行索引。</param>
/// <param name="column">列索引。</param>
/// <returns>单元格值。</returns>
public static string GetCellValue(this ExcelWorksheet sheet, int row, int column)
{
ExcelRange cell = sheet.Cells[row, column];
try
{
if (cell.Value == null)
{
return "";
}
string s = cell.GetValue<string>();
return s.Trim();
}
catch (Exception e)
{
throw new Exception($"Rows {row} Columns {column} Content {cell.Text} {e}");
}
}
}

View File

@@ -0,0 +1,28 @@
namespace NBConfigBuilder;
/// <summary>
/// Excel表格类用于存储表格的名称和列信息。
/// </summary>
public sealed class ExcelTable
{
/// <summary>
/// 表格的名称。
/// </summary>
public readonly string Name;
/// <summary>
/// 客户端列信息,使用排序字典存储列名和列索引列表。
/// </summary>
public readonly SortedDictionary<string, List<int>> ClientColInfos = new();
/// <summary>
/// 服务器端列信息,使用排序字典存储列名和列索引列表。
/// </summary>
public readonly SortedDictionary<string, List<int>> ServerColInfos = new();
/// <summary>
/// 构造函数初始化Excel表格对象并设置表格名称。
/// </summary>
/// <param name="name">表格名称。</param>
public ExcelTable(string name)
{
Name = name;
}
}

View File

@@ -0,0 +1,98 @@
namespace NBConfigBuilder;
public static class ExcelTemplate
{
public static readonly string Template = """
using System;
using ProtoBuf;
using Fantasy;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.Collections.Concurrent;
using Fantasy.ConfigTable;
using Fantasy.Serialize;
// ReSharper disable CollectionNeverUpdated.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable CS0169
#pragma warning disable CS8618
#pragma warning disable CS8625
#pragma warning disable CS8603
namespace (namespace)
{
[ProtoContract]
public sealed partial class (ConfigName)Data : ASerialize, IConfigTable, IProto
{
[ProtoMember(1)]
public List<(ConfigName)> List { get; set; } = new List<(ConfigName)>();
#if FANTASY_NET
[ProtoIgnore]
private readonly ConcurrentDictionary<uint, (ConfigName)> _configs = new ConcurrentDictionary<uint, (ConfigName)>();
#else
[ProtoIgnore]
private readonly Dictionary<uint, (ConfigName)> _configs = new Dictionary<uint, (ConfigName)>();
#endif
private static (ConfigName)Data _instance = null;
public static (ConfigName)Data Instance
{
get { return _instance ??= ConfigTableHelper.Load<(ConfigName)Data>(); }
private set => _instance = value;
}
public (ConfigName) Get(uint id, bool check = true)
{
if (_configs.ContainsKey(id))
{
return _configs[id];
}
if (check)
{
throw new Exception($"(ConfigName) not find {id} Id");
}
return null;
}
public bool TryGet(uint id, out (ConfigName) config)
{
config = null;
if (!_configs.ContainsKey(id))
{
return false;
}
config = _configs[id];
return true;
}
public override void AfterDeserialization()
{
foreach (var config in List)
{
#if FANTASY_NET
_configs.TryAdd(config.Id, config);
#else
_configs.Add(config.Id, config);
#endif
config.AfterDeserialization();
}
EndInit();
}
public override void Dispose()
{
Instance = null;
}
}
[ProtoContract]
public sealed partial class (ConfigName) : ASerialize, IProto
{(Fields)
}
}
""";
}

View File

@@ -0,0 +1,22 @@
using OfficeOpenXml;
namespace NBConfigBuilder;
public sealed class ExcelWorksheets(ExcelExporter excelExporter)
{
public bool TryGetValue(string worksheetName, out ExcelWorksheet excelWorksheet)
{
if (excelExporter.Worksheets.TryGetValue(worksheetName, out excelWorksheet))
{
return true;
}
var computeHash64 = HashCodeHelper.ComputeHash64(worksheetName);
if (!excelExporter.VersionInfo.WorksheetNames.Contains(computeHash64))
{
Log.Info($"{worksheetName} is not exist!");
}
return false;
}
}

View File

@@ -0,0 +1,17 @@
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
namespace NBConfigBuilder;
/// <summary>
/// 导出信息类,用于存储导出操作的名称和文件信息。
/// </summary>
public class ExportInfo
{
/// <summary>
/// 导出操作的名称。
/// </summary>
public string Name;
/// <summary>
/// 导出操作生成的文件信息。
/// </summary>
public FileInfo FileInfo;
}

View File

@@ -0,0 +1,24 @@
namespace NBConfigBuilder;
/// <summary>
/// 导出类型枚举,用于标识不同类型的导出操作。
/// </summary>
public enum ExportType
{
/// <summary>
/// 无导出类型。
/// </summary>
None = 0,
/// <summary>
/// 所有数据的增量导出Excel类型。
/// </summary>
AllExcelIncrement = 1,
/// <summary>
/// 所有数据的全量导出Excel类型。
/// </summary>
AllExcel = 2,
/// <summary>
/// 导出类型枚举的最大值,一定要放在最后。
/// </summary>
Max,
}

View File

@@ -0,0 +1,7 @@
namespace NBConfigBuilder;
public class VersionInfo
{
public SortedSet<long> WorksheetNames = [];
public SortedDictionary<long, long> Tables = new();
}

View File

@@ -0,0 +1,265 @@
namespace NBConfigBuilder
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
textBoxExcelPath = new TextBox();
buttonSelectExcelPath = new Button();
label1 = new Label();
textBoxClientGenPath = new TextBox();
buttonSelectClientPath = new Button();
checkBoxGenClient = new CheckBox();
label2 = new Label();
textBoxServerGenPath = new TextBox();
label3 = new Label();
checkBoxGenServer = new CheckBox();
buttonSelectServerPath = new Button();
buttonRun = new Button();
textBoxClientGenJsonPath = new TextBox();
label4 = new Label();
buttonSelectClientJsonPath = new Button();
textBoxServerGenJsonPath = new TextBox();
label5 = new Label();
buttonSelectServerJsonPath = new Button();
SuspendLayout();
//
// textBoxExcelPath
//
textBoxExcelPath.Location = new Point(84, 13);
textBoxExcelPath.Name = "textBoxExcelPath";
textBoxExcelPath.Size = new Size(322, 23);
textBoxExcelPath.TabIndex = 0;
//
// buttonSelectExcelPath
//
buttonSelectExcelPath.Location = new Point(412, 13);
buttonSelectExcelPath.Name = "buttonSelectExcelPath";
buttonSelectExcelPath.Size = new Size(75, 23);
buttonSelectExcelPath.TabIndex = 1;
buttonSelectExcelPath.Text = "选择";
buttonSelectExcelPath.UseVisualStyleBackColor = true;
buttonSelectExcelPath.Click += buttonSelectExcelPath_Click;
//
// label1
//
label1.AutoSize = true;
label1.Location = new Point(7, 17);
label1.Name = "label1";
label1.Size = new Size(68, 17);
label1.TabIndex = 2;
label1.Text = "配置表路径";
//
// textBoxClientGenPath
//
textBoxClientGenPath.Location = new Point(84, 42);
textBoxClientGenPath.Name = "textBoxClientGenPath";
textBoxClientGenPath.Size = new Size(318, 23);
textBoxClientGenPath.TabIndex = 3;
//
// buttonSelectClientPath
//
buttonSelectClientPath.Location = new Point(412, 42);
buttonSelectClientPath.Name = "buttonSelectClientPath";
buttonSelectClientPath.Size = new Size(75, 23);
buttonSelectClientPath.TabIndex = 4;
buttonSelectClientPath.Text = "选择";
buttonSelectClientPath.UseVisualStyleBackColor = true;
buttonSelectClientPath.Click += buttonSelectClientPath_Click;
//
// checkBoxGenClient
//
checkBoxGenClient.AutoSize = true;
checkBoxGenClient.Checked = true;
checkBoxGenClient.CheckState = CheckState.Checked;
checkBoxGenClient.Location = new Point(495, 44);
checkBoxGenClient.Name = "checkBoxGenClient";
checkBoxGenClient.Size = new Size(87, 21);
checkBoxGenClient.TabIndex = 5;
checkBoxGenClient.Text = "生产服务端";
checkBoxGenClient.UseVisualStyleBackColor = true;
//
// label2
//
label2.AutoSize = true;
label2.Location = new Point(7, 47);
label2.Name = "label2";
label2.Size = new Size(68, 17);
label2.TabIndex = 6;
label2.Text = "客户端代码";
//
// textBoxServerGenPath
//
textBoxServerGenPath.Location = new Point(84, 100);
textBoxServerGenPath.Name = "textBoxServerGenPath";
textBoxServerGenPath.Size = new Size(318, 23);
textBoxServerGenPath.TabIndex = 7;
//
// label3
//
label3.AutoSize = true;
label3.Location = new Point(7, 103);
label3.Name = "label3";
label3.Size = new Size(68, 17);
label3.TabIndex = 8;
label3.Text = "服务端代码";
//
// checkBoxGenServer
//
checkBoxGenServer.AutoSize = true;
checkBoxGenServer.Checked = true;
checkBoxGenServer.CheckState = CheckState.Checked;
checkBoxGenServer.Location = new Point(495, 16);
checkBoxGenServer.Name = "checkBoxGenServer";
checkBoxGenServer.Size = new Size(87, 21);
checkBoxGenServer.TabIndex = 9;
checkBoxGenServer.Text = "生产客户端";
checkBoxGenServer.UseVisualStyleBackColor = true;
//
// buttonSelectServerPath
//
buttonSelectServerPath.Location = new Point(412, 100);
buttonSelectServerPath.Name = "buttonSelectServerPath";
buttonSelectServerPath.Size = new Size(75, 23);
buttonSelectServerPath.TabIndex = 10;
buttonSelectServerPath.Text = "选择";
buttonSelectServerPath.UseVisualStyleBackColor = true;
buttonSelectServerPath.Click += buttonSelectServerPath_Click;
//
// buttonRun
//
buttonRun.Location = new Point(495, 71);
buttonRun.Name = "buttonRun";
buttonRun.Size = new Size(80, 81);
buttonRun.TabIndex = 12;
buttonRun.Text = "执行";
buttonRun.UseVisualStyleBackColor = true;
buttonRun.Click += buttonRun_Click;
//
// textBoxClientGenJsonPath
//
textBoxClientGenJsonPath.Location = new Point(84, 71);
textBoxClientGenJsonPath.Name = "textBoxClientGenJsonPath";
textBoxClientGenJsonPath.Size = new Size(318, 23);
textBoxClientGenJsonPath.TabIndex = 13;
//
// label4
//
label4.AutoSize = true;
label4.Location = new Point(7, 77);
label4.Name = "label4";
label4.Size = new Size(68, 17);
label4.TabIndex = 14;
label4.Text = "客户端配置";
//
// buttonSelectClientJsonPath
//
buttonSelectClientJsonPath.Location = new Point(412, 71);
buttonSelectClientJsonPath.Name = "buttonSelectClientJsonPath";
buttonSelectClientJsonPath.Size = new Size(75, 23);
buttonSelectClientJsonPath.TabIndex = 15;
buttonSelectClientJsonPath.Text = "选择";
buttonSelectClientJsonPath.UseVisualStyleBackColor = true;
buttonSelectClientJsonPath.Click += buttonSelectClientJsonPath_Click;
//
// textBoxServerGenJsonPath
//
textBoxServerGenJsonPath.Location = new Point(85, 129);
textBoxServerGenJsonPath.Name = "textBoxServerGenJsonPath";
textBoxServerGenJsonPath.Size = new Size(317, 23);
textBoxServerGenJsonPath.TabIndex = 16;
//
// label5
//
label5.AutoSize = true;
label5.Location = new Point(7, 132);
label5.Name = "label5";
label5.Size = new Size(68, 17);
label5.TabIndex = 17;
label5.Text = "服务端配置";
//
// buttonSelectServerJsonPath
//
buttonSelectServerJsonPath.Location = new Point(412, 129);
buttonSelectServerJsonPath.Name = "buttonSelectServerJsonPath";
buttonSelectServerJsonPath.Size = new Size(75, 23);
buttonSelectServerJsonPath.TabIndex = 18;
buttonSelectServerJsonPath.Text = "选择";
buttonSelectServerJsonPath.UseVisualStyleBackColor = true;
buttonSelectServerJsonPath.Click += buttonSelectServerJsonPath_Click;
//
// Form1
//
AutoScaleDimensions = new SizeF(7F, 17F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(585, 161);
Controls.Add(buttonSelectServerJsonPath);
Controls.Add(label5);
Controls.Add(textBoxServerGenJsonPath);
Controls.Add(buttonSelectClientJsonPath);
Controls.Add(label4);
Controls.Add(textBoxClientGenJsonPath);
Controls.Add(buttonRun);
Controls.Add(buttonSelectServerPath);
Controls.Add(checkBoxGenServer);
Controls.Add(label3);
Controls.Add(textBoxServerGenPath);
Controls.Add(label2);
Controls.Add(checkBoxGenClient);
Controls.Add(buttonSelectClientPath);
Controls.Add(textBoxClientGenPath);
Controls.Add(label1);
Controls.Add(buttonSelectExcelPath);
Controls.Add(textBoxExcelPath);
Name = "Form1";
Text = "配置生成导出";
ResumeLayout(false);
PerformLayout();
}
#endregion
private TextBox textBoxExcelPath;
private Button buttonSelectExcelPath;
private Label label1;
private TextBox textBoxClientGenPath;
private Button buttonSelectClientPath;
private CheckBox checkBoxGenClient;
private Label label2;
private TextBox textBoxServerGenPath;
private Label label3;
private CheckBox checkBoxGenServer;
private Button buttonSelectServerPath;
private Button buttonRun;
private TextBox textBoxClientGenJsonPath;
private Label label4;
private Button buttonSelectClientJsonPath;
private TextBox textBoxServerGenJsonPath;
private Label label5;
private Button buttonSelectServerJsonPath;
}
}

View File

@@ -0,0 +1,141 @@
using System.Text.Json;
namespace NBConfigBuilder
{
public partial class Form1 : Form
{
// 配置文件路径
private readonly string configPath =
Path.Combine(Path.GetDirectoryName(Application.ExecutablePath) ?? string.Empty, "config.json");
public Form1()
{
InitializeComponent();
// 设置窗口大小不可变
this.FormBorderStyle = FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
// 加载保存的配置
LoadConfig();
}
private void buttonRun_Click(object sender, EventArgs e)
{
// 保存当前配置
SaveConfig();
new ExcelExporter(ExportType.AllExcel).Run();
}
private void buttonSelectExcelPath_Click(object sender, EventArgs e)
{
using (FolderBrowserDialog folderDlg = new FolderBrowserDialog())
{
folderDlg.Description = "请选择配置表路径";
if (folderDlg.ShowDialog() == DialogResult.OK)
{
textBoxExcelPath.Text = folderDlg.SelectedPath;
}
}
}
private void buttonSelectClientJsonPath_Click(object sender, EventArgs e)
{
using (FolderBrowserDialog folderDlg = new FolderBrowserDialog())
{
folderDlg.Description = @"选择客户端json保存目录";
if (folderDlg.ShowDialog() == DialogResult.OK)
{
textBoxClientGenJsonPath.Text = folderDlg.SelectedPath;
}
}
}
private void buttonSelectServerJsonPath_Click(object sender, EventArgs e)
{
using (FolderBrowserDialog folderDlg = new FolderBrowserDialog())
{
folderDlg.Description = @"选择服务端json保存目录";
if (folderDlg.ShowDialog() == DialogResult.OK)
{
textBoxServerGenJsonPath.Text = folderDlg.SelectedPath;
}
}
}
private void buttonSelectClientPath_Click(object sender, EventArgs e)
{
using (FolderBrowserDialog folderDlg = new FolderBrowserDialog())
{
folderDlg.Description = @"选择客户端代码保存目录";
if (folderDlg.ShowDialog() == DialogResult.OK)
{
textBoxClientGenPath.Text = folderDlg.SelectedPath;
}
}
}
private void buttonSelectServerPath_Click(object sender, EventArgs e)
{
using (FolderBrowserDialog folderDlg = new FolderBrowserDialog())
{
folderDlg.Description = @"选择服务端代码保存目录";
if (folderDlg.ShowDialog() == DialogResult.OK)
{
textBoxServerGenPath.Text = folderDlg.SelectedPath;
}
}
}
// 添加保存和加载配置的方法
private void SaveConfig()
{
var config = new AppConfig
{
ExcelPath = textBoxExcelPath.Text,
ClientPath = textBoxClientGenPath.Text,
ClientJsonPath = textBoxClientGenJsonPath.Text,
ServerPath = textBoxServerGenPath.Text,
ServerJsonPath = textBoxServerGenJsonPath.Text,
GenClient = checkBoxGenClient.Checked,
GenServer = checkBoxGenServer.Checked,
};
App.Config = config;
try
{
string json = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(configPath, json);
}
catch (Exception ex)
{
MessageBox.Show($"保存配置失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void LoadConfig()
{
if (!File.Exists(configPath)) return;
try
{
string json = File.ReadAllText(configPath);
var config = JsonSerializer.Deserialize<AppConfig>(json);
if (config != null)
{
App.Config = config;
textBoxExcelPath.Text = config.ExcelPath ?? "";
textBoxClientGenPath.Text = config.ClientPath ?? "";
textBoxServerGenPath.Text = config.ServerPath ?? "";
checkBoxGenClient.Checked = config.GenClient;
checkBoxGenServer.Checked = config.GenServer;
textBoxClientGenJsonPath.Text = config.ClientJsonPath ?? "";
textBoxServerGenJsonPath.Text = config.ServerJsonPath ?? "";
}
}
catch (Exception ex)
{
MessageBox.Show($"加载配置失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,97 @@
using NLog;
using System;
using System.Diagnostics;
namespace NBConfigBuilder
{
public static class Log
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
static Log()
{
}
/// <summary>
/// 记录信息级别的日志消息。
/// </summary>
/// <param name="msg">日志消息。</param>
public static void Info(string msg)
{
logger.Info(msg);
}
/// <summary>
/// 记录错误级别的日志消息,并附带调用栈信息。
/// </summary>
/// <param name="msg">日志消息。</param>
public static void Error(string msg)
{
var st = new StackTrace(1, true);
logger.Error($"{msg}\n{st}");
}
/// <summary>
/// 记录异常的错误级别的日志消息,并附带调用栈信息。
/// </summary>
/// <param name="e">异常对象。</param>
public static void Error(Exception e)
{
if (e.Data.Contains("StackTrace"))
{
logger.Error($"{e.Data["StackTrace"]}\n{e}");
return;
}
var str = e.ToString();
logger.Error(str);
}
/// <summary>
/// 记录信息级别的格式化日志消息。
/// </summary>
/// <param name="message">日志消息模板。</param>
/// <param name="args">格式化参数。</param>
public static void Info(string message, params object[] args)
{
logger.Info(message, args);
}
/// <summary>
/// 记录调试级别的日志消息。
/// </summary>
/// <param name="msg">日志消息。</param>
public static void Debug(string msg)
{
logger.Debug(msg);
}
/// <summary>
/// 记录警告级别的日志消息。
/// </summary>
/// <param name="msg">日志消息。</param>
public static void Warn(string msg)
{
logger.Warn(msg);
}
/// <summary>
/// 记录严重错误级别的日志消息。
/// </summary>
/// <param name="msg">日志消息。</param>
public static void Fatal(string msg)
{
logger.Fatal(msg);
}
/// <summary>
/// 记录异常的错误级别的日志消息。
/// </summary>
/// <param name="e">异常对象。</param>
/// <param name="message">附加消息。</param>
public static void Error(Exception e, string message)
{
logger.Error(e, message);
}
}
}

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>disable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EPPlus" Version="8.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="NLog" Version="6.0.4" />
<PackageReference Include="protobuf-net" Version="3.2.56" />
</ItemGroup>
<ItemGroup>
<None Update="NLog.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Update="Form1.cs">
<SubType>Form</SubType>
</Compile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<!-- 文件目标 -->
<target xsi:type="File"
name="fileTarget"
fileName="logs/${shortdate}.log"
layout="${longdate} ${uppercase:${level}} ${message} ${exception:format=ToString}" />
<!-- 控制台目标 -->
<target xsi:type="Console"
name="consoleTarget"
layout="${longdate} ${uppercase:${level}} ${message} ${exception:format=ToString}" />
</targets>
<rules>
<!-- 记录所有级别高于 Debug 的日志到文件 -->
<logger name="*" minlevel="Debug" writeTo="fileTarget" />
<!-- 记录所有级别高于 Info 的日志到控制台 -->
<logger name="*" minlevel="Info" writeTo="consoleTarget" />
</rules>
</nlog>

View File

@@ -0,0 +1,25 @@
using NLog;
namespace NBConfigBuilder
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// 配置NLog
var logger = LogManager.GetCurrentClassLogger();
logger.Info("Application started");
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
logger.Info("Application closing");
}
}
}

View File

@@ -0,0 +1,182 @@
using System.Text;
namespace NBConfigBuilder;
/// <summary>
/// 文件操作助手类,提供了各种文件操作方法。
/// </summary>
public static partial class FileHelper
{
/// <summary>
/// 获取相对路径的完整路径。
/// </summary>
/// <param name="relativePath">相对路径。</param>
/// <returns>完整路径。</returns>
public static string GetFullPath(string relativePath)
{
return Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePath));
}
/// <summary>
/// 获取相对路径的完整路径。
/// </summary>
/// <param name="relativePath">相对于指定的目录的相对路径。</param>
/// <param name="srcDir">指定的目录</param>
/// <returns>完整路径。</returns>
public static string GetFullPath(string relativePath, string srcDir)
{
return Path.GetFullPath(Path.Combine(srcDir, relativePath));
}
/// <summary>
/// 获取相对路径的的文本信息。
/// </summary>
/// <param name="relativePath"></param>
/// <returns></returns>
public static async Task<string> GetTextByRelativePath(string relativePath)
{
var fullPath = GetFullPath(relativePath);
return await File.ReadAllTextAsync(fullPath, Encoding.UTF8);
}
/// <summary>
/// 获取绝对路径的的文本信息。
/// </summary>
/// <param name="fullPath"></param>
/// <returns></returns>
public static async Task<string> GetText(string fullPath)
{
return await File.ReadAllTextAsync(fullPath, Encoding.UTF8);
}
/// <summary>
/// 根据文件夹路径创建文件夹,如果文件夹不存在会自动创建文件夹。
/// </summary>
/// <param name="directoryPath"></param>
public static void CreateDirectory(string directoryPath)
{
if (directoryPath.LastIndexOf('/') != directoryPath.Length - 1)
{
directoryPath += "/";
}
var directoriesByFilePath = GetDirectoriesByFilePath(directoryPath);
foreach (var dir in directoriesByFilePath)
{
if (Directory.Exists(dir))
{
continue;
}
Directory.CreateDirectory(dir);
}
}
/// <summary>
/// 将文件复制到目标路径,如果目标目录不存在会自动创建目录。
/// </summary>
/// <param name="sourceFile">源文件路径。</param>
/// <param name="destinationFile">目标文件路径。</param>
/// <param name="overwrite">是否覆盖已存在的目标文件。</param>
public static void Copy(string sourceFile, string destinationFile, bool overwrite)
{
CreateDirectory(destinationFile);
File.Copy(sourceFile, destinationFile, overwrite);
}
/// <summary>
/// 获取文件路径内的所有文件夹路径。
/// </summary>
/// <param name="filePath">文件路径。</param>
/// <returns>文件夹路径列表。</returns>
public static IEnumerable<string> GetDirectoriesByFilePath(string filePath)
{
var dir = "";
var fileDirectories = filePath.Split('/');
for (var i = 0; i < fileDirectories.Length - 1; i++)
{
dir = $"{dir}{fileDirectories[i]}/";
yield return dir;
}
if (fileDirectories.Length == 1)
{
yield return filePath;
}
}
/// <summary>
/// 将文件夹内的所有内容复制到目标位置。
/// </summary>
/// <param name="sourceDirectory">源文件夹路径。</param>
/// <param name="destinationDirectory">目标文件夹路径。</param>
/// <param name="overwrite">是否覆盖已存在的文件。</param>
public static void CopyDirectory(string sourceDirectory, string destinationDirectory, bool overwrite)
{
// 创建目标文件夹
if (!Directory.Exists(destinationDirectory))
{
Directory.CreateDirectory(destinationDirectory);
}
// 获取当前文件夹中的所有文件
var files = Directory.GetFiles(sourceDirectory);
// 拷贝文件到目标文件夹
foreach (var file in files)
{
var fileName = Path.GetFileName(file);
var destinationPath = Path.Combine(destinationDirectory, fileName);
File.Copy(file, destinationPath, overwrite);
}
// 获取源文件夹中的所有子文件夹
var directories = Directory.GetDirectories(sourceDirectory);
// 递归方式拷贝文件夹
foreach (var directory in directories)
{
var directoryName = Path.GetFileName(directory);
var destinationPath = Path.Combine(destinationDirectory, directoryName);
CopyDirectory(directory, destinationPath, overwrite);
}
}
/// <summary>
/// 获取目录下的所有文件
/// </summary>
/// <param name="folderPath">文件夹路径。</param>
/// <param name="searchPattern">需要查找的文件通配符</param>
/// <param name="searchOption">查找的类型</param>
/// <returns></returns>
public static string[] GetDirectoryFile(string folderPath, string searchPattern, SearchOption searchOption)
{
return Directory.GetFiles(folderPath, searchPattern, searchOption);
}
/// <summary>
/// 清空文件夹内的所有文件。
/// </summary>
/// <param name="folderPath">文件夹路径。</param>
public static void ClearDirectoryFile(string folderPath)
{
if (!Directory.Exists(folderPath))
{
return;
}
var files = Directory.GetFiles(folderPath);
foreach (var file in files)
{
File.Delete(file);
}
}
}

View File

@@ -0,0 +1,228 @@
using System.Security.Cryptography;
using System.Text;
namespace NBConfigBuilder;
/// <summary>
/// HashCode算法帮助类
/// </summary>
public static partial class HashCodeHelper
{
private static readonly SHA256 Sha256Hash = SHA256.Create();
/// <summary>
/// 计算两个字符串的HashCode
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static int GetHashCode(string a, string b)
{
var hash = 17;
hash = hash * 31 + a.GetHashCode();
hash = hash * 31 + b.GetHashCode();
return hash;
}
#if FANTASY_NET || !FANTASY_WEBGL
/// <summary>
/// 使用bkdr算法生成一个long的值
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static unsafe long GetBKDRHashCode(string str)
{
ulong hash = 0;
// 如果要修改这个种子、建议选择一个质数来做种子
const uint seed = 13131; // 31 131 1313 13131 131313 etc..
fixed (char* p = str)
{
for (var i = 0; i < str.Length; i++)
{
var c = p[i];
var high = (byte)(c >> 8);
var low = (byte)(c & byte.MaxValue);
hash = hash * seed + high;
hash = hash * seed + low;
}
}
return (long)hash;
}
/// <summary>
/// 使用MurmurHash3算法生成一个uint的值
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static unsafe uint MurmurHash3(string str)
{
const uint seed = 0xc58f1a7b;
uint hash = seed;
uint c1 = 0xcc9e2d51;
uint c2 = 0x1b873593;
fixed (char* p = str)
{
var current = p;
for (var i = 0; i < str.Length; i++)
{
var k1 = (uint)(*current);
k1 *= c1;
k1 = (k1 << 15) | (k1 >> (32 - 15));
k1 *= c2;
hash ^= k1;
hash = (hash << 13) | (hash >> (32 - 13));
hash = hash * 5 + 0xe6546b64;
current++;
}
}
hash ^= (uint)str.Length;
hash ^= hash >> 16;
hash *= 0x85ebca6b;
hash ^= hash >> 13;
hash *= 0xc2b2ae35;
hash ^= hash >> 16;
return hash;
}
/// <summary>
/// 使用MurmurHash3算法生成一个long的值
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static unsafe long ComputeHash64(string str)
{
const ulong seed = 0xc58f1a7bc58f1a7bUL; // 64-bit seed
var hash = seed;
var c1 = 0x87c37b91114253d5UL;
var c2 = 0x4cf5ad432745937fUL;
fixed (char* p = str)
{
var current = p;
for (var i = 0; i < str.Length; i++)
{
var k1 = (ulong)(*current);
k1 *= c1;
k1 = (k1 << 31) | (k1 >> (64 - 31));
k1 *= c2;
hash ^= k1;
hash = (hash << 27) | (hash >> (64 - 27));
hash = hash * 5 + 0x52dce729;
current++;
}
}
hash ^= (ulong)str.Length;
hash ^= hash >> 33;
hash *= 0xff51afd7ed558ccdUL;
hash ^= hash >> 33;
hash *= 0xc4ceb9fe1a85ec53UL;
hash ^= hash >> 33;
return (long)hash;
}
#endif
#if FANTASY_WEBGL
/// <summary>
/// 使用bkdr算法生成一个long的值
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static long GetBKDRHashCode(string str)
{
long hash = 0;
// 如果要修改这个种子、建议选择一个质数来做种子
const uint seed = 13131; // 31 131 1313 13131 131313 etc..
foreach (var c in str)
{
var high = (byte)(c >> 8);
var low = (byte)(c & byte.MaxValue);
hash = hash * seed + high;
hash = hash * seed + low;
}
return hash;
}
/// <summary>
/// 使用MurmurHash3算法生成一个uint的值
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static uint MurmurHash3(string str)
{
const uint seed = 0xc58f1a7b;
uint hash = seed;
uint c1 = 0xcc9e2d51;
uint c2 = 0x1b873593;
foreach (var t in str)
{
var k1 = (uint)(t);
k1 *= c1;
k1 = (k1 << 15) | (k1 >> (32 - 15));
k1 *= c2;
hash ^= k1;
hash = (hash << 13) | (hash >> (32 - 13));
hash = hash * 5 + 0xe6546b64;
}
hash ^= (uint)str.Length;
hash ^= hash >> 16;
hash *= 0x85ebca6b;
hash ^= hash >> 13;
hash *= 0xc2b2ae35;
hash ^= hash >> 16;
return hash;
}
/// <summary>
/// 使用MurmurHash3算法生成一个long的值
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static long ComputeHash64(string str)
{
const ulong seed = 0xc58f1a7bc58f1a7bUL; // 64-bit seed
var hash = seed;
var c1 = 0x87c37b91114253d5UL;
var c2 = 0x4cf5ad432745937fUL;
foreach (var t in str)
{
var k1 = (ulong)(t);
k1 *= c1;
k1 = (k1 << 31) | (k1 >> (64 - 31));
k1 *= c2;
hash ^= k1;
hash = (hash << 27) | (hash >> (64 - 27));
hash = hash * 5 + 0x52dce729;
}
hash ^= (ulong)str.Length;
hash ^= hash >> 33;
hash *= 0xff51afd7ed558ccdUL;
hash ^= hash >> 33;
hash *= 0xc4ceb9fe1a85ec53UL;
hash ^= hash >> 33;
return (long)hash;
}
#endif
/// <summary>
/// 根据字符串计算一个Hash值
/// </summary>
/// <param name="rawData"></param>
/// <returns></returns>
public static int ComputeSha256HashAsInt(string rawData)
{
var bytes = Sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData));
return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
}
}

View File

@@ -0,0 +1,65 @@
namespace NBConfigBuilder;
/// <summary>
/// 提供与时间相关的帮助方法。
/// </summary>
public static partial class TimeHelper
{
/// <summary>
/// 一小时的毫秒值。
/// </summary>
public const long Hour = 3600000;
/// <summary>
/// 一分钟的毫秒值。
/// </summary>
public const long Minute = 60000;
/// <summary>
/// 一天的毫秒值。
/// </summary>
public const long OneDay = 86400000;
// 1970年1月1日的Ticks
private const long Epoch = 621355968000000000L;
/// <summary>
/// 获取当前时间的毫秒数从1970年1月1日开始计算。
/// </summary>
public static long Now => (DateTime.UtcNow.Ticks - Epoch) / 10000;
/// <summary>
/// 根据时间获取时间戳
/// </summary>
public static long Transition(DateTime dateTime)
{
return (dateTime.ToUniversalTime().Ticks - Epoch) / 10000;
}
/// <summary>
/// 根据时间获取 时间戳
/// </summary>
public static long TransitionToSeconds(DateTime dateTime)
{
return (dateTime.ToUniversalTime().Ticks - Epoch) / 10000000;
}
/// <summary>
/// 将毫秒数转换为日期时间。
/// </summary>
/// <param name="timeStamp">要转换的毫秒数。</param>
/// <returns>转换后的日期时间。</returns>
public static DateTime Transition(this long timeStamp)
{
return new DateTime(Epoch + timeStamp * 10000, DateTimeKind.Utc).ToUniversalTime();
}
/// <summary>
/// 将毫秒数转换为本地时间的日期时间。
/// </summary>
/// <param name="timeStamp">要转换的毫秒数。</param>
/// <returns>转换后的本地时间的日期时间。</returns>
public static DateTime TransitionLocal(this long timeStamp)
{
return new DateTime(Epoch + timeStamp * 10000, DateTimeKind.Utc).ToLocalTime();
}
}

View File

@@ -664,6 +664,7 @@ public sealed class ExcelExporter
Task.WaitAll(exportToBinaryTasks.ToArray());
}
private void GenerateBinary(string fileInfoFullName, ExcelWorksheet excelWorksheet, DynamicConfigDataType dynamicInfo, List<int> cols, string id, int row, bool isLast, bool isServer)
{
if (cols == null || IsNullOrEmpty(id) || cols.Count <= 0 || dynamicInfo?.ConfigType == null)
@@ -718,6 +719,7 @@ public sealed class ExcelExporter
dynamicInfo.Json.AppendLine($"{json},");
}
}
/// <summary>
/// 从 Excel 文件加载工作表并返回 ExcelWorksheet 对象。
/// </summary>