添加 chatserver 项目并更新相关配置

在 `chatclient.sln` 中添加 `chatserver` 项目,配置调试和发布设置。更新 `App.config` 中 `log4net` 的配置路径。修改 `TrayIconManager.cs` 中的分隔符样式引用。更新 `chatapi.cs` 中的服务器地址和相关数据结构,增加 `userid` 和 `token` 字段。优化 `LoginWindow.xaml` 和 `MainWindow.xaml.cs` 的布局和逻辑,确保用户 ID 正确处理。更新 `log4net.config` 日志格式,添加控制台输出。配置 `chatserver.csproj` 的依赖项,添加服务器基本逻辑和消息类型枚举。更新 `launchSettings.json` 启动配置。
This commit is contained in:
绪山七寻 2025-06-15 12:51:51 +08:00
parent 6364f5f4d1
commit 686ef24cde
16 changed files with 609 additions and 105 deletions

View File

@ -1,10 +1,12 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.13.35931.197 d17.13 VisualStudioVersion = 17.13.35931.197
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "chatclient", "chatclient\chatclient.csproj", "{6965B200-D0AA-4729-A5A4-30DFD307EB80}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "chatclient", "chatclient\chatclient.csproj", "{6965B200-D0AA-4729-A5A4-30DFD307EB80}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "chatserver", "chatserver\chatserver.csproj", "{5AE823C9-2275-45F1-B312-0D1ED4237A2D}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -15,6 +17,10 @@ Global
{6965B200-D0AA-4729-A5A4-30DFD307EB80}.Debug|Any CPU.Build.0 = Debug|Any CPU {6965B200-D0AA-4729-A5A4-30DFD307EB80}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6965B200-D0AA-4729-A5A4-30DFD307EB80}.Release|Any CPU.ActiveCfg = Release|Any CPU {6965B200-D0AA-4729-A5A4-30DFD307EB80}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6965B200-D0AA-4729-A5A4-30DFD307EB80}.Release|Any CPU.Build.0 = Release|Any CPU {6965B200-D0AA-4729-A5A4-30DFD307EB80}.Release|Any CPU.Build.0 = Release|Any CPU
{5AE823C9-2275-45F1-B312-0D1ED4237A2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5AE823C9-2275-45F1-B312-0D1ED4237A2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5AE823C9-2275-45F1-B312-0D1ED4237A2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5AE823C9-2275-45F1-B312-0D1ED4237A2D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -3,5 +3,5 @@
<configSections> <configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" /> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections> </configSections>
<log4net configSource="config/log4net.config" /> <log4net configSource="config\log4net.config" />
</configuration> </configuration>

View File

@ -48,7 +48,7 @@ namespace chatclient.Data
// 添加菜单项 // 添加菜单项
contextMenu.Items.Add(CreateMenuItem("打开程序", PackIconKind.WindowRestore, OpenApp_Click)); contextMenu.Items.Add(CreateMenuItem("打开程序", PackIconKind.WindowRestore, OpenApp_Click));
contextMenu.Items.Add(CreateMenuItem("设置", PackIconKind.Cog, Settings_Click)); contextMenu.Items.Add(CreateMenuItem("设置", PackIconKind.Cog, Settings_Click));
contextMenu.Items.Add(new Separator { Style = (Style)Application.Current.Resources["MaterialDesignLightSeparator"] }); contextMenu.Items.Add(new Separator { Style = (Style)Application.Current.Resources["Separator"] });
//contextMenu.Items.Add(CreateMenuItem("Check for Updates", PackIconKind.Update, Updates_Click)); //contextMenu.Items.Add(CreateMenuItem("Check for Updates", PackIconKind.Update, Updates_Click));
//contextMenu.Items.Add(CreateMenuItem("Help", PackIconKind.HelpCircle, Help_Click)); //contextMenu.Items.Add(CreateMenuItem("Help", PackIconKind.HelpCircle, Help_Click));
//contextMenu.Items.Add(new Separator { Style = (Style)Application.Current.Resources["MaterialDesignLightSeparator"] }); //contextMenu.Items.Add(new Separator { Style = (Style)Application.Current.Resources["MaterialDesignLightSeparator"] });

View File

@ -4,9 +4,9 @@ namespace chatclient.Data
{ {
internal class Server internal class Server
{ {
public const string ServerUrl = "http://175.24.191.172:5001"; public const string ServerUrl = "http://127.0.0.1:5001";
public const string ServerIP = "175.24.191.172"; public const string ServerIP = "127.0.0.1";
public const int ServerPort = 8889; public const int ServerPort = 52006;
} }
internal class LoginData internal class LoginData
{ {
@ -21,6 +21,7 @@ namespace chatclient.Data
public string? message { get; set; } public string? message { get; set; }
public string? token { get; set; } public string? token { get; set; }
public string? username { get; set; } public string? username { get; set; }
public string? userid { get; set; } = "Unid";
} }
internal class SignData internal class SignData
{ {
@ -30,7 +31,7 @@ namespace chatclient.Data
} }
internal class SignResultData internal class SignResultData
{ {
public bool success { get; set; } = false; public string? status { get; set; } = null;
public string? message { get; set; } = null; public string? message { get; set; } = null;
} }
internal class RegisterData internal class RegisterData
@ -40,14 +41,19 @@ namespace chatclient.Data
internal class ChatRegisterData internal class ChatRegisterData
{ {
public string? user { get; set; } = "Unnamed"; public string? user { get; set; } = "Unnamed";
public required string userid { get; set; } = "Unid";
public string? status { get; set; } = null; public string? status { get; set; } = null;
public string? message { get; set; } = null; public string? message { get; set; } = null;
public string? image { get; set; } = null; public string? avatar { get; set; } = null;
public DateTime timestamp { get; set; } = DateTime.Now; public MessageType? msgtype { get; set; } = MessageType.Text;
public DateTime? timestamp { get; set; } = DateTime.Now;
} }
internal class ChatData internal class ChatData
{ {
public required string type { get; set; } = "chat"; public required string type { get; set; } = "chat";
public required string message { get; set; } = "message"; public required string message { get; set; } = "message";
public MessageType? msgtype { get; set; } = MessageType.Text;
public required string userid { get; set; } = "Unid";
public string? token { get; set; } = null; // 添加token字段
} }
} }

View File

@ -9,7 +9,6 @@
mc:Ignorable="d" mc:Ignorable="d"
Title="LoginWindow" Height="540" Width="330" MinHeight="540" MinWidth="330" MaxHeight="540" MaxWidth="330" Title="LoginWindow" Height="540" Width="330" MinHeight="540" MinWidth="330" MaxHeight="540" MaxWidth="330"
ResizeMode="NoResize" Closing="Window_Closing" Loaded="Window_Loaded"> ResizeMode="NoResize" Closing="Window_Closing" Loaded="Window_Loaded">
<TabControl> <TabControl>
<TabItem Header="登录账号" Cursor="Hand" Height="40"> <TabItem Header="登录账号" Cursor="Hand" Height="40">
<Grid Background="#FFE5E5E5"> <Grid Background="#FFE5E5E5">

View File

@ -108,14 +108,6 @@ namespace chatclient
if (Task.IsCompletedSuccessfully) if (Task.IsCompletedSuccessfully)
{ {
log.Info("注册请求发送成功"); log.Info("注册请求发送成功");
Application.Current.Dispatcher.Invoke(() =>
{
var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
if (loginWindow != null)
{
loginWindow.SignMsg = "注册请求已发送,请等待服务器响应";
}
});
} }
else else
{ {
@ -187,47 +179,47 @@ namespace chatclient
} }
}); });
} }
public static async Task HttpSignRegistryUser(string Username, string Userpassword) //public static async Task HttpSignRegistryUser(string Username, string Userpassword)
{ //{
try // try
{ // {
var SignData = new // var SignData = new
{ // {
type = "register", // type = "register",
username = Username, // username = Username,
password = Userpassword // password = Userpassword
}; // };
string SignJsonData = JsonSerializer.Serialize(SignData); // string SignJsonData = JsonSerializer.Serialize(SignData);
byte[] dataBytes = Encoding.UTF8.GetBytes(SignJsonData); // byte[] dataBytes = Encoding.UTF8.GetBytes(SignJsonData);
var content = new StringContent(SignJsonData, Encoding.UTF8, "application/json"); // var content = new StringContent(SignJsonData, Encoding.UTF8, "application/json");
var response = await MainWindow.HttpClient.PostAsync($"{Server.ServerUrl}/api/register", content); // var response = await MainWindow.HttpClient.PostAsync($"{Server.ServerUrl}/api/register", content);
var responseBody = await response.Content.ReadAsStringAsync(); // var responseBody = await response.Content.ReadAsStringAsync();
log.Info($"注册请求已发送,响应内容: {responseBody}"); // log.Info($"注册请求已发送,响应内容: {responseBody}");
var signresponse = JsonSerializer.Deserialize<SignResultData>(responseBody); // var signresponse = JsonSerializer.Deserialize<SignResultData>(responseBody);
if (signresponse!.success) // if (signresponse!.status)
{ // {
log.Info($"注册成功: {signresponse.message}"); // log.Info($"注册成功: {signresponse.message}");
await Login(true, Username, Userpassword); // await Login(true, Username, Userpassword);
} // }
else // else
{ // {
log.Error($"注册失败: {signresponse.message}"); // log.Error($"注册失败: {signresponse.message}");
Application.Current.Dispatcher.Invoke(() => // Application.Current.Dispatcher.Invoke(() =>
{ // {
var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault(); // var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
loginWindow!.SignMsg = signresponse.message; // loginWindow!.SignMsg = signresponse.message;
}); // });
} // }
} // }
catch (Exception ex) // catch (Exception ex)
{ // {
log.Error("注册请求发送失败", ex); // log.Error("注册请求发送失败", ex);
} // }
finally // finally
{ // {
log.Info("注册请求已完成"); // log.Info("注册请求已完成");
} // }
} // }
public static async Task Login(bool Sign, string Username, string Userpassword) public static async Task Login(bool Sign, string Username, string Userpassword)
{ {
// 公共的登录数据准备 // 公共的登录数据准备
@ -341,7 +333,7 @@ namespace chatclient
log.Error("保存登录信息到临时文件失败", ex); log.Error("保存登录信息到临时文件失败", ex);
} }
} }
if (MainWindow.token == null) Application.Current.Shutdown(); if (MainWindow.UserId == null) Application.Current.Shutdown();
} }
} }
} }

View File

@ -138,10 +138,17 @@
<StackPanel Width="auto" Height="auto"> <StackPanel Width="auto" Height="auto">
<materialDesign:PackIcon Width="24" Height="24" HorizontalAlignment="Center" Kind="Cog" /> <materialDesign:PackIcon Width="24" Height="24" HorizontalAlignment="Center" Kind="Cog" />
<TextBlock HorizontalAlignment="Center" Text="设置" /> <TextBlock HorizontalAlignment="Center" Text="设置" />
</StackPanel> </StackPanel>
</TabItem.Header> </TabItem.Header>
<StackPanel> <StackPanel Margin="10">
<TextBlock Text="这里没有东西..."/> <TextBlock Text="设置" FontSize="20" FontWeight="Bold" Margin="0,0,0,12"/>
<Separator Margin="0 0" Width="auto" Height="10" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
<TextBlock Text="关于" FontSize="20" FontWeight="Bold" Margin="0,0,0,12"/>
<TextBlock Text="ChatClient 聊天室" FontSize="16" Margin="0,0,0,8"/>
<TextBlock Text="版本 1.0.0" Margin="0,0,0,8"/>
<TextBlock Text="作者DZY &amp; 绪山七寻" Margin="0,0,0,8"/>
<TextBlock Text="本项目基于 .NET 8 和 MaterialDesignInXAML。" TextWrapping="Wrap" Margin="0,0,0,8"/>
</StackPanel> </StackPanel>
</TabItem> </TabItem>
</TabControl> </TabControl>

View File

@ -35,6 +35,7 @@ namespace chatclient
static string? receive; static string? receive;
public static string UserName { get; set; } = "?"; public static string UserName { get; set; } = "?";
public static string? token = null; public static string? token = null;
public static string? UserId = null;
private static readonly ILog log = LogManager.GetLogger(typeof(MainWindow)); private static readonly ILog log = LogManager.GetLogger(typeof(MainWindow));
public static Socket? Client; public static Socket? Client;
public static readonly HttpClient HttpClient = new HttpClient(); public static readonly HttpClient HttpClient = new HttpClient();
@ -133,12 +134,13 @@ namespace chatclient
var Type = JsonSerializer.Deserialize<RegisterData>(msg); var Type = JsonSerializer.Deserialize<RegisterData>(msg);
if (Type != null) if (Type != null)
{ {
if (Type.type == "login_1") if (Type.type == "login")
{ {
var LoginResponse = JsonSerializer.Deserialize<LoginResultData>(msg); var LoginResponse = JsonSerializer.Deserialize<LoginResultData>(msg);
if (LoginResponse!.status == "success" && LoginResponse != null) if (LoginResponse!.status == "succeed" && LoginResponse != null)
{ {
token = LoginResponse.token; //token = LoginResponse.token;
UserId = LoginResponse.userid ?? "Unid";
UserName = LoginResponse.username ?? "Unnamed"; UserName = LoginResponse.username ?? "Unnamed";
Application.Current.Dispatcher.Invoke(() => Application.Current.Dispatcher.Invoke(() =>
{ {
@ -155,7 +157,7 @@ namespace chatclient
Sender = "System", Sender = "System",
MsgType = MessageType.System, MsgType = MessageType.System,
Image = new BitmapImage(new Uri("pack://application:,,,/resource/user.png")), Image = new BitmapImage(new Uri("pack://application:,,,/resource/user.png")),
Content = $"你好 {UserName} !", Content = $"你好 {UserName} (id: {UserId} )!",
Timestamp = DateTime.Now, Timestamp = DateTime.Now,
Alignment = HorizontalAlignment.Center, Alignment = HorizontalAlignment.Center,
SenderColor = new SolidColorBrush(Colors.Gray) SenderColor = new SolidColorBrush(Colors.Gray)
@ -163,7 +165,31 @@ namespace chatclient
mainWindow?.Messages.Add(chatmessage); mainWindow?.Messages.Add(chatmessage);
} }
}); });
log.Info($"用户 {UserName} 登录成功(token:{token})"); log.Info($"用户 {UserName} 登录成功(token:{token},userid:{UserId})");
}
else if (LoginResponse!.status == "error_0")
{
log.Warn($"登录失败: {LoginResponse!.message}\nMsg:{msg}");
Application.Current.Dispatcher.Invoke(() =>
{
var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
if (loginWindow != null)
{
loginWindow.LoginMsg = "用户名或密码错误";
}
});
}
else if (LoginResponse!.status == "error_2")
{
log.Warn($"登录失败: {LoginResponse!.message}\nMsg:{msg}");
Application.Current.Dispatcher.Invoke(() =>
{
var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
if (loginWindow != null)
{
loginWindow.LoginMsg = "用户名长度必须在2到20个字符之间";
}
});
} }
else else
{ {
@ -177,41 +203,68 @@ namespace chatclient
}); });
} }
} }
else if (Type.type == "login_0") else if (Type.type == "register")
{
var LoginResponse = JsonSerializer.Deserialize<LoginResultData>(msg);
log.Warn($"登录失败: {LoginResponse!.message}\nMsg:{msg}");
Application.Current.Dispatcher.Invoke(() =>
{
var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
if (loginWindow != null)
{
loginWindow.LoginMsg = "用户名或密码错误";
}
});
}
else if (Type.type == "register_1")
{ {
var SignResponse = JsonSerializer.Deserialize<SignResultData>(msg); var SignResponse = JsonSerializer.Deserialize<SignResultData>(msg);
log.Warn($"注册成功\nMsg:{msg}"); if (SignResponse!.status == "success")
Application.Current.Dispatcher.Invoke(async () =>
{ {
//var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault(); log.Warn($"注册成功\nMsg:{msg}");
await LoginWindow.Login(true,LoginWindow.SignName, LoginWindow.SignPassword1); Application.Current.Dispatcher.Invoke(async () =>
});
}
else if (Type.type == "register_0")
{
var SignResponse = JsonSerializer.Deserialize<SignResultData>(msg);
log.Warn($"注册失败: {SignResponse!.message}\nMsg:{msg}");
Application.Current.Dispatcher.Invoke(() =>
{
var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
if (loginWindow != null)
{ {
loginWindow.SignMsg = "用户名已存在"; //var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
await LoginWindow.Login(true, LoginWindow.SignName, LoginWindow.SignPassword1);
});
}
else if (SignResponse!.status == "error_1")
{
log.Warn($"注册失败: {SignResponse!.message}\nMsg:{msg}");
Application.Current.Dispatcher.Invoke(() =>
{
var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
if (loginWindow != null)
{
loginWindow.SignMsg = "密码长度必须在4到20个字符之间";
}
});
}
else if (SignResponse!.status == "error_2")
{
log.Warn($"注册失败: {SignResponse!.message}\nMsg:{msg}");
Application.Current.Dispatcher.Invoke(() =>
{
var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
if (loginWindow != null)
{
loginWindow.SignMsg = "用户名长度必须在2到20个字符之间";
}
});
}
else if (SignResponse!.status == "error_3")
{
log.Warn($"注册失败: {SignResponse!.message}\nMsg:{msg}");
{
Application.Current.Dispatcher.Invoke(() =>
{
var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
if (loginWindow != null)
{
loginWindow.SignMsg = "用户名已存在";
}
});
} }
}); }
else
{
log.Error($"注册失败: {SignResponse!.message}\nMsg:{msg}");
Application.Current.Dispatcher.Invoke(() =>
{
var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
if (loginWindow != null)
{
loginWindow.SignMsg = SignResponse != null ? SignResponse.message : "服务器返回错误";
}
});
}
} }
else if (Type.type == "chat") else if (Type.type == "chat")
{ {
@ -221,15 +274,15 @@ namespace chatclient
Application.Current.Dispatcher.Invoke(() => Application.Current.Dispatcher.Invoke(() =>
{ {
// 处理聊天消息 // 处理聊天消息
if (chat.user == UserName) if (chat.userid == UserId)
{ {
var chatmessage = new ChatMessage var chatmessage = new ChatMessage
{ {
Sender = chat.user ?? "未知用户", Sender = chat.user ?? "未知用户",
MsgType = MessageType.Text, MsgType = MessageType.Text,
Image = new BitmapImage(new Uri(chat.image ?? "pack://application:,,,/resource/user.png", UriKind.Absolute)), Image = new BitmapImage(new Uri(chat.avatar ?? "pack://application:,,,/resource/user.png")),
Content = chat.message ?? "(无内容)", Content = chat.message ?? "(无内容)",
Timestamp = DateTime.Now, Timestamp = chat.timestamp ?? DateTime.Now,
Alignment = HorizontalAlignment.Right, Alignment = HorizontalAlignment.Right,
SenderColor = new SolidColorBrush(Colors.Blue) SenderColor = new SolidColorBrush(Colors.Blue)
}; };
@ -242,9 +295,9 @@ namespace chatclient
{ {
Sender = chat.user ?? "未知用户", Sender = chat.user ?? "未知用户",
MsgType = MessageType.Text, MsgType = MessageType.Text,
Image = new BitmapImage(new Uri(chat.image ?? "pack://application:,,,/resource/user.png", UriKind.Absolute)), Image = new BitmapImage(new Uri(chat.avatar ?? "pack://application:,,,/resource/user.png")),
Content = chat.message ?? "(无内容)", Content = chat.message ?? "(无内容)",
Timestamp = chat.timestamp, Timestamp = chat.timestamp ?? DateTime.Now,
Alignment = HorizontalAlignment.Left, Alignment = HorizontalAlignment.Left,
SenderColor = new SolidColorBrush(Colors.Black) SenderColor = new SolidColorBrush(Colors.Black)
}; };
@ -258,6 +311,7 @@ namespace chatclient
log.Error("反序列化聊天数据时返回了 null"); log.Error("反序列化聊天数据时返回了 null");
} }
} }
else if (Type.type == "ping") { }
else else
{ {
log.Error($"未知的消息类型: {Type.type},请检查服务器响应格式"); log.Error($"未知的消息类型: {Type.type},请检查服务器响应格式");
@ -307,7 +361,10 @@ namespace chatclient
var newChatMessage = new ChatData var newChatMessage = new ChatData
{ {
type = "chat", type = "chat",
message = txtMessage.Text message = txtMessage.Text,
msgtype = MessageType.Text,
userid = UserId ?? "Unid", // 使用UserId作为发送者ID
token = token // 添加当前token
}; };
string ChatJsonData = JsonSerializer.Serialize(newChatMessage); string ChatJsonData = JsonSerializer.Serialize(newChatMessage);
byte[] dataBytes = Encoding.UTF8.GetBytes(ChatJsonData); byte[] dataBytes = Encoding.UTF8.GetBytes(ChatJsonData);
@ -372,7 +429,7 @@ namespace chatclient
{ {
log.Info("MainWindow 关闭事件触发,清理资源"); log.Info("MainWindow 关闭事件触发,清理资源");
// 清理资源 // 清理资源
Client?.Shutdown(SocketShutdown.Both); if (Client!.Connected) Client?.Shutdown(SocketShutdown.Both);
Client?.Close(); Client?.Close();
log.Info("关闭Socket连接"); log.Info("关闭Socket连接");
Client?.Dispose(); Client?.Dispose();

View File

@ -10,7 +10,7 @@
<staticLogFileName value="false" /> <staticLogFileName value="false" />
<param name="MaxSizeRollBackups" value="100" /> <param name="MaxSizeRollBackups" value="100" />
<layout type="log4net.Layout.PatternLayout"> <layout type="log4net.Layout.PatternLayout">
<conversionPattern value="[%date %thread %logger %-5level]%ndc - %message%newline" /> <conversionPattern value="[%date %thread %logger %-5level] - %message%newline" />
</layout> </layout>
</appender> </appender>
<root> <root>

7
chatserver/App.config Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net configSource="config\log4net.config" />
</configuration>

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace chatserver.Data
{
public enum MessageType
{
Text,
Image,//图片
File,//文件
System//系统信息
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace chatserver.Data
{
internal class LoginData
{
public string? username { get; set; } = null;
public string? password { get; set; } = null;
public string? token { get; set; } = null;
}
internal class LoginResultData
{
public required string type { get; set; } = "login";
public string? userid { get; set; } = "Unid";
public string? status { get; set; }
public string? message { get; set; }
public string? token { get; set; }
public string? username { get; set; }
}
internal class SignData
{
public string? username { get; set; } = null;
public string? password { get; set; } = null;
}
internal class SignResultData
{
public required string type { get; set; } = "sign";
public string? status { get; set; } = null;
public string? message { get; set; } = null;
}
internal class TypeData
{
public string? type { get; set; }
}
internal class ChatRegisterData
{
public required string type { get; set; } = "chat";
public required string userid { get; set; } = "Unid";
public string user { get; set; } = "Unnamed";
public string? status { get; set; } = null;
public string? message { get; set; } = null;
public string? avatar { get; set; } = null;
public MessageType? msgtype { get; set; } = MessageType.Text;
public DateTime? timestamp { get; set; } = DateTime.Now;
}
internal class ChatData
{
public required string message { get; set; } = "message";
public MessageType? msgtype { get; set; } = MessageType.Text;
public required string userid { get; set; } = "Unid";
public string? token { get; set; } = null; // 添加token字段
}
}

264
chatserver/Program.cs Normal file
View File

@ -0,0 +1,264 @@
using System.Net.Sockets;
using System.Net.Http;
using log4net;
using log4net.Config;
using System.Net;
using System.Data.SQLite;
using chatserver.Data;
using System.Text.Json;
using System.Reflection;
[assembly: XmlConfigurator(ConfigFile = "config/log4net.config", Watch = true)]
namespace chatserver
{
internal class ChatServer
{
private static readonly ILog log = LogManager.GetLogger(typeof(ChatServer));
private static List<Socket> Client = new();
private static List<string> token = new();
private static Socket? Server;
private static SQLiteConnection? User_db;
static void Main(string[] args)
{
//XmlConfigurator.Configure();
log.Info("Hello World!");
Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Server.Bind(new IPEndPoint(IPAddress.Any, 52006));
Server.Listen(20);
OpenUser_db();
log.Info("服务器以在52006端口监听");
while (true)
{
try
{
Socket client = Server.Accept();
Client.Add(client);
log.Info($"用户{client.RemoteEndPoint}连接");
Thread thread = new Thread(() => HandleClient(client));
thread.Start();
}
catch (Exception ex)
{
log.Error("Error accepting client connection: " + ex.Message);
}
}
}
public static void OpenUser_db()
{
log.Info("正在打开数据库连接...");
User_db = new SQLiteConnection("Data Source=ServerUser.db;Version=3;"); //没有数据库则自动创建
User_db.Open();
EnsureUsersTableExists(); // 确保users表存在
log.Info("数据库连接已打开");
}
static void HandleClient(Socket socket)
{
try
{
while (true)
{
byte[] buffer = new byte[1024];
int received = socket.Receive(buffer);
if (received == 0) break; // 客户端断开连接
string message = System.Text.Encoding.UTF8.GetString(buffer, 0, received);
log.Info("Received message: " + message);
var Type = JsonSerializer.Deserialize<TypeData>(message);
if (Type != null)
{
if (Type.type == "register")
{
var sginuser = JsonSerializer.Deserialize<SignData>(message);
if (sginuser != null && sginuser.username != null && sginuser.password != null)
{
log.Info($"用户 {sginuser.username} 正在注册");
if (string.IsNullOrEmpty(sginuser.username) || string.IsNullOrEmpty(sginuser.password))
{
log.Warn("用户名或密码不能为空");
var emptyResult = new SignResultData { type = "register", status = "error_-1", message = "用户名或密码不能为空" };
socket.Send(JsonSerializer.SerializeToUtf8Bytes(emptyResult));
continue; // 如果用户名或密码为空,则跳过注册流程
}
if (sginuser.username.Length < 2 || sginuser.username.Length > 20)
{
log.Warn($"用户注册时 {sginuser.username} 用户名长度不符合要求");
var lengthResult = new SignResultData { type = "register", status = "error_2", message = "用户名长度必须在2到20个字符之间" };
socket.Send(JsonSerializer.SerializeToUtf8Bytes(lengthResult));
continue; // 如果用户名长度不符合要求,则跳过注册流程
}
if (sginuser.password.Length < 4 || sginuser.password.Length > 20)
{
log.Warn($"用户注册时 {sginuser.username} 密码长度不符合要求");
var weakPwdResult = new SignResultData { type = "register", status = "error_1", message = "密码长度必须在4到20个字符之间" };
socket.Send(JsonSerializer.SerializeToUtf8Bytes(weakPwdResult));
continue; // 如果密码过弱,则跳过注册流程
}
if (UserExists(sginuser.username))
{
log.Warn($"用户 {sginuser.username} 已存在");
var Result = new SignResultData { type = "register", status = "error_0", message = "用户名已存在" };
socket.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(Result)));
continue;// 如果用户名已存在,则跳过注册流程
}
var cmd = new SQLiteCommand("INSERT INTO users (userid, username, password) VALUES (@userid, @username, @password)", User_db);
var timedUlid = Ulid.NewUlid(DateTimeOffset.UtcNow);
cmd.Parameters.AddWithValue("@userid", timedUlid);
cmd.Parameters.AddWithValue("@username", sginuser.username);
cmd.Parameters.AddWithValue("@password", sginuser.password);
cmd.ExecuteNonQuery();
log.Info($"用户 {sginuser.username} 注册成功(id:{timedUlid})");
var result = new SignResultData { type = "register", status = "succeed", message = "注册成功" };
socket.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(result)));
}
}
else if (Type.type == "login")
{
var loginData = JsonSerializer.Deserialize<LoginData>(message);
if (loginData != null)
{
if (string.IsNullOrEmpty(loginData.username) || string.IsNullOrEmpty(loginData.password))
{
log.Warn("用户名或密码不能为空");
var emptyResult = new LoginResultData { type = "login", status = "error_-1", message = "用户名或密码不能为空" };
socket.Send(JsonSerializer.SerializeToUtf8Bytes(emptyResult));
continue; // 如果用户名或密码为空,则跳过登录流程
}
if (loginData.username.Length < 2 || loginData.username.Length > 20)
{
log.Warn($"用户登录时 {loginData.username} 用户名长度不符合要求");
var lengthResult = new LoginResultData { type = "login", status = "error_2", message = "用户名长度必须在2到20个字符之间" };
socket.Send(JsonSerializer.SerializeToUtf8Bytes(lengthResult));
continue; // 如果用户名长度不符合要求,则跳过登录流程
}
var Authentication = UserAuthentication(loginData.username, loginData.password);
if (Authentication != "")
{
log.Info($"用户 {loginData.username} 登录成功 (id:{Authentication})");
var result = new LoginResultData
{
type = "login",
status = "succeed",
message = "登录成功",
//token = Authentication,
username = loginData.username,
userid = Authentication
};
socket.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(result)));
}
else
{
log.Warn($"用户 {loginData.username} 登录失败,用户名或密码错误");
var result = new LoginResultData { type = "login", status = "error_0", message = "用户名或密码错误" };
socket.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(result)));
}
}
}
else if (Type.type == "chat")
{
var chatData = JsonSerializer.Deserialize<ChatData>(message);
if (chatData != null && chatData.message != null)
{
log.Info($"接收到聊天消息: {chatData.message}");
var chatRegisterData = new ChatRegisterData
{
type = "chat",
userid = chatData.userid ?? "Unid",
user = GetUsernameByUserId(chatData.userid!),
message = chatData.message,
msgtype = chatData.msgtype ?? MessageType.Text,
// 替换为
timestamp = DateTime.Now
};
foreach (var client in Client)
{
client.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(chatRegisterData)));
}
}
else
{
log.Warn("接收到无效的聊天消息");
}
}
else
{
log.Warn("未知的请求类型: " + Type.type);
}
}
}
}
catch (SocketException ex)
{
log.Error("Socket error: " + ex.Message);
}
catch (JsonException ex)
{
log.Error("JSON parsing error: " + ex.Message);
}
catch (Exception ex)
{
log.Error("Error handling client: " + ex.Message);
}
finally
{
log.Info($"用户{socket.RemoteEndPoint}已断开连接");
socket.Close();
Client.Remove(socket);
}
}
/// <summary>
/// 查询User_db是否有相同用户名
/// </summary>
/// <param name="username"></param>
/// <returns></returns>
static bool UserExists(string username)
{
using var cmd = new SQLiteCommand("SELECT COUNT(*) FROM users WHERE username = @username", User_db);
cmd.Parameters.AddWithValue("@username", username);
var count = Convert.ToInt32(cmd.ExecuteScalar());
return count > 0;
}
/// <summary>
/// 验证用户登录信息并返回userid结果
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
static string UserAuthentication(string username, string password)
{
using var cmd = new SQLiteCommand("SELECT userid FROM users WHERE username = @username AND password = @password", User_db);
cmd.Parameters.AddWithValue("@username", username);
cmd.Parameters.AddWithValue("@password", password);
var result = cmd.ExecuteScalar();
return result != null ? result.ToString()! : string.Empty;
}
// 在ChatServer类中添加一个方法用于初始化users表
private static void EnsureUsersTableExists()
{
using var cmd = new SQLiteCommand(@"
CREATE TABLE IF NOT EXISTS users (
userid TEXT PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL
)", User_db);
cmd.ExecuteNonQuery();
}
/// <summary>
/// 根据userid查询对应的用户名
/// </summary>
/// <param name="userid"></param>
/// <returns>用户名,如果不存在则返回</returns>
static string GetUsernameByUserId(string userid)
{
if (string.IsNullOrEmpty(userid) || userid == "Unid")
{
return "Unnamed"; // 如果userid为空或为"Unid",返回默认用户名
}
using var cmd = new SQLiteCommand("SELECT username FROM users WHERE userid = @userid", User_db);
cmd.Parameters.AddWithValue("@userid", userid);
var result = cmd.ExecuteScalar();
return result != null ? result.ToString()! : "Unnamed";
}
}
}

View File

@ -0,0 +1,8 @@
{
"profiles": {
"chatserver": {
"commandName": "Project",
"sqlDebugging": false
}
}
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="config\log4net.config" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="config\log4net.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="log4net" Version="3.1.0" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
<PackageReference Include="Ulid" Version="1.3.4" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
log4net 配置文件
该配置定义了日志记录的方式、格式和存储位置。
主要使用 RollingFileAppender 进行按日期滚动的日志文件输出。
-->
<configuration>
<log4net>
<!--
RollingLogFileAppender按日期滚动的文件日志记录器
日志文件存储在 log\ 目录下,文件名格式为 yyyyMMdd_chat.log
日志文件采用 UTF-8 编码,最大保留 100 个备份文件
-->
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="[%date %thread %logger %-5level] - %message%newline" />
</layout>
</appender>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<!-- 日志文件名日期格式 -->
<datePattern value="yyyyMMdd'_chat.log'" />
<!-- 日志文件编码 -->
<encoding value="utf-8" />
<!-- 日志文件目录 -->
<file value="log\\" />
<!-- 是否追加到文件 -->
<appendToFile value="true" />
<!-- 按日期滚动日志文件 -->
<rollingStyle value="Date" />
<!-- 是否使用静态文件名 -->
<staticLogFileName value="false" />
<!-- 最大备份文件数 -->
<param name="MaxSizeRollBackups" value="100" />
<!-- 日志输出格式 -->
<layout type="log4net.Layout.PatternLayout">
<!--
日志格式说明:
%date 日志时间
%thread 线程ID
%logger 日志记录器名称
%-5level 日志级别
%ndc 嵌套诊断上下文
%message 日志内容
%newline 换行
-->
<conversionPattern value="[%date %thread %logger %-5level] - %message%newline" />
</layout>
</appender>
<!--
根日志记录器,记录所有级别日志
并将日志输出到 RollingLogFileAppender
-->
<root>
<level value="all" />
<appender-ref ref="ConsoleAppender" />
<appender-ref ref="RollingLogFileAppender" />
</root>
</log4net>
</configuration>