Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
fd25d57be5 | |||
97e8f4e82a | |||
686ef24cde |
159
API.md
Normal file
159
API.md
Normal file
@ -0,0 +1,159 @@
|
||||
## Socket API
|
||||
Socket服务器运行在`52006`端口。客户端连接后,可以发送JSON格式的消息。每个消息必须包含`type`字段,表示操作类型。
|
||||
|
||||
### 消息格式
|
||||
所有消息均为JSON格式,编码为UTF-8字符串发送。
|
||||
|
||||
### 1. 注册
|
||||
- **请求**:
|
||||
```json
|
||||
{
|
||||
"type": "register",
|
||||
"username": string,
|
||||
"password": string,
|
||||
}
|
||||
```
|
||||
|
||||
- **响应**:
|
||||
- 成功:
|
||||
```json
|
||||
{
|
||||
"type": "register",
|
||||
"status": "succeed",
|
||||
"message": "注册成功"
|
||||
}
|
||||
```
|
||||
- 用户名已存在:
|
||||
```json
|
||||
{
|
||||
"type": "register",
|
||||
"status": "error_0",
|
||||
"message": "用户名已存在"
|
||||
}
|
||||
```
|
||||
- 用户名格式错误:
|
||||
```json
|
||||
{
|
||||
"type": "register",
|
||||
"status": "error_2",
|
||||
"message": "用户名长度必须在2到20个字符之间"
|
||||
}
|
||||
```
|
||||
- 密码格式错误:
|
||||
```json
|
||||
{
|
||||
"type": "register",
|
||||
"status": "error_1",
|
||||
"message": "密码长度必须在4到20个字符之间"
|
||||
}
|
||||
```
|
||||
- 其他错误
|
||||
```json
|
||||
{
|
||||
"type" = "register",
|
||||
"status" = "error_-1",
|
||||
"message" = "用户名或密码不能为空"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 登录
|
||||
- **请求**:
|
||||
```json
|
||||
{
|
||||
"type": "login",
|
||||
"username": string,
|
||||
"password": string
|
||||
}
|
||||
```
|
||||
|
||||
- **响应**:
|
||||
- 成功:
|
||||
```json
|
||||
{
|
||||
"type" = "login",
|
||||
"status" = "succeed",
|
||||
"message" = "登录成功",
|
||||
"username" = string,
|
||||
"userid" = string
|
||||
}
|
||||
```
|
||||
- 账号或密码错误:
|
||||
```json
|
||||
{
|
||||
"type" = "login",
|
||||
"status" = "error_0",
|
||||
"message" = "用户名或密码错误"
|
||||
}
|
||||
```
|
||||
- 密码格式错误:
|
||||
```json
|
||||
{
|
||||
"type" = "login",
|
||||
"status" = "error_1",
|
||||
"message" = "密码长度不能超过20个字符"
|
||||
}
|
||||
```
|
||||
- 用户名格式错误:
|
||||
```json
|
||||
{
|
||||
"type" = "login",
|
||||
"status" = "error_2",
|
||||
"message" = "用户名长度必须在2到20个字符之间"
|
||||
}
|
||||
```
|
||||
- 其他错误:
|
||||
```json
|
||||
{
|
||||
"type" = "login",
|
||||
"status" = "error_-1",
|
||||
"message" = "用户名或密码不能为空"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 发送聊天消息
|
||||
- **请求**:
|
||||
```json
|
||||
{
|
||||
"type" = "chat";
|
||||
"message" = string;
|
||||
"msgtype" = MessageType; //MessageType定义在下面的条目上
|
||||
"userid" = string;
|
||||
"token" = null; // 可空,因为目前没搞这个。所以可以不用发送token。
|
||||
}
|
||||
```
|
||||
|
||||
- **响应**:
|
||||
- 成功:
|
||||
```json
|
||||
{
|
||||
"type" = "chat";
|
||||
"userid" = "Unid";
|
||||
"user" = "Unnamed";
|
||||
"status" = string;
|
||||
"message" = string;
|
||||
"avatar" = null;
|
||||
"msgtype" = MessageType; //MessageType定义在下面的条目上
|
||||
"timestamp" = DateTime(例如:2025-06-15T12:41:26.6322042+08:00);
|
||||
}
|
||||
```
|
||||
|
||||
### MessageType格式
|
||||
MessageType在服务器的定义如下,可以先查看关于[C# 枚举(Enum)](https://www.runoob.com/csharp/csharp-enum.html)的文章。
|
||||
```C#
|
||||
public enum MessageType
|
||||
{
|
||||
Text,
|
||||
Image,//图片
|
||||
File,//文件
|
||||
System//系统信息
|
||||
}
|
||||
```
|
||||
所以在`MessageType.Text`的值为`0`。以此类推,`MessageTypeImage`值为`1`.
|
||||
|
||||
表现在json文本中为
|
||||
```json
|
||||
{
|
||||
"msgtype" = 0
|
||||
}
|
||||
```
|
||||
键为`"msgtype"`的值为`MessageType.Text`。
|
@ -1,10 +1,12 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.13.35931.197 d17.13
|
||||
VisualStudioVersion = 17.13.35931.197
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "chatclient", "chatclient\chatclient.csproj", "{6965B200-D0AA-4729-A5A4-30DFD307EB80}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "chatserver", "chatserver\chatserver.csproj", "{5AE823C9-2275-45F1-B312-0D1ED4237A2D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -3,5 +3,5 @@
|
||||
<configSections>
|
||||
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
|
||||
</configSections>
|
||||
<log4net configSource="config/log4net.config" />
|
||||
<log4net configSource="config\log4net.config" />
|
||||
</configuration>
|
@ -48,7 +48,7 @@ namespace chatclient.Data
|
||||
// 添加菜单项
|
||||
contextMenu.Items.Add(CreateMenuItem("打开程序", PackIconKind.WindowRestore, OpenApp_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("Help", PackIconKind.HelpCircle, Help_Click));
|
||||
//contextMenu.Items.Add(new Separator { Style = (Style)Application.Current.Resources["MaterialDesignLightSeparator"] });
|
||||
|
@ -4,9 +4,9 @@ namespace chatclient.Data
|
||||
{
|
||||
internal class Server
|
||||
{
|
||||
public const string ServerUrl = "http://175.24.191.172:5001";
|
||||
public const string ServerIP = "175.24.191.172";
|
||||
public const int ServerPort = 8889;
|
||||
public const string ServerUrl = "http://127.0.0.1:5001";
|
||||
public const string ServerIP = "127.0.0.1";
|
||||
public const int ServerPort = 52006;
|
||||
}
|
||||
internal class LoginData
|
||||
{
|
||||
@ -21,6 +21,7 @@ namespace chatclient.Data
|
||||
public string? message { get; set; }
|
||||
public string? token { get; set; }
|
||||
public string? username { get; set; }
|
||||
public string? userid { get; set; } = "Unid";
|
||||
}
|
||||
internal class SignData
|
||||
{
|
||||
@ -30,7 +31,7 @@ namespace chatclient.Data
|
||||
}
|
||||
internal class SignResultData
|
||||
{
|
||||
public bool success { get; set; } = false;
|
||||
public string? status { get; set; } = null;
|
||||
public string? message { get; set; } = null;
|
||||
}
|
||||
internal class RegisterData
|
||||
@ -40,14 +41,19 @@ namespace chatclient.Data
|
||||
internal class ChatRegisterData
|
||||
{
|
||||
public string? user { get; set; } = "Unnamed";
|
||||
public required string userid { get; set; } = "Unid";
|
||||
public string? status { get; set; } = null;
|
||||
public string? message { get; set; } = null;
|
||||
public string? image { get; set; } = null;
|
||||
public DateTime timestamp { get; set; } = DateTime.Now;
|
||||
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 type { get; set; } = "chat";
|
||||
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字段
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@
|
||||
mc:Ignorable="d"
|
||||
Title="LoginWindow" Height="540" Width="330" MinHeight="540" MinWidth="330" MaxHeight="540" MaxWidth="330"
|
||||
ResizeMode="NoResize" Closing="Window_Closing" Loaded="Window_Loaded">
|
||||
|
||||
<TabControl>
|
||||
<TabItem Header="登录账号" Cursor="Hand" Height="40">
|
||||
<Grid Background="#FFE5E5E5">
|
||||
|
@ -108,14 +108,6 @@ namespace chatclient
|
||||
if (Task.IsCompletedSuccessfully)
|
||||
{
|
||||
log.Info("注册请求发送成功");
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
|
||||
if (loginWindow != null)
|
||||
{
|
||||
loginWindow.SignMsg = "注册请求已发送,请等待服务器响应";
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -187,47 +179,47 @@ namespace chatclient
|
||||
}
|
||||
});
|
||||
}
|
||||
public static async Task HttpSignRegistryUser(string Username, string Userpassword)
|
||||
{
|
||||
try
|
||||
{
|
||||
var SignData = new
|
||||
{
|
||||
type = "register",
|
||||
username = Username,
|
||||
password = Userpassword
|
||||
};
|
||||
string SignJsonData = JsonSerializer.Serialize(SignData);
|
||||
byte[] dataBytes = Encoding.UTF8.GetBytes(SignJsonData);
|
||||
var content = new StringContent(SignJsonData, Encoding.UTF8, "application/json");
|
||||
var response = await MainWindow.HttpClient.PostAsync($"{Server.ServerUrl}/api/register", content);
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
log.Info($"注册请求已发送,响应内容: {responseBody}");
|
||||
var signresponse = JsonSerializer.Deserialize<SignResultData>(responseBody);
|
||||
if (signresponse!.success)
|
||||
{
|
||||
log.Info($"注册成功: {signresponse.message}");
|
||||
await Login(true, Username, Userpassword);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Error($"注册失败: {signresponse.message}");
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
|
||||
loginWindow!.SignMsg = signresponse.message;
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("注册请求发送失败", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
log.Info("注册请求已完成");
|
||||
}
|
||||
}
|
||||
//public static async Task HttpSignRegistryUser(string Username, string Userpassword)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// var SignData = new
|
||||
// {
|
||||
// type = "register",
|
||||
// username = Username,
|
||||
// password = Userpassword
|
||||
// };
|
||||
// string SignJsonData = JsonSerializer.Serialize(SignData);
|
||||
// byte[] dataBytes = Encoding.UTF8.GetBytes(SignJsonData);
|
||||
// var content = new StringContent(SignJsonData, Encoding.UTF8, "application/json");
|
||||
// var response = await MainWindow.HttpClient.PostAsync($"{Server.ServerUrl}/api/register", content);
|
||||
// var responseBody = await response.Content.ReadAsStringAsync();
|
||||
// log.Info($"注册请求已发送,响应内容: {responseBody}");
|
||||
// var signresponse = JsonSerializer.Deserialize<SignResultData>(responseBody);
|
||||
// if (signresponse!.status)
|
||||
// {
|
||||
// log.Info($"注册成功: {signresponse.message}");
|
||||
// await Login(true, Username, Userpassword);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// log.Error($"注册失败: {signresponse.message}");
|
||||
// Application.Current.Dispatcher.Invoke(() =>
|
||||
// {
|
||||
// var loginWindow = Application.Current.Windows.OfType<LoginWindow>().FirstOrDefault();
|
||||
// loginWindow!.SignMsg = signresponse.message;
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// log.Error("注册请求发送失败", ex);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// log.Info("注册请求已完成");
|
||||
// }
|
||||
// }
|
||||
public static async Task Login(bool Sign, string Username, string Userpassword)
|
||||
{
|
||||
// 公共的登录数据准备
|
||||
@ -341,7 +333,7 @@ namespace chatclient
|
||||
log.Error("保存登录信息到临时文件失败", ex);
|
||||
}
|
||||
}
|
||||
if (MainWindow.token == null) Application.Current.Shutdown();
|
||||
if (MainWindow.UserId == null) Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,10 +138,17 @@
|
||||
<StackPanel Width="auto" Height="auto">
|
||||
<materialDesign:PackIcon Width="24" Height="24" HorizontalAlignment="Center" Kind="Cog" />
|
||||
<TextBlock HorizontalAlignment="Center" Text="设置" />
|
||||
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<StackPanel>
|
||||
<TextBlock Text="这里没有东西..."/>
|
||||
<StackPanel Margin="10">
|
||||
<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 & 绪山七寻" Margin="0,0,0,8"/>
|
||||
<TextBlock Text="本项目基于 .NET 8 和 MaterialDesignInXAML。" TextWrapping="Wrap" Margin="0,0,0,8"/>
|
||||
</StackPanel>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
@ -35,6 +35,7 @@ namespace chatclient
|
||||
static string? receive;
|
||||
public static string UserName { get; set; } = "?";
|
||||
public static string? token = null;
|
||||
public static string? UserId = null;
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(MainWindow));
|
||||
public static Socket? Client;
|
||||
public static readonly HttpClient HttpClient = new HttpClient();
|
||||
@ -133,12 +134,13 @@ namespace chatclient
|
||||
var Type = JsonSerializer.Deserialize<RegisterData>(msg);
|
||||
if (Type != null)
|
||||
{
|
||||
if (Type.type == "login_1")
|
||||
if (Type.type == "login")
|
||||
{
|
||||
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";
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
@ -155,7 +157,7 @@ namespace chatclient
|
||||
Sender = "System",
|
||||
MsgType = MessageType.System,
|
||||
Image = new BitmapImage(new Uri("pack://application:,,,/resource/user.png")),
|
||||
Content = $"你好 {UserName} !",
|
||||
Content = $"你好 {UserName} (id: {UserId} )!",
|
||||
Timestamp = DateTime.Now,
|
||||
Alignment = HorizontalAlignment.Center,
|
||||
SenderColor = new SolidColorBrush(Colors.Gray)
|
||||
@ -163,7 +165,31 @@ namespace chatclient
|
||||
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
|
||||
{
|
||||
@ -177,22 +203,11 @@ namespace chatclient
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (Type.type == "login_0")
|
||||
{
|
||||
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")
|
||||
else if (Type.type == "register")
|
||||
{
|
||||
var SignResponse = JsonSerializer.Deserialize<SignResultData>(msg);
|
||||
if (SignResponse!.status == "success")
|
||||
{
|
||||
log.Warn($"注册成功\nMsg:{msg}");
|
||||
Application.Current.Dispatcher.Invoke(async () =>
|
||||
{
|
||||
@ -200,10 +215,34 @@ namespace chatclient
|
||||
await LoginWindow.Login(true, LoginWindow.SignName, LoginWindow.SignPassword1);
|
||||
});
|
||||
}
|
||||
else if (Type.type == "register_0")
|
||||
else if (SignResponse!.status == "error_1")
|
||||
{
|
||||
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 = "密码长度必须在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();
|
||||
@ -213,6 +252,20 @@ namespace chatclient
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
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")
|
||||
{
|
||||
var chat = JsonSerializer.Deserialize<ChatRegisterData>(msg);
|
||||
@ -221,15 +274,15 @@ namespace chatclient
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// 处理聊天消息
|
||||
if (chat.user == UserName)
|
||||
if (chat.userid == UserId)
|
||||
{
|
||||
var chatmessage = new ChatMessage
|
||||
{
|
||||
Sender = chat.user ?? "未知用户",
|
||||
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 ?? "(无内容)",
|
||||
Timestamp = DateTime.Now,
|
||||
Timestamp = chat.timestamp ?? DateTime.Now,
|
||||
Alignment = HorizontalAlignment.Right,
|
||||
SenderColor = new SolidColorBrush(Colors.Blue)
|
||||
};
|
||||
@ -242,9 +295,9 @@ namespace chatclient
|
||||
{
|
||||
Sender = chat.user ?? "未知用户",
|
||||
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 ?? "(无内容)",
|
||||
Timestamp = chat.timestamp,
|
||||
Timestamp = chat.timestamp ?? DateTime.Now,
|
||||
Alignment = HorizontalAlignment.Left,
|
||||
SenderColor = new SolidColorBrush(Colors.Black)
|
||||
};
|
||||
@ -258,6 +311,7 @@ namespace chatclient
|
||||
log.Error("反序列化聊天数据时返回了 null");
|
||||
}
|
||||
}
|
||||
else if (Type.type == "ping") { }
|
||||
else
|
||||
{
|
||||
log.Error($"未知的消息类型: {Type.type},请检查服务器响应格式");
|
||||
@ -307,7 +361,10 @@ namespace chatclient
|
||||
var newChatMessage = new ChatData
|
||||
{
|
||||
type = "chat",
|
||||
message = txtMessage.Text
|
||||
message = txtMessage.Text,
|
||||
msgtype = MessageType.Text,
|
||||
userid = UserId ?? "Unid", // 使用UserId作为发送者ID
|
||||
token = token // 添加当前token
|
||||
};
|
||||
string ChatJsonData = JsonSerializer.Serialize(newChatMessage);
|
||||
byte[] dataBytes = Encoding.UTF8.GetBytes(ChatJsonData);
|
||||
@ -372,7 +429,7 @@ namespace chatclient
|
||||
{
|
||||
log.Info("MainWindow 关闭事件触发,清理资源");
|
||||
// 清理资源
|
||||
Client?.Shutdown(SocketShutdown.Both);
|
||||
if (Client!.Connected) Client?.Shutdown(SocketShutdown.Both);
|
||||
Client?.Close();
|
||||
log.Info("关闭Socket连接");
|
||||
Client?.Dispose();
|
||||
|
@ -10,7 +10,7 @@
|
||||
<staticLogFileName value="false" />
|
||||
<param name="MaxSizeRollBackups" value="100" />
|
||||
<layout type="log4net.Layout.PatternLayout">
|
||||
<conversionPattern value="[%date %thread %logger %-5level]%ndc - %message%newline" />
|
||||
<conversionPattern value="[%date %thread %logger %-5level] - %message%newline" />
|
||||
</layout>
|
||||
</appender>
|
||||
<root>
|
||||
|
7
chatserver/App.config
Normal file
7
chatserver/App.config
Normal 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>
|
16
chatserver/Data/ChatData.cs
Normal file
16
chatserver/Data/ChatData.cs
Normal 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//系统信息
|
||||
}
|
||||
}
|
57
chatserver/Data/chatapi.cs
Normal file
57
chatserver/Data/chatapi.cs
Normal 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
264
chatserver/Program.cs
Normal 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";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
8
chatserver/Properties/launchSettings.json
Normal file
8
chatserver/Properties/launchSettings.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"chatserver": {
|
||||
"commandName": "Project",
|
||||
"sqlDebugging": false
|
||||
}
|
||||
}
|
||||
}
|
26
chatserver/chatserver.csproj
Normal file
26
chatserver/chatserver.csproj
Normal 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>
|
59
chatserver/config/log4net.config
Normal file
59
chatserver/config/log4net.config
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user