初次提交

master
nxiaoxiao 1 year ago
commit a214b95c82

22
.gitignore vendored

@ -0,0 +1,22 @@
# 忽略所有编译输出和构建工件
bin/
obj/
# Visual Studio 文件
.vs/
*.user
*.suo
# NuGet 包
*.nupkg
*.snupkg
# 忽略发布和部署文件
*.publishsettings
publish/
#一些功能标识文件
.pkey
.dblock
server.yaml
map_img.png

@ -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<ActiveDataElement>
{
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 };
}
}
}

@ -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);
}
}
}

@ -0,0 +1,307 @@
using System.Reflection;
using System.Text;
using System.Text.Json;
namespace OMS.NET.Common
{
public sealed class ServerConfig
{
private Dictionary<string, string> _config = new();
/// <summary>
/// 连接数据库的连接字符串 default Empty
/// 默认不带数据库名称
/// </summary>
public string ConnectionString => _config.TryGetValue("ConnectionString", out string? value)
? value
: "";
/// <summary>
/// 数据库名称 default map_db
/// </summary>
public string DataBaseName => _config.TryGetValue("DataBaseName", out string? value)
? value
: "map_db";
/// <summary>
/// 监听端口 default 8080
/// </summary>
public string ListenPort => _config.TryGetValue("ListenPort", out string? value)
? value
: "8080";
/// <summary>
/// 是否开启匿名登录 default false
/// </summary>
public string AnonymousLogin => _config.TryGetValue("AnonymousLogin", out string? value)
? value
: "false";
/// <summary>
/// 地图的背景图片位置图像尺寸为220px * 165px 最大为60kb 支持PNG和jpg类型的图片格式
/// default ./map_img.png
/// </summary>
public string Img => _config.TryGetValue("ServerConfigImg", out string? value)
? value
: "./map_img.png";
/// <summary>
/// key是唯一的,长度不限格式k[a-Z0-9]是客户端采用https://name.com/ + m/key 的方式快捷访问地图服务器的
/// default k0
/// </summary>
public string Key => _config.TryGetValue("ServerConfigKey", out string? value)
? value
: "k0";
/// <summary>
/// 服务器websocket链接的地址
/// default 'ws://0.0.0.0:8080'
/// </summary>
public string Url => _config.TryGetValue("ServerConfigUrl", out string? value)
? value
: "ws://0.0.0.0:8080";
/// <summary>
/// 服务器名称
/// default 地图名称
/// </summary>
public string Name => _config.TryGetValue("ServerConfigName", out string? value)
? value
: "地图名称";
/// <summary>
/// 服务器最大在线人数
/// default 20
/// </summary>
public string MaxUser => _config.TryGetValue("ServerConfigMaxUser", out string? value)
? value
: "20";
/// <summary>
/// 最大高度
/// default 10000
/// </summary>
public string MaxHeight => _config.TryGetValue("ServerConfigMaxHeight", out string? value)
? value
: "10000";
/// <summary>
/// 最小高度
/// default -10000
/// </summary>
public string MinHeight => _config.TryGetValue("ServerConfigMinHeight", out string? value)
? value
: "-10000";
/// <summary>
/// 最大宽度
/// default 10000
/// </summary>
public string MaxWidth => _config.TryGetValue("ServerConfigMaxWidth", out string? value)
? value
: "10000";
/// <summary>
/// 最小宽度
/// default -10000
/// </summary>
public string MinWidth => _config.TryGetValue("ServerConfigMinWidth", out string? value)
? value
: "-10000";
/// <summary>
/// 区域内横轴和纵轴的最小层级下layer0每像素移动量单位 纵轴单位
/// default 1
/// </summary>
public string Unit1Y => _config.TryGetValue("ServerConfigUnit1Y", out string? value)
? value
: "1";
/// <summary>
/// 区域内横轴和纵轴的最小层级下layer0每像素移动量单位 横轴单位
/// default 1
/// </summary>
public string Unit1X => _config.TryGetValue("ServerConfigUnit1X", out string? value)
? value
: "1";
/// <summary>
/// 打开地图时的默认中心点 x
/// default 0
/// </summary>
public string P0X => _config.TryGetValue("ServerConfigP0X", out string? value)
? value
: "0";
/// <summary>
/// 打开地图时的默认中心点 y
/// default 0
/// </summary>
public string P0Y => _config.TryGetValue("ServerConfigP0Y", out string? value)
? value
: "0";
/// <summary>
/// 最大层级
/// default 5
/// </summary>
public string MaxLayer => _config.TryGetValue("ServerConfigMaxLayer", out string? value)
? value
: "default";
/// <summary>
/// 最小层级
/// default 0
/// </summary>
public string MinLayer => _config.TryGetValue("ServerConfigMinLayer", out string? value)
? value
: "default";
/// <summary>
/// 默认层级
/// default 0
/// </summary>
public string DefaultLayer => _config.TryGetValue("ServerConfigDefaultLayer", out string? value)
? value
: "0";
/// <summary>
/// 默认缩放比例
/// default 1
/// </summary>
public string ZoomAdd => _config.TryGetValue("ServerConfigZoomAdd", out string? value)
? value
: "1";
/// <summary>
/// 表示是否启用额外的底图服务
/// default false
/// </summary>
public string EnableBase => _config.TryGetValue("ServerConfigEnableBase", out string? value)
? value
: "false";
/// <summary>
/// 默认x
/// default 0
/// </summary>
public string DefaultX => _config.TryGetValue("ServerConfigDefaultX", out string? value)
? value
: "0";
/// <summary>
/// 默认y
/// default 0
/// </summary>
public string DefaultY => _config.TryGetValue("ServerConfigDefaultY", out string? value)
? value
: "0";
/// <summary>
/// 屏幕默认分辨率x
/// default 1920
/// </summary>
public string ResolutionX => _config.TryGetValue("ServerConfigResolutionX", out string? value)
? value
: "1920";
/// <summary>
/// 屏幕默认分辨率y
/// default 980
/// </summary>
public string ResolutionY => _config.TryGetValue("ServerConfigResolutionY", out string? value)
? value
: "980";
/// <summary>
/// 最大底图缩放等级
/// default 0
/// </summary>
public string MaxZoom => _config.TryGetValue("ServerConfigMaxZoom", out string? value)
? value
: "0";
/// <summary>
/// 最小底图缩放等级
/// default 0
/// </summary>
public string MinZoom => _config.TryGetValue("ServerConfigMinZoom", out string? value)
? value
: "0";
/// <summary>
/// 默认底图缩放等级
/// default 0
/// </summary>
public string DefaultZoom => _config.TryGetValue("ServerConfigDefaultZoom", out string? value)
? value
: "0";
/// <summary>
/// 底图服务的服务器URL
/// default empty
/// </summary>
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();
}
}
}

@ -0,0 +1,32 @@
using System.Net;
namespace OMS.NET.Common
{
public class UserConnect
{
/// <summary>
/// ws连接生成的唯一uuid
/// </summary>
public string ID { get; set; }
/// <summary>
/// ip+port暂时仅用于服务端日志输出和记录
/// </summary>
public IPEndPoint UserEndPoint { get; set; }
/// <summary>
/// 用户邮箱,如果为空则说明此连接未登录
/// </summary>
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; }
// }
}

@ -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<Point> PointsFromBase64(string base64)
{
string utf8 = Encoding.UTF8.GetString(Convert.FromBase64String(base64));
return JsonSerializer.Deserialize<List<Point>>(utf8, options) ?? new List<Point>();
}
public static Point? PointFromBase64(string base64)
{
string utf8 = Encoding.UTF8.GetString(Convert.FromBase64String(base64));
return JsonSerializer.Deserialize<Point>(utf8, options);
}
//===========================================================
public static List<KeyValuePair<string, string>> DetailsFromBase64(string base64)
{
string utf8 = Encoding.UTF8.GetString(Convert.FromBase64String(base64));
return JsonSerializer.Deserialize<List<KeyValuePair<string, string>>>(utf8, options) ?? new List<KeyValuePair<string, string>>();
}
public static Dictionary<string, string> MembersFromBase64(string base64)
{
string utf8 = Encoding.UTF8.GetString(Convert.FromBase64String(base64));
return JsonSerializer.Deserialize<Dictionary<string, string>>(utf8, options) ?? new Dictionary<string, string>();
}
public static List<long> OrderMembersFromBase64(string base64)
{
//string utf8 = Encoding.UTF8.GetString(Convert.FromBase64String(base64));
return JsonSerializer.Deserialize<List<long>>(base64, options) ?? new List<long>();
}
public static List<object> StructuresFromBase64(string base64)
{
string utf8 = Encoding.UTF8.GetString(Convert.FromBase64String(base64));
return JsonSerializer.Deserialize<List<object>>(utf8, options) ?? new List<object>();
}
}
}

@ -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;
}
}
}

@ -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<MapData> GetMapDataList()
{
List<MapData> 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;
}
}
}

@ -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;
}
/// <summary>
/// 获取所有MapLayer数据
/// </summary>
/// <returns></returns>
public static List<MapLayer> GetMapLayerList()
{
List<MapLayer> 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;
}
/// <summary>
/// 指定是否将指定字段编码成base64格式 ???
/// </summary>
public static void ProcessMapLayers(List<MapLayer> mapLayers, bool encode = false)
{
mapLayers.ForEach(mapLayer =>
{
if (encode)
{
}
else
{
}
});
}
}
}

@ -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<UserConnect> _UserConnectList = new();
private static readonly object _UserConnectListLock = new();
/// <summary>
/// 连接客户端数量
/// </summary>
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<UserConnect> UserConnects
{
get
{
lock (_UserConnectListLock)
{
return _UserConnectList;
}
}
}
/// <summary>
/// 添加ws连接记录
/// </summary>
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);
}
}
/// <summary>
/// 添加登录记录
/// </summary>
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();
/// <summary>
/// 提供只读服务器配置访问
/// </summary>
public static ServerConfig ServerConfig
{
get
{
lock (_ServerConfigLock)
{
return _ServerConfig;
}
}
}
/// <summary>
/// 提供完整的数据库连接字符串,带数据库名称
/// </summary>
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;
}
}
}
/// <summary>
/// 导入指定私钥,如果不存在则创建并保存
/// </summary>
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));
}
}
}
/// <summary>
/// 以base64格式导出公钥字符串
/// </summary>
/// <returns>公钥字符串base64格式</returns>
public static string GetRsaPublickKey()
{
lock (_RsaLock)
{
return Convert.ToBase64String(_Rsa.ExportRSAPublicKey());
}
}
/// <summary>
/// 解密
/// </summary>
/// <param name="base64">base64编码的密文</param>
/// <returns>原文字符串 utf-8编码</returns>
public static string DecryptFromBase64String(string base64)
{
lock (_RsaLock)
{
byte[] decrypt = _Rsa.Decrypt(Convert.FromBase64String(base64),
RSAEncryptionPadding.Pkcs1);
return Encoding.UTF8.GetString(decrypt);
}
}
/// <summary>
/// 加密
/// </summary>
/// <param name="src">原文字符串 utf-8编码</param>
/// <returns>base64编码的密文</returns>
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<AccountData> _AccountDataList = new();
private static readonly object _AccountDataListLock = new();
/// <summary>
/// 缓存已查询用户数据,如果已存在则替换,需要在数据更新后调用
/// </summary>
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;
}
}
}
/// <summary>
/// 提供一个简单获取登录数据的方法,而不需要重复进行数据库查询
/// </summary>
public static AccountData? GetLoginAccountData(string? email)
{
if (email == null) return null;
lock (_AccountDataListLock)
{
return _AccountDataList.Find(u => u.UserEmail == email);
}
}
#endregion
/// <summary>
/// 默认loglevel为3发布版本需要改成2
/// </summary>
private static Logger _Logger = new(3);
private static readonly object _LoggerLock = new();
public static Logger Log
{
get
{
lock (_LoggerLock)
{
return _Logger;
}
}
}
}
}

@ -0,0 +1,31 @@
using System.Text.Json;
namespace OMS.NET.Instructs
{
/// <summary>
/// 广播指令的基类
/// </summary>
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";
}
}
}

@ -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<MapData> 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";
}
}
}

@ -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<MapLayer> 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";
}
}
}

@ -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<string?> emails = GlobalArea.UserConnects.Where(u => u.UserEmail != null)
.Select(u => u.UserEmail).Distinct().ToList();
List<dynamic> 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";
}
}
}

@ -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";
}
}
}

@ -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";
}
}
}

@ -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";
}
}
}

@ -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";
}
}
}

@ -0,0 +1,165 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace OMS.NET.Instructs
{
/// <summary>
/// 声明json转换的命名策略
/// </summary>
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,
};
}
/// <summary>
/// 实现依据type和class的值决定反序列化的对象类型
/// </summary>
class InstructConverter : JsonConverter<Instruct>
{
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<GetPublickeyInstruct>(root.GetRawText(), options),
"get_serverImg" => JsonSerializer.Deserialize<GetServerImgInstruct>(root.GetRawText(), options),
"get_serverConfig" => JsonSerializer.Deserialize<GetServerConfigInstruct>(root.GetRawText(), options),
"login" => JsonSerializer.Deserialize<LoginInstruct>(root.GetRawText(), options),
"test" => JsonSerializer.Deserialize<TestInstruct>(root.GetRawText(), options),
"get_userData" => JsonSerializer.Deserialize<GetUserDataInstruct>(root.GetRawText(), options),
"get_presence" => JsonSerializer.Deserialize<GetPresenceInstruct>(root.GetRawText(), options),
"get_mapData" => JsonSerializer.Deserialize<GetMapDataInstruct>(root.GetRawText(), options),
"get_mapLayer" => JsonSerializer.Deserialize<GetMapLayerInstruct>(root.GetRawText(), options),
//广播指令
"broadcast" => classValue switch
{
//广播指令继承结构
"" => JsonSerializer.Deserialize<BroadcastInstuct>(root.GetRawText(), options),
_ => JsonSerializer.Deserialize<Instruct>(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<Instruct> ResponseOrBroadcastInstructs { get; set; } = new();
public Instruct()
{
this.Type = "";
}
/// <summary>
/// 指令处理逻辑
/// </summary>
/// <returns>将耗时任务交给Task以不阻塞单个连接的多个请求</returns>
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<Instruct>(jsonString, options);
}
catch (Exception ex)
{
GlobalArea.Log.Error(ex.Message);
return null;
}
}
}
}

@ -0,0 +1,80 @@
using System.Text.Json;
using OMS.NET.DbClass;
namespace OMS.NET.Instructs
{
/// <summary>
/// 登录指令
/// </summary>
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";
}
}
}

@ -0,0 +1,32 @@
namespace OMS.NET.Instructs
{
/// <summary>
/// 用于测试和代码复制
/// </summary>
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";
}
}
}

@ -0,0 +1,23 @@
namespace OMS.NET.Instructs
{
/// <summary>
/// 用于测试和代码复制
/// </summary>
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);
});
}
}
}

@ -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}");
}
}
}

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MySql.Data" Version="9.0.0" />
<PackageReference Include="WebSocketSharp-netstandard" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<None Update="server.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="init.sql">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="map_img.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

@ -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

@ -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<MapServer>("/");
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);
}
}
/// <summary>
/// 检查数据库,如果不满足预设条件则初始化
/// </summary>
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("满足预设条件,跳过数据库检查");
}
}
}
}

@ -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
```

@ -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)
);
Loading…
Cancel
Save