From a214b95c821e30315efff46a0361bccccd7101da Mon Sep 17 00:00:00 2001 From: nxiaoxiao <3247747442@qq.com> Date: Wed, 14 Aug 2024 20:07:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AC=A1=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 22 ++ Common/ActiveData.cs | 140 ++++++++++++ Common/Logger.cs | 81 +++++++ Common/ServerConfig.cs | 307 +++++++++++++++++++++++++++ Common/UserConnect.cs | 32 +++ Common/Utils.cs | 70 ++++++ DbClass/AccountData.cs | 112 ++++++++++ DbClass/MapData.cs | 155 ++++++++++++++ DbClass/MapLayer.cs | 142 +++++++++++++ GlobalArea.cs | 302 ++++++++++++++++++++++++++ Instructs/BroadcastInstuct.cs | 31 +++ Instructs/GetMapDataInstruct.cs | 36 ++++ Instructs/GetMapLayerInstruct.cs | 36 ++++ Instructs/GetPresenceInstruct.cs | 52 +++++ Instructs/GetPublickeyInstruct.cs | 32 +++ Instructs/GetServerConfigInstruct.cs | 47 ++++ Instructs/GetServerImgInstruct.cs | 84 ++++++++ Instructs/GetUserDataInstruct.cs | 48 +++++ Instructs/Instruct.cs | 165 ++++++++++++++ Instructs/LoginInstruct.cs | 80 +++++++ Instructs/PingInstuct.cs | 32 +++ Instructs/TestInstuct.cs | 23 ++ MapServer.cs | 70 ++++++ OMS.NET.csproj | 26 +++ OMS.NET.sln | 25 +++ Program.cs | 108 ++++++++++ README.md | 68 ++++++ init.sql | 40 ++++ 28 files changed, 2366 insertions(+) create mode 100644 .gitignore create mode 100644 Common/ActiveData.cs create mode 100644 Common/Logger.cs create mode 100644 Common/ServerConfig.cs create mode 100644 Common/UserConnect.cs create mode 100644 Common/Utils.cs create mode 100644 DbClass/AccountData.cs create mode 100644 DbClass/MapData.cs create mode 100644 DbClass/MapLayer.cs create mode 100644 GlobalArea.cs create mode 100644 Instructs/BroadcastInstuct.cs create mode 100644 Instructs/GetMapDataInstruct.cs create mode 100644 Instructs/GetMapLayerInstruct.cs create mode 100644 Instructs/GetPresenceInstruct.cs create mode 100644 Instructs/GetPublickeyInstruct.cs create mode 100644 Instructs/GetServerConfigInstruct.cs create mode 100644 Instructs/GetServerImgInstruct.cs create mode 100644 Instructs/GetUserDataInstruct.cs create mode 100644 Instructs/Instruct.cs create mode 100644 Instructs/LoginInstruct.cs create mode 100644 Instructs/PingInstuct.cs create mode 100644 Instructs/TestInstuct.cs create mode 100644 MapServer.cs create mode 100644 OMS.NET.csproj create mode 100644 OMS.NET.sln create mode 100644 Program.cs create mode 100644 README.md create mode 100644 init.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64de127 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# 忽略所有编译输出和构建工件 +bin/ +obj/ + +# Visual Studio 文件 +.vs/ +*.user +*.suo + +# NuGet 包 +*.nupkg +*.snupkg + +# 忽略发布和部署文件 +*.publishsettings +publish/ + +#一些功能标识文件 +.pkey +.dblock +server.yaml +map_img.png \ No newline at end of file diff --git a/Common/ActiveData.cs b/Common/ActiveData.cs new file mode 100644 index 0000000..34d8a46 --- /dev/null +++ b/Common/ActiveData.cs @@ -0,0 +1,140 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OMS.NET.Common +{ + public class UserInfo + { + public string? Name { get; set; } + public string? Email { get; set; } + public string? Color { get; set; } + } + + public class ActiveDataElement + { + public string EID { get; set; } + public UserInfo? Pick { get; set; } + public UserInfo? Select { get; set; } + + public ActiveDataElement(string id) + { + EID = "E" + id; + } + } + + public class ActiveDataElementConverter : JsonConverter + { + public override ActiveDataElement Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + reader.Read(); + string? eid = (reader.GetString()?[1..]) ?? throw new JsonException(); // Removing 'E' prefix to get the ID + var element = new ActiveDataElement(eid); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + if (reader.TokenType == JsonTokenType.StartObject) + { + continue; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + string? propertyName = reader.GetString(); + reader.Read(); + + switch (propertyName) + { + case "pick": + element.Pick = DeserializeUserInfo(ref reader); + break; + case "select": + element.Select = DeserializeUserInfo(ref reader); + break; + default: + reader.Skip(); // Skip unknown properties + break; + } + } + //reader.CurrentDepth = 0; + reader.Read(); + //还需要读取一次,因为之前有过一次读取,跳过了一个token,最后一个token也要读一次 + return element; + } + + public override void Write(Utf8JsonWriter writer, ActiveDataElement value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WritePropertyName(value.EID); + writer.WriteStartObject(); + if (value.Pick != null) { WriteUserInfo(writer, "pick", value.Pick); } + if (value.Select != null) { WriteUserInfo(writer, "select", value.Select); } + writer.WriteEndObject(); + writer.WriteEndObject(); + } + + private static void WriteUserInfo(Utf8JsonWriter writer, string propertyName, UserInfo userInfo) + { + writer.WriteStartObject(propertyName); + if (userInfo != null) + { + writer.WriteString("name", userInfo.Name); + writer.WriteString("email", userInfo.Email); + writer.WriteString("color", userInfo.Color); + } + writer.WriteEndObject(); + } + + private static UserInfo DeserializeUserInfo(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + string? name = null; + string? email = null; + string? color = null; + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + string? propertyName = reader.GetString(); + reader.Read(); + switch (propertyName) + { + case "name": + name = reader.GetString(); + break; + case "email": + email = reader.GetString(); + break; + case "color": + color = reader.GetString(); + break; + default: + reader.Skip(); // Skip unknown properties + break; + } + } + return new UserInfo { Name = name, Email = email, Color = color }; + } + } +} \ No newline at end of file diff --git a/Common/Logger.cs b/Common/Logger.cs new file mode 100644 index 0000000..65bcd7f --- /dev/null +++ b/Common/Logger.cs @@ -0,0 +1,81 @@ +namespace OMS.NET.Common +{ + public class Logger + { + private readonly int _logLevel; + private int logCount; + private string logPath; + private static readonly object LogLock = new(); + + private void WriteLog(string message) + { + lock (LogLock) + { + using var writer = new StreamWriter(this.logPath, true); + writer.WriteLine(message); + } + } + + public Logger(int level) + { + _logLevel = level; + this.logPath = GetNewLogFile(); + } + + private string GetNewLogFile() + { + this.logCount = 0; + string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log", $"{DateTime.Now:yyyy-MM-dd-HH-mm-ss}.log"); + if (!Directory.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log"))) + { + Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log")); + } + File.WriteAllText(path, ""); + return path; + } + + private void Log(string message, int level) + { + if (level <= this._logLevel) + { + string logtime = DateTime.Now.ToString("[yyyy-MM-dd HH:mm:ss]"); + string leveltext = level switch + { + 0 => "[ERROR]", + 1 => "[WARN]", + 2 => "[INFO]", + 3 => "[DEBUG]", + _ => level.ToString(), + }; + string logtext = leveltext + " " + logtime + " " + message; + Console.WriteLine(logtext); + if (logCount > 100000) + { + this.logPath = GetNewLogFile(); + } + //File.AppendAllTextAsync(this.logPath, logtext); + WriteLog(logtext); + logCount++; + } + } + public void Error(string message) + { + Log(message, 0); + } + + public void Warn(string message) + { + Log(message, 1); + } + + public void Info(string message) + { + Log(message, 2); + } + + public void Debug(string message) + { + Log(message, 3); + } + } +} \ No newline at end of file diff --git a/Common/ServerConfig.cs b/Common/ServerConfig.cs new file mode 100644 index 0000000..225f259 --- /dev/null +++ b/Common/ServerConfig.cs @@ -0,0 +1,307 @@ +using System.Reflection; +using System.Text; +using System.Text.Json; + +namespace OMS.NET.Common +{ + public sealed class ServerConfig + { + private Dictionary _config = new(); + /// + /// 连接数据库的连接字符串 default Empty + /// 默认不带数据库名称 + /// + public string ConnectionString => _config.TryGetValue("ConnectionString", out string? value) + ? value + : ""; + + /// + /// 数据库名称 default map_db + /// + public string DataBaseName => _config.TryGetValue("DataBaseName", out string? value) + ? value + : "map_db"; + + /// + /// 监听端口 default 8080 + /// + public string ListenPort => _config.TryGetValue("ListenPort", out string? value) + ? value + : "8080"; + + /// + /// 是否开启匿名登录 default false + /// + public string AnonymousLogin => _config.TryGetValue("AnonymousLogin", out string? value) + ? value + : "false"; + + /// + /// 地图的背景图片位置,图像尺寸为:220px * 165px 最大为60kb 支持PNG和jpg类型的图片格式 + /// default ./map_img.png + /// + public string Img => _config.TryGetValue("ServerConfigImg", out string? value) + ? value + : "./map_img.png"; + + /// + /// key是唯一的,长度不限,格式k[a-Z0-9],是客户端采用https://name.com/ + m/key 的方式快捷访问地图服务器的 + /// default k0 + /// + public string Key => _config.TryGetValue("ServerConfigKey", out string? value) + ? value + : "k0"; + + /// + /// 服务器websocket链接的地址 + /// default 'ws://0.0.0.0:8080' + /// + public string Url => _config.TryGetValue("ServerConfigUrl", out string? value) + ? value + : "ws://0.0.0.0:8080"; + + /// + /// 服务器名称 + /// default 地图名称 + /// + public string Name => _config.TryGetValue("ServerConfigName", out string? value) + ? value + : "地图名称"; + + /// + /// 服务器最大在线人数 + /// default 20 + /// + public string MaxUser => _config.TryGetValue("ServerConfigMaxUser", out string? value) + ? value + : "20"; + + /// + /// 最大高度 + /// default 10000 + /// + public string MaxHeight => _config.TryGetValue("ServerConfigMaxHeight", out string? value) + ? value + : "10000"; + + /// + /// 最小高度 + /// default -10000 + /// + public string MinHeight => _config.TryGetValue("ServerConfigMinHeight", out string? value) + ? value + : "-10000"; + + /// + /// 最大宽度 + /// default 10000 + /// + public string MaxWidth => _config.TryGetValue("ServerConfigMaxWidth", out string? value) + ? value + : "10000"; + + /// + /// 最小宽度 + /// default -10000 + /// + public string MinWidth => _config.TryGetValue("ServerConfigMinWidth", out string? value) + ? value + : "-10000"; + + /// + /// 区域内横轴和纵轴的最小层级下(layer0)每像素移动量单位 纵轴单位 + /// default 1 + /// + public string Unit1Y => _config.TryGetValue("ServerConfigUnit1Y", out string? value) + ? value + : "1"; + /// + /// 区域内横轴和纵轴的最小层级下(layer0)每像素移动量单位 横轴单位 + /// default 1 + /// + public string Unit1X => _config.TryGetValue("ServerConfigUnit1X", out string? value) + ? value + : "1"; + + /// + /// 打开地图时的默认中心点 x + /// default 0 + /// + public string P0X => _config.TryGetValue("ServerConfigP0X", out string? value) + ? value + : "0"; + /// + /// 打开地图时的默认中心点 y + /// default 0 + /// + public string P0Y => _config.TryGetValue("ServerConfigP0Y", out string? value) + ? value + : "0"; + + /// + /// 最大层级 + /// default 5 + /// + public string MaxLayer => _config.TryGetValue("ServerConfigMaxLayer", out string? value) + ? value + : "default"; + + /// + /// 最小层级 + /// default 0 + /// + public string MinLayer => _config.TryGetValue("ServerConfigMinLayer", out string? value) + ? value + : "default"; + + /// + /// 默认层级 + /// default 0 + /// + public string DefaultLayer => _config.TryGetValue("ServerConfigDefaultLayer", out string? value) + ? value + : "0"; + + /// + /// 默认缩放比例 + /// default 1 + /// + public string ZoomAdd => _config.TryGetValue("ServerConfigZoomAdd", out string? value) + ? value + : "1"; + + /// + /// 表示是否启用额外的底图服务 + /// default false + /// + public string EnableBase => _config.TryGetValue("ServerConfigEnableBase", out string? value) + ? value + : "false"; + + /// + /// 默认x + /// default 0 + /// + public string DefaultX => _config.TryGetValue("ServerConfigDefaultX", out string? value) + ? value + : "0"; + + /// + /// 默认y + /// default 0 + /// + public string DefaultY => _config.TryGetValue("ServerConfigDefaultY", out string? value) + ? value + : "0"; + + /// + /// 屏幕默认分辨率x + /// default 1920 + /// + public string ResolutionX => _config.TryGetValue("ServerConfigResolutionX", out string? value) + ? value + : "1920"; + + /// + /// 屏幕默认分辨率y + /// default 980 + /// + public string ResolutionY => _config.TryGetValue("ServerConfigResolutionY", out string? value) + ? value + : "980"; + + /// + /// 最大底图缩放等级 + /// default 0 + /// + public string MaxZoom => _config.TryGetValue("ServerConfigMaxZoom", out string? value) + ? value + : "0"; + + /// + /// 最小底图缩放等级 + /// default 0 + /// + public string MinZoom => _config.TryGetValue("ServerConfigMinZoom", out string? value) + ? value + : "0"; + + /// + /// 默认底图缩放等级 + /// default 0 + /// + public string DefaultZoom => _config.TryGetValue("ServerConfigDefaultZoom", out string? value) + ? value + : "0"; + + /// + /// 底图服务的服务器URL + /// default empty + /// + public string BaseMapUrl => _config.TryGetValue("ServerConfigBaseMapUrl", out string? value) + ? value + : ""; + + public ServerConfig() + { + //do nothing,everything default + } + public ServerConfig(string configPath) + { + if (!File.Exists(configPath)) + { + throw new FileNotFoundException("服务器配置文件未找到, path: " + configPath); + } + foreach (var line in File.ReadLines(configPath)) + { + // 忽略空行和注释行 + var trimmedLine = line.Trim(); + if (string.IsNullOrEmpty(trimmedLine) || trimmedLine.StartsWith("#")) + { + continue; + } + + // 去掉行尾的注释 + var commentIndex = trimmedLine.IndexOf('#'); + if (commentIndex > -1) + { + trimmedLine = trimmedLine[..commentIndex].Trim(); + } + + // 查找冒号的位置 + var separatorIndex = trimmedLine.IndexOf(':'); + if (separatorIndex > -1) + { + // 获取键和值,并去除冒号前后的空格 + var key = trimmedLine[..separatorIndex].Trim(); + var value = trimmedLine[(separatorIndex + 1)..].Trim(); + + // 将键值对添加到字典中 + _config[key] = value; + } + } + } + + public override string ToString() + { + StringBuilder stringBuilder = new(); + foreach (var key in _config.Keys) + { + stringBuilder.AppendLine($"{key}:{_config[key]}"); + } + //通过属性访问 + // stringBuilder.AppendLine("====加载默认值后的配置项===="); + // Type type = this.GetType(); + // PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + // foreach (PropertyInfo property in properties) + // { + // if (property.CanRead && !property.CanWrite) + // { + // object? value = property.GetValue(this); + // stringBuilder.AppendLine($"{property.Name} = {value}"); + // } + // } + return stringBuilder.ToString(); + } + } +} diff --git a/Common/UserConnect.cs b/Common/UserConnect.cs new file mode 100644 index 0000000..30df99d --- /dev/null +++ b/Common/UserConnect.cs @@ -0,0 +1,32 @@ +using System.Net; + +namespace OMS.NET.Common +{ + public class UserConnect + { + /// + /// ws连接生成的唯一uuid + /// + public string ID { get; set; } + /// + /// ip+port,暂时仅用于服务端日志输出和记录 + /// + public IPEndPoint UserEndPoint { get; set; } + /// + /// 用户邮箱,如果为空则说明此连接未登录 + /// + public string? UserEmail { get; set; } + public UserConnect(string ID, IPEndPoint UserEndPoint) + { + this.ID = ID; + this.UserEndPoint = UserEndPoint; + } + } + + // public class UserData + // { + // public string UserEmail { get; set; } + // public string UserName { get; set; } + // public int Map_Layer { get; set; } + // } +} \ No newline at end of file diff --git a/Common/Utils.cs b/Common/Utils.cs new file mode 100644 index 0000000..0ff551b --- /dev/null +++ b/Common/Utils.cs @@ -0,0 +1,70 @@ +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OMS.NET.Common +{ + public class Point + { + public double X { get; set; } + public double Y { get; set; } + } + + public class Util + { + public static readonly JsonSerializerOptions options = new() + { + ReadCommentHandling = JsonCommentHandling.Skip, //允许注释 + AllowTrailingCommas = true,//允许尾随逗号 + //PropertyNamingPolicy = new InstructNamingPolicy(), // 属性名为定制转换 + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, // 忽略 null 值 + WriteIndented = true, // 美化输出 + PropertyNameCaseInsensitive = true,//属性名忽略大小写 + }; + + public static string DetailsToBase64(object obj) + { + string utf8 = JsonSerializer.Serialize(obj, options); + return Convert.ToBase64String(Encoding.UTF8.GetBytes(utf8)); + } + //=========================================================== + + public static List PointsFromBase64(string base64) + { + string utf8 = Encoding.UTF8.GetString(Convert.FromBase64String(base64)); + return JsonSerializer.Deserialize>(utf8, options) ?? new List(); + } + + public static Point? PointFromBase64(string base64) + { + string utf8 = Encoding.UTF8.GetString(Convert.FromBase64String(base64)); + return JsonSerializer.Deserialize(utf8, options); + } + + //=========================================================== + public static List> DetailsFromBase64(string base64) + { + string utf8 = Encoding.UTF8.GetString(Convert.FromBase64String(base64)); + return JsonSerializer.Deserialize>>(utf8, options) ?? new List>(); + } + + public static Dictionary MembersFromBase64(string base64) + { + string utf8 = Encoding.UTF8.GetString(Convert.FromBase64String(base64)); + return JsonSerializer.Deserialize>(utf8, options) ?? new Dictionary(); + } + + public static List OrderMembersFromBase64(string base64) + { + //string utf8 = Encoding.UTF8.GetString(Convert.FromBase64String(base64)); + return JsonSerializer.Deserialize>(base64, options) ?? new List(); + } + + public static List StructuresFromBase64(string base64) + { + string utf8 = Encoding.UTF8.GetString(Convert.FromBase64String(base64)); + return JsonSerializer.Deserialize>(utf8, options) ?? new List(); + } + + } +} \ No newline at end of file diff --git a/DbClass/AccountData.cs b/DbClass/AccountData.cs new file mode 100644 index 0000000..1bb5293 --- /dev/null +++ b/DbClass/AccountData.cs @@ -0,0 +1,112 @@ +using MySql.Data.MySqlClient; + +namespace OMS.NET.DbClass +{ + public class AccountData + { + #region base + public string UserEmail { get; set; } + public string UserName { get; set; } + public string Password { get; set; } + public int? MapLayer { get; set; } + public string? DefaultA1 { get; set; } + public string? SavePoint { get; set; } + public string? UserQQ { get; set; } + public string? HeadColor { get; set; } + public int Mode { get; set; } + public int Phase { get; set; } + public string? Custom { get; set; } + + public AccountData() + { + this.UserEmail = ""; + this.UserName = ""; + this.Password = ""; + this.Mode = 1; + this.Phase = 1; + //this.Custom = ""; + } + #endregion + public static void Add(AccountData accountData) + { + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + var query = @"INSERT INTO account_data (user_email, user_name, pass_word, map_layer, default_a1, save_point, user_qq, head_color, mode, phase, custom) + VALUES (@UserEmail, @UserName, @Password, @MapLayer, @DefaultA1, @SavePoint, @UserQQ, @HeadColor, @Mode, @Phase, @Custom)"; + using var command = new MySqlCommand(query, connection); + command.Parameters.AddWithValue("@UserEmail", accountData.UserEmail); + command.Parameters.AddWithValue("@UserName", accountData.UserName); + command.Parameters.AddWithValue("@Password", accountData.Password); + command.Parameters.AddWithValue("@MapLayer", accountData.MapLayer ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@DefaultA1", accountData.DefaultA1 ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@SavePoint", accountData.SavePoint ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@UserQQ", accountData.UserQQ ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@HeadColor", accountData.HeadColor ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@Mode", accountData.Mode); + command.Parameters.AddWithValue("@Phase", accountData.Phase); + command.Parameters.AddWithValue("@Custom", accountData.Custom ?? (object)DBNull.Value); + command.ExecuteNonQuery(); + } + + public static void Update(AccountData accountData) + { + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + var query = @"UPDATE account_data SET user_name = @UserName, pass_word = @Password, map_layer = @MapLayer, default_a1 = @DefaultA1, save_point = @SavePoint, user_qq = @UserQQ, head_color = @HeadColor, mode = @Mode, phase = @Phase, custom = @Custom + WHERE user_email = @UserEmail"; + using var command = new MySqlCommand(query, connection); + command.Parameters.AddWithValue("@UserEmail", accountData.UserEmail); + command.Parameters.AddWithValue("@UserName", accountData.UserName); + command.Parameters.AddWithValue("@Password", accountData.Password); + command.Parameters.AddWithValue("@MapLayer", accountData.MapLayer ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@DefaultA1", accountData.DefaultA1 ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@SavePoint", accountData.SavePoint ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@UserQQ", accountData.UserQQ ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@HeadColor", accountData.HeadColor ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@Mode", accountData.Mode); + command.Parameters.AddWithValue("@Phase", accountData.Phase); + command.Parameters.AddWithValue("@Custom", accountData.Custom ?? (object)DBNull.Value); + command.ExecuteNonQuery(); + } + + public static void Delete(string userEmail) + { + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + var query = @"DELETE FROM account_data WHERE user_email = @UserEmail"; + using var command = new MySqlCommand(query, connection); + command.Parameters.AddWithValue("@UserEmail", userEmail); + command.ExecuteNonQuery(); + } + + public static AccountData? Get(string userEmail) + { + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + var query = @"SELECT user_email, user_name, pass_word, map_layer, default_a1, save_point, user_qq, head_color, mode, phase, custom + FROM account_data WHERE user_email = @UserEmail"; + using var command = new MySqlCommand(query, connection); + command.Parameters.AddWithValue("@UserEmail", userEmail); + using var reader = command.ExecuteReader(); + if (reader.Read()) + { + return new AccountData + { + UserEmail = reader.GetString("user_email"), + UserName = reader.GetString("user_name"), + Password = reader.GetString("pass_word"), + MapLayer = reader["map_layer"] as int?, + DefaultA1 = reader["default_a1"] as string, + SavePoint = reader["save_point"] as string, + UserQQ = reader["user_qq"] as string, + HeadColor = reader["head_color"] as string, + Mode = reader.GetInt32("mode"), + Phase = reader.GetInt32("phase"), + Custom = reader["custom"] as string, + }; + } + return null; + } + + } +} \ No newline at end of file diff --git a/DbClass/MapData.cs b/DbClass/MapData.cs new file mode 100644 index 0000000..a0d11f9 --- /dev/null +++ b/DbClass/MapData.cs @@ -0,0 +1,155 @@ +using MySql.Data.MySqlClient; + +namespace OMS.NET.DbClass +{ + public class MapData + { + #region base + public long Id { get; set; } + public string Type { get; set; } + public string Points { get; set; } + public string Point { get; set; } + public string? Color { get; set; } + public int Phase { get; set; } + public int? Width { get; set; } + public string? ChildRelations { get; set; } + public string? FatherRelations { get; set; } + public string? ChildNodes { get; set; } + public string? FatherNode { get; set; } + public string? Details { get; set; } + public string? Custom { get; set; } + + public MapData() + { + this.Id = 0; + this.Type = ""; + this.Points = ""; + this.Point = ""; + this.Phase = 1; + } + #endregion + + public static void Add(MapData mapData) + { + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + var query = @"INSERT INTO map_0_data (type, points, point, color, phase, width, child_relations, father_relations, child_nodes, father_node, details, custom) + VALUES (@Type, @Points, @Point, @Color, @Phase, @Width, @ChildRelations, @FatherRelations, @ChildNodes, @FatherNode, @Details, @Custom)"; + using MySqlCommand command = new(query, connection); + command.Parameters.AddWithValue("@Type", mapData.Type); + command.Parameters.AddWithValue("@Points", mapData.Points); + command.Parameters.AddWithValue("@Point", mapData.Point); + command.Parameters.AddWithValue("@Color", mapData.Color ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@Phase", mapData.Phase); + command.Parameters.AddWithValue("@Width", mapData.Width ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@ChildRelations", mapData.ChildRelations ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@FatherRelations", mapData.FatherRelations ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@ChildNodes", mapData.ChildNodes ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@FatherNode", mapData.FatherNode ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@Details", mapData.Details ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@Custom", mapData.Custom ?? (object)DBNull.Value); + command.ExecuteNonQuery(); + mapData.Id = command.LastInsertedId; + } + + public static int Update(MapData mapData) + { + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + var query = @"UPDATE map_0_data SET type = @Type, points = @Points, point = @Point, color = @Color, phase = @Phase, width = @Width, + child_relations = @ChildRelations, father_relations = @FatherRelations, child_nodes = @ChildNodes, father_node = @FatherNode, + details = @Details, custom = @Custom WHERE id = @Id"; + using MySqlCommand command = new(query, connection); + command.Parameters.AddWithValue("@Id", mapData.Id); + command.Parameters.AddWithValue("@Type", mapData.Type); + command.Parameters.AddWithValue("@Points", mapData.Points); + command.Parameters.AddWithValue("@Point", mapData.Point); + command.Parameters.AddWithValue("@Color", mapData.Color ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@Phase", mapData.Phase); + command.Parameters.AddWithValue("@Width", mapData.Width ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@ChildRelations", mapData.ChildRelations ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@FatherRelations", mapData.FatherRelations ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@ChildNodes", mapData.ChildNodes ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@FatherNode", mapData.FatherNode ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@Details", mapData.Details ?? (object)DBNull.Value); + command.Parameters.AddWithValue("@Custom", mapData.Custom ?? (object)DBNull.Value); + return command.ExecuteNonQuery(); + } + + public static void Delete(long id) + { + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + var query = @"DELETE FROM map_0_data WHERE id = @Id"; + using MySqlCommand command = new(query, connection); + command.Parameters.AddWithValue("@Id", id); + command.ExecuteNonQuery(); + } + + public static MapData? Get(long id) + { + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + var query = @"SELECT id, type, points, point, color, phase, width, child_relations, father_relations, child_nodes, father_node, details, custom + FROM map_0_data WHERE id = @Id"; + using MySqlCommand command = new(query, connection); + command.Parameters.AddWithValue("@Id", id); + using MySqlDataReader reader = command.ExecuteReader(); + if (reader.Read()) + { + return new MapData + { + Id = reader.GetInt64("id"), + Type = reader.GetString("type"), + Points = reader.GetString("points"), + Point = reader.GetString("point"), + Color = reader["color"] as string, + Phase = reader.GetInt32("phase"), + Width = reader["width"] as int?, + ChildRelations = reader["child_relations"] as string, + FatherRelations = reader["father_relations"] as string, + ChildNodes = reader["child_nodes"] as string, + FatherNode = reader["father_node"] as string, + Details = reader["details"] as string, + Custom = reader["custom"] as string + }; + } + return null; + } + + public static List GetMapDataList() + { + List mapDataList = new(); + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + var query = @"SELECT * FROM map_0_data"; + using var command = new MySqlCommand(query, connection); + using var reader = command.ExecuteReader(); + if (reader.HasRows) + { + while (reader.Read()) + { + var mapData = new MapData + { + Id = reader.GetInt64("id"), + Type = reader.GetString("type"), + Points = reader.GetString("points"), + Point = reader.GetString("point"), + Color = reader.IsDBNull(reader.GetOrdinal("color")) ? null : reader.GetString("color"), + Phase = reader.GetInt32("phase"), + Width = reader.IsDBNull(reader.GetOrdinal("width")) ? (int?)null : reader.GetInt32("width"), + ChildRelations = reader.IsDBNull(reader.GetOrdinal("child_relations")) ? null : reader.GetString("child_relations"), + FatherRelations = reader.IsDBNull(reader.GetOrdinal("father_relations")) ? null : reader.GetString("father_relations"), + ChildNodes = reader.IsDBNull(reader.GetOrdinal("child_nodes")) ? null : reader.GetString("child_nodes"), + FatherNode = reader.IsDBNull(reader.GetOrdinal("father_node")) ? null : reader.GetString("father_node"), + Details = reader.IsDBNull(reader.GetOrdinal("details")) ? null : reader.GetString("details"), + Custom = reader.IsDBNull(reader.GetOrdinal("custom")) ? null : reader.GetString("custom") + }; + mapDataList.Add(mapData); + } + } + + return mapDataList; + } + } +} \ No newline at end of file diff --git a/DbClass/MapLayer.cs b/DbClass/MapLayer.cs new file mode 100644 index 0000000..f3f810a --- /dev/null +++ b/DbClass/MapLayer.cs @@ -0,0 +1,142 @@ +using MySql.Data.MySqlClient; + +namespace OMS.NET.DbClass +{ + public class MapLayer + { + #region base + public long Id { get; set; } + public string Type { get; set; } + public string Members { get; set; } + public string Structure { get; set; } + public int Phase { get; set; } + + public MapLayer() + { + this.Id = 0; + this.Type = ""; + this.Members = ""; + this.Structure = ""; + this.Phase = 1; + } + public MapLayer(long id, string type, string members, string structure, int phase) + { + Id = id; + Type = type; + Members = members; + Structure = structure; + Phase = phase; + } + + #endregion + public static void Add(MapLayer mapLayer) + { + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + var query = @"INSERT INTO map_0_layer (type, members, structure, phase) + VALUES (@Type, @Members, @Structure, @Phase)"; + using var command = new MySqlCommand(query, connection); + command.Parameters.AddWithValue("@Type", mapLayer.Type); + command.Parameters.AddWithValue("@Members", mapLayer.Members); + command.Parameters.AddWithValue("@Structure", mapLayer.Structure); + command.Parameters.AddWithValue("@Phase", mapLayer.Phase); + command.ExecuteNonQuery(); + mapLayer.Id = command.LastInsertedId; + } + + public static void Update(MapLayer mapLayer) + { + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + var query = @"UPDATE map_0_layer SET type = @Type, members = @Members, structure = @Structure, phase = @Phase + WHERE id = @Id"; + using var command = new MySqlCommand(query, connection); + command.Parameters.AddWithValue("@Id", mapLayer.Id); + command.Parameters.AddWithValue("@Type", mapLayer.Type); + command.Parameters.AddWithValue("@Members", mapLayer.Members); + command.Parameters.AddWithValue("@Structure", mapLayer.Structure); + command.Parameters.AddWithValue("@Phase", mapLayer.Phase); + command.ExecuteNonQuery(); + } + + public static void Delete(long id) + { + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + var query = @"DELETE FROM map_0_layer WHERE id = @Id"; + using var command = new MySqlCommand(query, connection); + command.Parameters.AddWithValue("@Id", id); + command.ExecuteNonQuery(); + } + + public static MapLayer? Get(long id) + { + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + var query = @"SELECT id, type, members, structure, phase FROM map_0_layer WHERE id = @Id"; + using var command = new MySqlCommand(query, connection); + command.Parameters.AddWithValue("@Id", id); + using var reader = command.ExecuteReader(); + if (reader.Read()) + { + return new MapLayer + { + Id = reader.GetInt64("id"), + Type = reader.GetString("type"), + Members = reader.GetString("members"), + Structure = reader.GetString("structure"), + Phase = reader.GetInt32("phase") + }; + } + return null; + } + + /// + /// 获取所有MapLayer数据 + /// + /// + public static List GetMapLayerList() + { + List mapLayerList = new(); + using MySqlConnection connection = new(GlobalArea.ConnectionStringWithDbName); + connection.Open(); + string query = "SELECT * FROM map_0_layer"; + using var command = new MySqlCommand(query, connection); + using var reader = command.ExecuteReader(); + if (reader.HasRows) + { + while (reader.Read()) + { + var mapLayer = new MapLayer + { + Id = reader.GetInt64("id"), + Type = reader.GetString("type"), + Members = reader.GetString("members"), + Structure = reader.GetString("structure"), + Phase = reader.GetInt32("phase") + }; + mapLayerList.Add(mapLayer); + } + } + return mapLayerList; + } + + /// + /// 指定是否将指定字段编码成base64格式 ??? + /// + public static void ProcessMapLayers(List mapLayers, bool encode = false) + { + mapLayers.ForEach(mapLayer => + { + if (encode) + { + + } + else + { + + } + }); + } + } +} \ No newline at end of file diff --git a/GlobalArea.cs b/GlobalArea.cs new file mode 100644 index 0000000..31cd056 --- /dev/null +++ b/GlobalArea.cs @@ -0,0 +1,302 @@ +using System.Net; +using System.Security.Cryptography; +using System.Text; +using OMS.NET.Common; +using OMS.NET.DbClass; + +namespace OMS.NET +{ + class GlobalArea + { + #region 用户登录列表相关 + private static readonly List _UserConnectList = new(); + private static readonly object _UserConnectListLock = new(); + /// + /// 连接客户端数量 + /// + public static int ConnectClientsCount + { + get + { + lock (_UserConnectListLock) + { + return _UserConnectList.Count; + } + } + } + public static int LoginUserCount + { + get + { + lock (_UserConnectListLock) + { + return _UserConnectList + .Where(u => u.UserEmail != null) // 过滤出UserEmail不为空的项 + .Select(u => u.UserEmail) // 选择UserEmail + .Distinct().Count(); // 去重且计算数量 + } + } + } + public static List UserConnects + { + get + { + lock (_UserConnectListLock) + { + return _UserConnectList; + } + } + } + /// + /// 添加ws连接记录 + /// + public static void AddUserConnect(string wsid, IPEndPoint iPEndPoint) + { + lock (_UserConnectListLock) + { + UserConnect userConnect = new(wsid, iPEndPoint); + _UserConnectList.Add(userConnect); + } + } + public static void RemoveUserConnectByID(string wsid) + { + lock (_UserConnectListLock) + { + UserConnect? userConnect = _UserConnectList.Find(u => u.ID == wsid); + if (userConnect != null) + { + _UserConnectList.Remove(userConnect); + } + } + } + + public static bool LoginCheckByID(string wsid) + { + lock (_UserConnectListLock) + { + return _UserConnectList.Exists(u => u.ID == wsid && u.UserEmail != null); + } + } + + // public static string GetLoginEmailByID(string wsid){ + // lock (_UserConnectListLock){ + // return _UserConnectList.First(u => u.ID == wsid).UserEmail!; + // } + // } + + public static bool LoginCheckByEmail(string email) + { + lock (_UserConnectListLock) + { + return _UserConnectList.Exists(u => u.UserEmail == email); + } + } + /// + /// 添加登录记录 + /// + public static void Login(string wsid, string email) + { + lock (_UserConnectListLock) + { + UserConnect? userConnect = _UserConnectList.Find(u => u.ID == wsid); + if (userConnect != null) + { + userConnect.UserEmail = email; + } + } + } + + #endregion + + #region 服务器配置相关 + private static ServerConfig _ServerConfig = new(); + private static readonly object _ServerConfigLock = new(); + + /// + /// 提供只读服务器配置访问 + /// + public static ServerConfig ServerConfig + { + get + { + lock (_ServerConfigLock) + { + return _ServerConfig; + } + } + } + + /// + /// 提供完整的数据库连接字符串,带数据库名称 + /// + public static string ConnectionStringWithDbName + { + get + { + lock (_ServerConfigLock) + { + return _ServerConfig.ConnectionString + ";database=" + _ServerConfig.DataBaseName; + } + } + } + public static void LoadServerConfig() + { + lock (_ServerConfigLock) + { + string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"server.yaml"); + //读取配置文件 + _ServerConfig = new(configPath); + Console.WriteLine("已读取到配置文件..." + Environment.NewLine + _ServerConfig.ToString()); + } + } + #endregion + + #region RSA加解密相关 + private static readonly RSA _Rsa = RSA.Create(); + private static readonly object _RsaLock = new(); + + public static RSA Rsa + { + get + { + lock (_RsaLock) + { + return _Rsa; + } + } + } + + /// + /// 导入指定私钥,如果不存在则创建并保存 + /// + public static void LoadRsaPkey() + { + lock (_RsaLock) + { + string pkeyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @".pkey"); + if (File.Exists(pkeyPath)) + { + DateTime lastModifyTime = File.GetLastWriteTime(pkeyPath); + bool isOneYearApart = Math.Abs((DateTime.Now - lastModifyTime).TotalDays) >= 365; + if (isOneYearApart) + { + File.WriteAllText(pkeyPath, Convert.ToBase64String(_Rsa.ExportPkcs8PrivateKey())); + } + else + { + string pkey = File.ReadAllText(pkeyPath); + Console.WriteLine("继续使用现有pkey: " + pkey); + _Rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(pkey), out _); + } + + } + else + { + File.WriteAllText(pkeyPath, Convert.ToBase64String(_Rsa.ExportPkcs8PrivateKey())); + Console.WriteLine("创建pkey:" + File.ReadAllText(pkeyPath)); + } + + } + } + + /// + /// 以base64格式导出公钥字符串 + /// + /// 公钥字符串,base64格式 + public static string GetRsaPublickKey() + { + lock (_RsaLock) + { + return Convert.ToBase64String(_Rsa.ExportRSAPublicKey()); + } + } + + /// + /// 解密 + /// + /// base64编码的密文 + /// 原文字符串 utf-8编码 + public static string DecryptFromBase64String(string base64) + { + lock (_RsaLock) + { + byte[] decrypt = _Rsa.Decrypt(Convert.FromBase64String(base64), + RSAEncryptionPadding.Pkcs1); + return Encoding.UTF8.GetString(decrypt); + } + } + + /// + /// 加密 + /// + /// 原文字符串 utf-8编码 + /// base64编码的密文 + public static string EncryptToBase64String(string src) + { + lock (_RsaLock) + { + byte[] encrypt = _Rsa.Encrypt(Encoding.UTF8.GetBytes(src), + RSAEncryptionPadding.Pkcs1); + return Convert.ToBase64String(encrypt); + } + } + + #endregion + + #region 缓存与活动数据 + + private static readonly List _AccountDataList = new(); + private static readonly object _AccountDataListLock = new(); + + /// + /// 缓存已查询用户数据,如果已存在则替换,需要在数据更新后调用 + /// + public static void AddLoginAccountData(AccountData? accountData) + { + if (accountData == null) return; + lock (_AccountDataListLock) + { + int index = _AccountDataList.FindIndex(u => u.UserEmail == accountData.UserEmail); + if (index == -1) + { + _AccountDataList.Add(accountData); + } + else + { + _AccountDataList[index] = accountData; + } + } + } + + /// + /// 提供一个简单获取登录数据的方法,而不需要重复进行数据库查询 + /// + public static AccountData? GetLoginAccountData(string? email) + { + if (email == null) return null; + lock (_AccountDataListLock) + { + return _AccountDataList.Find(u => u.UserEmail == email); + } + } + + #endregion + + /// + /// 默认loglevel为3,发布版本需要改成2 + /// + private static Logger _Logger = new(3); + private static readonly object _LoggerLock = new(); + + public static Logger Log + { + get + { + lock (_LoggerLock) + { + return _Logger; + } + } + } + } +} \ No newline at end of file diff --git a/Instructs/BroadcastInstuct.cs b/Instructs/BroadcastInstuct.cs new file mode 100644 index 0000000..0ab7c0a --- /dev/null +++ b/Instructs/BroadcastInstuct.cs @@ -0,0 +1,31 @@ +using System.Text.Json; + +namespace OMS.NET.Instructs +{ + /// + /// 广播指令的基类 + /// + public class BroadcastInstuct : Instruct + { + public BroadcastInstuct() + { + this.Type = "broadcast"; + } + } + + public class SendCorrectInstuct : Instruct + { + public SendCorrectInstuct() + { + this.Type = "send_error"; + } + } + + public class SendErrorInstuct : Instruct + { + public SendErrorInstuct() + { + this.Type = "send_correct"; + } + } +} \ No newline at end of file diff --git a/Instructs/GetMapDataInstruct.cs b/Instructs/GetMapDataInstruct.cs new file mode 100644 index 0000000..bb4041a --- /dev/null +++ b/Instructs/GetMapDataInstruct.cs @@ -0,0 +1,36 @@ +using OMS.NET.DbClass; + +namespace OMS.NET.Instructs +{ + public class GetMapDataInstruct : Instruct + { + public GetMapDataInstruct() + { + this.Type = "get_mapData"; + } + + public override Task Handler(string wsid) + { + return Task.Run(() => + { + if (GlobalArea.LoginCheckByID(wsid)) + { + List mapDatas = MapData.GetMapDataList().Where(m => m.Phase != 2).ToList(); + this.ResponseOrBroadcastInstructs.Add(new SendMapDataInstruct() + { + IsResponse = true, + Data = mapDatas + }); + } + }); + } + } + + public class SendMapDataInstruct : Instruct + { + public SendMapDataInstruct() + { + this.Type = "send_mapData"; + } + } +} \ No newline at end of file diff --git a/Instructs/GetMapLayerInstruct.cs b/Instructs/GetMapLayerInstruct.cs new file mode 100644 index 0000000..b91d681 --- /dev/null +++ b/Instructs/GetMapLayerInstruct.cs @@ -0,0 +1,36 @@ +using OMS.NET.DbClass; + +namespace OMS.NET.Instructs +{ + public class GetMapLayerInstruct : Instruct + { + public GetMapLayerInstruct() + { + this.Type = "get_mapLayer"; + } + + public override Task Handler(string wsid) + { + return Task.Run(() => + { + if (GlobalArea.LoginCheckByID(wsid)) + { + List mapLayers = MapLayer.GetMapLayerList().Where(m => m.Phase != 2).ToList(); + this.ResponseOrBroadcastInstructs.Add(new SendMapLayerInstruct() + { + IsResponse = true, + Data = mapLayers + }); + } + }); + } + } + + public class SendMapLayerInstruct : Instruct + { + public SendMapLayerInstruct() + { + this.Type = "send_mapLayer"; + } + } +} \ No newline at end of file diff --git a/Instructs/GetPresenceInstruct.cs b/Instructs/GetPresenceInstruct.cs new file mode 100644 index 0000000..9c5c588 --- /dev/null +++ b/Instructs/GetPresenceInstruct.cs @@ -0,0 +1,52 @@ +using OMS.NET.DbClass; + +namespace OMS.NET.Instructs +{ + public class GetPresenceInstruct : Instruct + { + public GetPresenceInstruct() + { + this.Type = "get_presence"; + } + + public override Task Handler(string wsid) + { + return Task.Run(() => + { + if (GlobalArea.LoginCheckByID(wsid)) + { + //获取所有在线用户,移除重复 + List emails = GlobalArea.UserConnects.Where(u => u.UserEmail != null) + .Select(u => u.UserEmail).Distinct().ToList(); + List allLoginUsers = new(); + emails.ForEach(email => + { + AccountData accountData = GlobalArea.GetLoginAccountData(email)!; + //由于是已登录用户,默认都能查询到值 + allLoginUsers.Add(new + { + userEmail = accountData.UserEmail, + userQq = accountData.UserQQ, + userName = accountData.UserName, + headColor = accountData.HeadColor, + }); + }); + this.ResponseOrBroadcastInstructs.Add(new SendPresenceInstruct() + { + IsResponse = true, + Data = allLoginUsers, + }); + + } + }); + } + } + + public class SendPresenceInstruct : Instruct + { + public SendPresenceInstruct() + { + this.Type = "send_presence"; + } + } +} \ No newline at end of file diff --git a/Instructs/GetPublickeyInstruct.cs b/Instructs/GetPublickeyInstruct.cs new file mode 100644 index 0000000..9e6d02a --- /dev/null +++ b/Instructs/GetPublickeyInstruct.cs @@ -0,0 +1,32 @@ +namespace OMS.NET.Instructs +{ + public class GetPublickeyInstruct : Instruct + { + public GetPublickeyInstruct() + { + this.Type = "get_publickey"; + } + + public override Task Handler(string wsid) + { + return Task.Run(() => + { + this.ResponseOrBroadcastInstructs.Add(new PublickeyInstruct() + { + Data = GlobalArea.GetRsaPublickKey(), + IsResponse = true + }); + //Thread.Sleep(9000);//模拟耗时操作 + }); + } + } + + public class PublickeyInstruct : Instruct + { + public PublickeyInstruct() + { + this.Type = "publickey"; + } + + } +} \ No newline at end of file diff --git a/Instructs/GetServerConfigInstruct.cs b/Instructs/GetServerConfigInstruct.cs new file mode 100644 index 0000000..365b5f2 --- /dev/null +++ b/Instructs/GetServerConfigInstruct.cs @@ -0,0 +1,47 @@ +namespace OMS.NET.Instructs +{ + public class GetServerConfigInstruct : Instruct + { + public GetServerConfigInstruct() + { + this.Type = "get_serverConfig"; + } + + public override Task Handler(string wsid) + { + return Task.Run(() => + { + this.ResponseOrBroadcastInstructs.Add(new SendServerConfigInstruct + { + IsResponse = true, + Data = new + { + anonymous_login = bool.TryParse(GlobalArea.ServerConfig.AnonymousLogin, out bool booleanValue) && booleanValue, + key = GlobalArea.ServerConfig.Key, + url = GlobalArea.ServerConfig.Url, + name = GlobalArea.ServerConfig.Name, + online_number = GlobalArea.ConnectClientsCount, + max_online = GlobalArea.ServerConfig.MaxUser, + default_x = GlobalArea.ServerConfig.DefaultX, + default_y = GlobalArea.ServerConfig.DefaultY, + //以下在0.7会删除 + enable_base_map = GlobalArea.ServerConfig.EnableBase, + //base_map_type= GlobalArea.ServerConfig.ServerConfigEnableBase, + max_zoom = GlobalArea.ServerConfig.MaxZoom, + min_zoom = GlobalArea.ServerConfig.MinZoom, + default_zoom = GlobalArea.ServerConfig.DefaultZoom, + base_map_url = GlobalArea.ServerConfig.BaseMapUrl + } + }); + }); + } + } + + public class SendServerConfigInstruct : Instruct + { + public SendServerConfigInstruct() + { + this.Type = "send_serverConfig"; + } + } +} \ No newline at end of file diff --git a/Instructs/GetServerImgInstruct.cs b/Instructs/GetServerImgInstruct.cs new file mode 100644 index 0000000..c0ba598 --- /dev/null +++ b/Instructs/GetServerImgInstruct.cs @@ -0,0 +1,84 @@ +using System.Globalization; +using WebSocketSharp; + +namespace OMS.NET.Instructs +{ + public class GetServerImgInstruct : Instruct + { + public GetServerImgInstruct() + { + this.Type = "get_serverImg"; + } + + public override Task Handler(string wsid) + { + return Task.Run(() => + { + if (this.Time != null) + { + //时间解析 + string format = "yyyy-MM-dd HH:mm:ss"; + DateTime clientDateTime = DateTime.ParseExact(this.Time, format, CultureInfo.InvariantCulture); + //Console.WriteLine("Converted DateTime: " + clientDateTime); + string serverImgPath = GlobalArea.ServerConfig.Img; + if (File.Exists(serverImgPath)) + { + DateTime serverImgLastModified = File.GetLastWriteTime(serverImgPath); + Instruct res = new SendServerConfigInstruct + { + IsResponse = true + }; + if (serverImgLastModified > clientDateTime) + { + byte[] imageBytes = File.ReadAllBytes(serverImgPath); + string imageFileType = GetImageFileType(imageBytes); + string base64String = $"data:image/{imageFileType};base64," + Convert.ToBase64String(imageBytes); + //Console.WriteLine("Base64 Encoded Image: " + base64String); + res.Data = new + { + @string = base64String, + time = serverImgLastModified.ToString(format), + }; + } + else + { + res.Data = new + { + @string = "" + }; + } + this.ResponseOrBroadcastInstructs.Add(res); + } + } + }); + } + + static string GetImageFileType(byte[] imageBytes) + { + byte[] fileHeader = imageBytes.SubArray(0, 8); + + // PNG 文件的文件头字节为 89 50 4E 47 0D 0A 1A 0A + if (fileHeader[0] == 0x89 && fileHeader[1] == 0x50 && fileHeader[2] == 0x4E && fileHeader[3] == 0x47 && + fileHeader[4] == 0x0D && fileHeader[5] == 0x0A && fileHeader[6] == 0x1A && fileHeader[7] == 0x0A) + { + return "png"; + } + + // JPEG 文件的文件头字节为 FF D8 FF + if (fileHeader[0] == 0xFF && fileHeader[1] == 0xD8 && fileHeader[2] == 0xFF) + { + return "jpeg"; + } + + return "unknown"; + } + } + + public class SendServerImgInstruct : Instruct + { + public SendServerImgInstruct() + { + this.Type = "send_serverImg"; + } + } +} \ No newline at end of file diff --git a/Instructs/GetUserDataInstruct.cs b/Instructs/GetUserDataInstruct.cs new file mode 100644 index 0000000..6131274 --- /dev/null +++ b/Instructs/GetUserDataInstruct.cs @@ -0,0 +1,48 @@ +using OMS.NET.DbClass; + +namespace OMS.NET.Instructs +{ + public class GetUserDataInstruct : Instruct + { + public GetUserDataInstruct() + { + this.Type = "get_userData"; + } + + public override Task Handler(string wsid) + { + return Task.Run(() => + { + if (GlobalArea.LoginCheckByID(wsid)) + { + string userEmail = GlobalArea.UserConnects.First(u => u.ID == wsid).UserEmail!; + AccountData accountData = GlobalArea.GetLoginAccountData(userEmail)!; + this.ResponseOrBroadcastInstructs.Add(new SendUserDataInstruct() + { + IsResponse = true, + Data = new + { + user_email = accountData.UserEmail, + user_name = accountData.UserName, + map_layer = accountData.MapLayer, + default_a1 = accountData.DefaultA1, + save_point = accountData.SavePoint, + head_color = accountData.HeadColor, + mode = accountData.Mode, + phase = accountData.Phase, + custom = accountData.Custom, + } + }); + } + }); + } + } + + public class SendUserDataInstruct : Instruct + { + public SendUserDataInstruct() + { + this.Type = "send_userData"; + } + } +} \ No newline at end of file diff --git a/Instructs/Instruct.cs b/Instructs/Instruct.cs new file mode 100644 index 0000000..bd5997a --- /dev/null +++ b/Instructs/Instruct.cs @@ -0,0 +1,165 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OMS.NET.Instructs +{ + /// + /// 声明json转换的命名策略 + /// + class InstructNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + return Convert(name); + } + + public static string Convert(string name) => name switch + { + "Type" => "type", + "Class" => "class", + "Conveyor" => "conveyor", + "Time" => "time", + "Data" => "data", + _ => name, + }; + } + + /// + /// 实现依据type和class的值决定反序列化的对象类型 + /// + class InstructConverter : JsonConverter + { + public override Instruct Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using JsonDocument doc = JsonDocument.ParseValue(ref reader); + JsonElement root = doc.RootElement; + //Console.WriteLine(root.GetRawText()); + if (root.TryGetProperty("type", out JsonElement typeProperty)) + { + string? typeValue = typeProperty.GetString(); + string? classValue = root.TryGetProperty("class", out JsonElement classProperty) ? classProperty.GetString() : null; + Instruct? instruct = typeValue switch + { + //实现新指令需在这里添加反序列化类型,限定为客户端发过来的指令类型 + "get_publickey" => JsonSerializer.Deserialize(root.GetRawText(), options), + "get_serverImg" => JsonSerializer.Deserialize(root.GetRawText(), options), + "get_serverConfig" => JsonSerializer.Deserialize(root.GetRawText(), options), + "login" => JsonSerializer.Deserialize(root.GetRawText(), options), + "test" => JsonSerializer.Deserialize(root.GetRawText(), options), + "get_userData" => JsonSerializer.Deserialize(root.GetRawText(), options), + "get_presence" => JsonSerializer.Deserialize(root.GetRawText(), options), + "get_mapData" => JsonSerializer.Deserialize(root.GetRawText(), options), + "get_mapLayer" => JsonSerializer.Deserialize(root.GetRawText(), options), + + //广播指令 + "broadcast" => classValue switch + { + //广播指令继承结构 + "" => JsonSerializer.Deserialize(root.GetRawText(), options), + _ => JsonSerializer.Deserialize(root.GetRawText(), options) + }, + _ => null + } ?? throw new JsonException("反序列化失败"); + return instruct; + } + else + { + throw new JsonException("type property not found"); + } + } + + public override void Write(Utf8JsonWriter writer, Instruct value, JsonSerializerOptions options) + { + //JsonSerializer.Serialize(writer, (object)value, options); 这个在Data是对象时导致了无限递归调用 + writer.WriteStartObject(); + writer.WriteString(InstructNamingPolicy.Convert(nameof(Instruct.Type)), value.Type); + if (value.Class != null) + { + writer.WriteString(InstructNamingPolicy.Convert(nameof(Instruct.Class)), value.Class); + } + if (value.Conveyor != null) + { + writer.WriteString(InstructNamingPolicy.Convert(nameof(Instruct.Conveyor)), value.Conveyor); + } + if (value.Time != null) + { + writer.WriteString(InstructNamingPolicy.Convert(nameof(Instruct.Time)), value.Time); + } + if (value.Data != null) + { + writer.WritePropertyName(InstructNamingPolicy.Convert(nameof(Instruct.Data))); + JsonSerializer.Serialize(writer, value.Data, value.Data.GetType(), options); + } + writer.WriteEndObject(); + } + } + + public class Instruct + { + public static readonly JsonSerializerOptions options = new() + { + ReadCommentHandling = JsonCommentHandling.Skip, //允许注释 + AllowTrailingCommas = true,//允许尾随逗号 + PropertyNamingPolicy = new InstructNamingPolicy(), // 属性名为定制转换 + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, // 忽略 null 值 + WriteIndented = true, // 美化输出 + //PropertyNameCaseInsensitive = true,//属性名忽略大小写 + Converters = { new InstructConverter() }, + }; + public string Type { get; set; } + public string? Class { get; set; } + public string? Conveyor { get; set; } + public string? Time { get; set; } + public dynamic? Data { get; set; } + + [JsonIgnore] + public bool IsResponse { get; set; } = false; + [JsonIgnore] + public bool IsBroadcast { get; set; } = false; + [JsonIgnore] + public List ResponseOrBroadcastInstructs { get; set; } = new(); + + public Instruct() + { + this.Type = ""; + } + + /// + /// 指令处理逻辑 + /// + /// 将耗时任务交给Task以不阻塞单个连接的多个请求 + public virtual Task Handler(string wsid) + { + return Task.CompletedTask; + } + + public async Task HandlerAndMeasure(string wsid) + { + Stopwatch stopWatch = new(); + stopWatch.Start(); + await Handler(wsid); + stopWatch.Stop(); + GlobalArea.Log.Debug($"处理{this.GetType()}耗时:{stopWatch.ElapsedMilliseconds}ms"); + } + + public string ToJsonString() + { + return JsonSerializer.Serialize(this, options); + } + + public static Instruct? JsonStringParse(string jsonString) + { + try + { + return JsonSerializer.Deserialize(jsonString, options); + } + catch (Exception ex) + { + GlobalArea.Log.Error(ex.Message); + return null; + } + } + } +} \ No newline at end of file diff --git a/Instructs/LoginInstruct.cs b/Instructs/LoginInstruct.cs new file mode 100644 index 0000000..e8a78f2 --- /dev/null +++ b/Instructs/LoginInstruct.cs @@ -0,0 +1,80 @@ + +using System.Text.Json; +using OMS.NET.DbClass; + +namespace OMS.NET.Instructs +{ + /// + /// 登录指令 + /// + public class LoginInstruct : Instruct + { + public LoginInstruct() + { + this.Type = "login"; + } + + public override Task Handler(string wsid) + { + return Task.Run(() => + { + if (this.Data?.GetType() == typeof(JsonElement)) + { + string email = this.Data.GetProperty("email").GetString(); + string password = this.Data.GetProperty("password").GetString(); + if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password)) + { + GlobalArea.Log.Warn("登录信息不能为空!"); + return; + } + //能够获取到则说明客户端有发送数据过来 + Instruct res1 = new LoginStatusInstuct + { + Data = false, + IsResponse = true + };//默认为false + //Console.WriteLine($"已获取到{email}:{password},解密测试{GlobalArea.DecryptFromBase64String(password)}"); + AccountData? accountData = AccountData.Get(email); + GlobalArea.AddLoginAccountData(accountData); + if (accountData != null) + { + //只能原文比较,密文每次都不一样,涉及随机性填充 + if (accountData.Password == GlobalArea.DecryptFromBase64String(password)) + { + res1.Data = true;//登录成功则修改为true + this.ResponseOrBroadcastInstructs.Add(res1); + GlobalArea.Log.Info($"{accountData.UserEmail}:登录成功"); + GlobalArea.Login(wsid, accountData.UserEmail); + GlobalArea.Log.Info($"当前登录用户数量: {GlobalArea.LoginUserCount}"); + this.ResponseOrBroadcastInstructs.Add(new Instruct + { + Type = "broadcast", + Class = "logIn", + Data = new + { + userEmail = accountData.UserEmail, + userName = accountData.UserName, + headColor = accountData.HeadColor, + userQq = accountData.UserQQ, + }, + IsBroadcast = true + }); + } + else + { + this.ResponseOrBroadcastInstructs.Add(res1); + } + } + } + }); + } + } + + public class LoginStatusInstuct : Instruct + { + public LoginStatusInstuct() + { + this.Type = "loginStatus"; + } + } +} \ No newline at end of file diff --git a/Instructs/PingInstuct.cs b/Instructs/PingInstuct.cs new file mode 100644 index 0000000..47551ef --- /dev/null +++ b/Instructs/PingInstuct.cs @@ -0,0 +1,32 @@ +namespace OMS.NET.Instructs +{ + /// + /// 用于测试和代码复制 + /// + public class PingInstuct : Instruct + { + public PingInstuct() + { + this.Type = "ping"; + } + + public override Task Handler(string wsid) + { + return Task.Run(() => + { + this.ResponseOrBroadcastInstructs.Add(new PongInstuct() + { + IsResponse = true + }); + }); + } + } + + public class PongInstuct : Instruct + { + public PongInstuct() + { + this.Type = "pong"; + } + } +} \ No newline at end of file diff --git a/Instructs/TestInstuct.cs b/Instructs/TestInstuct.cs new file mode 100644 index 0000000..90b4821 --- /dev/null +++ b/Instructs/TestInstuct.cs @@ -0,0 +1,23 @@ +namespace OMS.NET.Instructs +{ + /// + /// 用于测试和代码复制 + /// + public class TestInstruct : Instruct + { + public TestInstruct() + { + this.Type = "test"; + } + + public override Task Handler(string wsid) + { + return Task.Run(() => + { + //GlobalArea.UserConnects.Where(x => x.ID == wsid).First().UserEmail = "xxx@xx.com";//login + GlobalArea.Log.Info("当前客户端连接数:" + GlobalArea.ConnectClientsCount); + GlobalArea.Log.Info("当前登录用户数:" + GlobalArea.LoginUserCount); + }); + } + } +} \ No newline at end of file diff --git a/MapServer.cs b/MapServer.cs new file mode 100644 index 0000000..2731691 --- /dev/null +++ b/MapServer.cs @@ -0,0 +1,70 @@ +using System.Net; +using System.Text; +using System.Text.Unicode; +using OMS.NET.Instructs; +using WebSocketSharp; +using WebSocketSharp.Server; +using ErrorEventArgs = WebSocketSharp.ErrorEventArgs; +namespace OMS.NET +{ + public class MapServer : WebSocketBehavior + { + private IPEndPoint iPEndPoint = new(IPAddress.Any, 0); + protected override async void OnMessage(MessageEventArgs e) + { + GlobalArea.Log.Info(this.ID + " " + this.Context.UserEndPoint.ToString() + ":" + e.Data); + Instruct? instruct = Instruct.JsonStringParse(e.Data); + if (instruct != null) + { + await instruct.HandlerAndMeasure(this.ID);//传递ws连接的id,为某些需要判断ws连接状态的处理逻辑准备 + if (instruct.ResponseOrBroadcastInstructs.Count > 0) + { + instruct.ResponseOrBroadcastInstructs.ForEach(res => + { + if (res.IsResponse) + { + string str = res.ToJsonString(); + Send(str); + //GlobalArea.Log.Debug("已发送回复 " + str); + } + if (res.IsBroadcast) + { + string str = res.ToJsonString(); + foreach (IWebSocketSession session in this.Sessions.Sessions) + { + //看起来只有登录后的连接才能收到广播,这里添加下过滤 + if (GlobalArea.LoginCheckByID(session.ID)) + { + session.Context.WebSocket.Send(str); + } + } + //GlobalArea.Log.Debug("已发送广播 " + str); + } + }); + } + } + } + + protected override void OnOpen() + { + this.iPEndPoint = this.Context.UserEndPoint; + GlobalArea.AddUserConnect(this.ID, this.iPEndPoint); + Console.WriteLine(this.ID + " " + this.iPEndPoint.ToString() + " Conection Open"); + Console.WriteLine($"当前连接客户端数量: {GlobalArea.ConnectClientsCount}"); + } + + protected override void OnClose(CloseEventArgs e) + { + GlobalArea.RemoveUserConnectByID(this.ID); + Console.WriteLine(this.ID + " " + this.iPEndPoint.ToString() + " Conection Close" + e.Reason); + Console.WriteLine($"当前连接客户端数量: {GlobalArea.ConnectClientsCount}"); + } + + protected override void OnError(ErrorEventArgs e) + { + GlobalArea.RemoveUserConnectByID(this.ID); + Console.WriteLine(this.ID + " " + this.iPEndPoint.ToString() + " Conection Error Close" + e.Message); + Console.WriteLine($"当前连接客户端数量: {GlobalArea.ConnectClientsCount}"); + } + } +} diff --git a/OMS.NET.csproj b/OMS.NET.csproj new file mode 100644 index 0000000..06a87c5 --- /dev/null +++ b/OMS.NET.csproj @@ -0,0 +1,26 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + Always + + + Always + + + Always + + + diff --git a/OMS.NET.sln b/OMS.NET.sln new file mode 100644 index 0000000..e15155f --- /dev/null +++ b/OMS.NET.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OMS.NET", "OMS.NET.csproj", "{604E2FC2-C98A-46AB-AB27-49E0101DC376}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {604E2FC2-C98A-46AB-AB27-49E0101DC376}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {604E2FC2-C98A-46AB-AB27-49E0101DC376}.Debug|Any CPU.Build.0 = Debug|Any CPU + {604E2FC2-C98A-46AB-AB27-49E0101DC376}.Release|Any CPU.ActiveCfg = Release|Any CPU + {604E2FC2-C98A-46AB-AB27-49E0101DC376}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D0739889-D7E5-4D7C-A77F-511F50DA6EF5} + EndGlobalSection +EndGlobal diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..6da2346 --- /dev/null +++ b/Program.cs @@ -0,0 +1,108 @@ +using WebSocketSharp.Server; +using MySql.Data.MySqlClient; +using System.Text; +using System.Security.Cryptography; +using OMS.NET.Instructs; +using System.Dynamic; +using System.Security.Cryptography.X509Certificates; + +namespace OMS.NET +{ + class Program + { + static void Main(string[] args) + { + try + { + //判断是否满足运行条件 + GlobalArea.LoadServerConfig();//加载服务器配置 默认server.yaml + GlobalArea.LoadRsaPkey();//加载rsa私钥 默认.pkey + DbCheck(); + + //开启ws监听 + var wssv = new WebSocketServer(Convert.ToInt16(GlobalArea.ServerConfig.ListenPort), false); + //wssv.SslConfiguration.ServerCertificate = new X509Certificate2(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "server.pfx")); + wssv.AddWebSocketService("/"); + wssv.Start(); + Console.WriteLine("已开启ws监听, 端口: " + GlobalArea.ServerConfig.ListenPort); + Console.WriteLine("输入exit退出程序"); + bool loopFlag = true; + while (loopFlag) + { + string input = Console.ReadLine() ?? ""; + switch (input.Trim()) + { + case "exit": + loopFlag = false; + break; + case "logins": + Console.WriteLine("当前登录用户 " + GlobalArea.LoginUserCount); + break; + default: + break; + } + } + wssv.Stop(); + } + catch (Exception e) + { + GlobalArea.Log.Error(e.Message); + } + } + + /// + /// 检查数据库,如果不满足预设条件则初始化 + /// + static void DbCheck() + { + //.dblock存在,则默认数据库结构无问题 + string dblockPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @".dblock"); + string initsqlPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"init.sql"); + if (!File.Exists(dblockPath)) + { + using MySqlConnection connection = new(GlobalArea.ServerConfig.ConnectionString); + connection.Open(); + GlobalArea.Log.Info("dblock不存在,连接到数据库服务进行检查"); + //检查数据库是否存在,检查数据表是否 + // 检查数据库是否存在 + string dbName = GlobalArea.ServerConfig.DataBaseName; + string checkDatabaseQuery = $"SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '{dbName}'"; + MySqlCommand checkDatabaseCmd = new(checkDatabaseQuery, connection); + object result = checkDatabaseCmd.ExecuteScalar(); + + if (result == null) + { + // 指定数据库不存在,选择创建数据库及相关表结构 + string createDatabaseQuery = $"CREATE DATABASE {dbName} CHARACTER SET utf8mb4"; + MySqlCommand createDatabaseCmd = new(createDatabaseQuery, connection); + createDatabaseCmd.ExecuteNonQuery(); + GlobalArea.Log.Info($"已创建数据库 {dbName}"); + } + + //创建默认数据表,实现通用逻辑,从init.sql读取创建相关表的sql语句,默认以分号结尾,忽略空行和换行 + //如果表已存在则sql应当不会执行任何操作,需要在sql添加 IF NOT EXISTS + if (File.Exists(initsqlPath)) + { + string useDb = $"USE {dbName}"; + MySqlCommand createDatabaseCmd = new(useDb, connection); + createDatabaseCmd.ExecuteNonQuery(); + File.ReadAllText(initsqlPath) + .Split(new[] { ';' }) + .Select(s => s.Trim()) + .Where(s => !string.IsNullOrWhiteSpace(s)) + .ToList().ForEach(sql => + { + MySqlCommand createDatabaseCmd = new(sql, connection); + createDatabaseCmd.ExecuteNonQuery(); + GlobalArea.Log.Info($"执行SQL成功 {sql}"); + }); + } + File.WriteAllText(dblockPath, ""); + } + else + { + GlobalArea.Log.Info("满足预设条件,跳过数据库检查"); + } + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..dc895ef --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# OMS.NET +OMS.NET为标准名称,使用.NET 6开发,语言使用C#。 +一个跨平台的OME服务端的.NET实现。 + +## 先决条件 +### 确保.NET开发环境已安装 +- [.NET SDK](https://dotnet.microsoft.com/download) + +### 创建server.yaml +模板如下,按情况修改user、pas、port +需准备默认的map_img.png +```yaml +#连接字符串和默认监听端口 +ConnectionString: server=localhost;port=3306;user=user;password=pas +DataBaseName: map_test1 +ListenPort: port + +#匿名登录 +AnonymousLogin: false +#服务器基础信息 +#ServerConfigImg: ./map_img.png +#ServerConfigKey: k0 +#ServerConfigUrl: ws://0.0.0.0:1080 +#ServerConfigName: 地图名称 +#ServerConfigMaxUser: 20 + +#以下四个常量规定了地图在什么区域内进行绘制 +#ServerConfigMaxHeight: 10000 +#ServerConfigMinHeight: -10000 +#ServerConfigMaxWidth: 10000 +#ServerConfigMinWidth: -10000 + +#以下两个常量决定了区域内横轴和纵轴的最小层级下(layer0)每像素移动量单位 +#ServerConfigUnit1Y: 1 +#ServerConfigUnit1X: 1 + +#打开地图时的默认中心点 +#ServerConfigP0X: 0 +#ServerConfigP0Y: 0 + +#层级为地图数据的缩放层级,可限制用户在高层级编辑地图导致严重误差 +#ServerConfigMaxLayer: 5 +#ServerConfigMinLayer: 0 +#ServerConfigDefaultLayer: 0 +#ServerConfigZoomAdd: 1 + +#底图配置 +#缩放比为带有底图瓦图服务器的地图服务,可限制用户对底图缩放 +#ServerConfigEnableBase: false +#ServerConfigDefaultX: 0 +#ServerConfigDefaultY: 0 +#ServerConfigResolutionX: 1920 +#ServerConfigResolutionY: 980 +#ServerConfigMaxZoom: 0 +#ServerConfigMinZoom: 0 +#ServerConfigDefaultZoom: 0 +#ServerConfigBaseMapUrl: empty +``` + + +## 快速开始 +```bash +git clone https://xxx.git +cd OMS.NET +dotnet restore +dotnet build +dotnet run +``` \ No newline at end of file diff --git a/init.sql b/init.sql new file mode 100644 index 0000000..06ec3fc --- /dev/null +++ b/init.sql @@ -0,0 +1,40 @@ +CREATE TABLE IF NOT EXISTS map_0_data ( + id BIGINT(20) NOT NULL AUTO_INCREMENT, + type VARCHAR(12) NOT NULL, + points MEDIUMTEXT NOT NULL, + point VARCHAR(255) NOT NULL, + color VARCHAR(255), + phase INT(1) NOT NULL, + width INT(11), + child_relations MEDIUMTEXT, + father_relations VARCHAR(255), + child_nodes MEDIUMTEXT, + father_node VARCHAR(255), + details MEDIUMTEXT, + custom MEDIUMTEXT, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS map_0_layer ( + id BIGINT(20) NOT NULL AUTO_INCREMENT, + type VARCHAR(255) NOT NULL, + members MEDIUMTEXT NOT NULL, + structure MEDIUMTEXT NOT NULL, + phase INT(1) NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS account_data ( + user_email VARCHAR(255) NOT NULL, + user_name VARCHAR(255) NOT NULL, + pass_word VARCHAR(255) NOT NULL, + map_layer INT(11), + default_a1 VARCHAR(255), + save_point MEDIUMTEXT, + user_qq VARCHAR(255), + head_color VARCHAR(6), + mode INT(1) NOT NULL, + phase INT(1) NOT NULL, + custom MEDIUMTEXT, + PRIMARY KEY (user_email) +); \ No newline at end of file