From 936a485195148cf2d38b1e72051caccc9318a154 Mon Sep 17 00:00:00 2001
From: XuShanQiXun <3401460572@qq.com>
Date: Sun, 22 Jun 2025 00:14:43 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=97=B6=E9=97=B4=E6=A0=BC?=
=?UTF-8?q?=E5=BC=8F=E5=8C=96=E5=92=8C=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95?=
=?UTF-8?q?=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
在 `ChatDataModel.cs` 中添加 `TimeFormatConverter` 类,用于格式化本地时间,并在 `MainWindow.xaml` 中应用该转换器。
在 `chatapi.cs` 中新增 `HistoryRequest` 和 `HistoryResponse` 类以处理历史记录请求和响应。
修改 `LoginWindow.xaml.cs` 中的数据发送方式,使用 `SendWithPrefix` 方法以支持数据压缩和长度前缀。
在 `MainWindow.xaml.cs` 中添加 `LoadHistoryMessages` 方法以加载历史消息,并在接收到响应时更新消息列表。
在 `Program.cs` 中实现数据压缩和解压缩方法,提升网络传输效率。
新增消息表和索引以支持消息存储和查询。
更新日志记录以提供更详细的操作信息和错误处理。
---
chatclient/Data/ChatDataModel.cs | 73 ++++++++++
chatclient/Data/chatapi.cs | 25 +++-
chatclient/LoginWindow.xaml.cs | 8 +-
chatclient/MainWindow.xaml | 6 +-
chatclient/MainWindow.xaml.cs | 201 +++++++++++++++++++++++----
chatserver/Data/chatapi.cs | 42 ++++++
chatserver/Program.cs | 225 ++++++++++++++++++++++++++++---
7 files changed, 523 insertions(+), 57 deletions(-)
diff --git a/chatclient/Data/ChatDataModel.cs b/chatclient/Data/ChatDataModel.cs
index 3e773cf..af99423 100644
--- a/chatclient/Data/ChatDataModel.cs
+++ b/chatclient/Data/ChatDataModel.cs
@@ -8,6 +8,8 @@ using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows.Input;
+using System.Globalization;
+using System.Windows.Data;
namespace chatclient.Data
{
@@ -60,4 +62,75 @@ namespace chatclient.Data
public string? UserName { get; set; }
public string? UserPassword { get; set; }
}
+ ///
+ /// 时间格式转换器类
+ ///
+ public class TimeFormatConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is DateTime timestamp)
+ {
+ // 获取本地时区信息
+ TimeZoneInfo localTimeZone = TimeZoneInfo.Local;
+ bool isLocalTimeZoneUtcPlus8 = localTimeZone.BaseUtcOffset == TimeSpan.FromHours(8);
+ DateTime localTime;
+ if (timestamp.Kind == DateTimeKind.Utc)
+ {
+ localTime = timestamp.ToLocalTime();
+ }
+ else if (timestamp.Kind == DateTimeKind.Unspecified)
+ {
+ // 假设未指定时间是UTC时间(常见实践)
+ localTime = DateTime.SpecifyKind(timestamp, DateTimeKind.Utc).ToLocalTime();
+ }
+ else
+ {
+ localTime = timestamp;
+ }
+
+ var now = DateTime.Now;
+ var today = now.Date;
+ var yesterday = today.AddDays(-1);
+ var localTimeDate = localTime.Date;
+
+ // 格式化时间字符串
+ string formattedTime;
+ if (localTimeDate == today)
+ {
+ formattedTime = localTime.ToString("HH:mm:ss");
+ }
+ else if (localTimeDate == yesterday)
+ {
+ formattedTime = "昨天 " + localTime.ToString("HH:mm");
+ }
+ else if (localTime.Year == now.Year)
+ {
+ formattedTime = localTime.ToString("MM/dd HH:mm");
+ }
+ else
+ {
+ formattedTime = localTime.ToString("yy/MM/dd HH:mm");
+ }
+
+ // 仅在非UTC+8时区显示时区信息
+ if (!isLocalTimeZoneUtcPlus8)
+ {
+ // 获取原始时间的时区信息
+ string timeZoneInfo = timestamp.Kind == DateTimeKind.Utc
+ ? "UTC"
+ : "UTC"+localTime.ToString("zzz");
+
+ return $"{timeZoneInfo} {formattedTime}";
+ }
+ return formattedTime;
+ }
+ return value;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
}
diff --git a/chatclient/Data/chatapi.cs b/chatclient/Data/chatapi.cs
index 6dba7a3..724429a 100644
--- a/chatclient/Data/chatapi.cs
+++ b/chatclient/Data/chatapi.cs
@@ -5,8 +5,8 @@ namespace chatclient.Data
internal class Server
{
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 string ServerIP = "175.24.191.172";
+ //public const string ServerIP = "127.0.0.1";
public const int ServerPort = 52006;
}
internal class LoginData
@@ -57,4 +57,25 @@ namespace chatclient.Data
public required string userid { get; set; } = "Unid";
public string? token { get; set; } = null; // 添加token字段
}
+ ///
+ /// 历史记录请求类
+ ///
+ internal class HistoryRequest
+ {
+ public string type { get; set; } = "history";
+ public int offset { get; set; } = 0; // 分页偏移量
+ public int count { get; set; } = 10; // 请求数量
+ public string? chat_type { get; set; } = "group"; // group/private
+ public string? room_id { get; set; } = "global"; // 群聊房间ID
+ public string? receiver_id { get; set; } = null; // 私聊接收者ID
+ }
+
+ ///
+ /// 历史记录响应类
+ ///
+ internal class HistoryResponse
+ {
+ public List history { get; set; } = new List();
+ public int total_count { get; set; } // 总消息数
+ }
}
diff --git a/chatclient/LoginWindow.xaml.cs b/chatclient/LoginWindow.xaml.cs
index 5b1e999..32f955e 100644
--- a/chatclient/LoginWindow.xaml.cs
+++ b/chatclient/LoginWindow.xaml.cs
@@ -150,7 +150,7 @@ namespace chatclient
// 检查Socket是否可用
if (MainWindow.Client?.Connected == true)
{
- MainWindow.Client.Send(dataBytes);
+ MainWindow.SendWithPrefix(dataBytes);
return;
}
log.Info("未连接服务器,尝试异步连接");
@@ -162,7 +162,7 @@ namespace chatclient
MainWindow.Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
MainWindow.Client?.Connect(IPAddress.Parse(Server.ServerIP), Server.ServerPort);
MainWindow.StartReceive();
- MainWindow.Client?.Send(dataBytes);
+ MainWindow.SendWithPrefix(dataBytes);
}
catch (Exception ex)
{
@@ -235,7 +235,7 @@ namespace chatclient
// 检查Socket是否可用
if (MainWindow.Client?.Connected == true)
{
- MainWindow.Client.Send(dataBytes);
+ MainWindow.SendWithPrefix(dataBytes);
return;
}
log.Info("未连接服务器,尝试异步连接");
@@ -247,7 +247,7 @@ namespace chatclient
MainWindow.Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
MainWindow.Client?.Connect(IPAddress.Parse(Server.ServerIP), Server.ServerPort);
MainWindow.StartReceive();
- MainWindow.Client?.Send(dataBytes);
+ MainWindow.SendWithPrefix(dataBytes);
}
catch (Exception ex)
{
diff --git a/chatclient/MainWindow.xaml b/chatclient/MainWindow.xaml
index 2b5eca4..144a7c0 100644
--- a/chatclient/MainWindow.xaml
+++ b/chatclient/MainWindow.xaml
@@ -5,10 +5,14 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:local="clr-namespace:chatclient"
+ xmlns:data="clr-namespace:chatclient.Data"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" x:Class="chatclient.MainWindow"
mc:Ignorable="d"
Title="ChatWindow" Height="450" Width="800" MinHeight="240" MinWidth="380"
Style="{StaticResource MaterialDesignWindow}" Closed="MainWindow_Closed" Loaded="MainWindow_Loaded">
+
+
+
@@ -75,7 +79,7 @@
-
+
diff --git a/chatclient/MainWindow.xaml.cs b/chatclient/MainWindow.xaml.cs
index 11b2f64..792ffea 100644
--- a/chatclient/MainWindow.xaml.cs
+++ b/chatclient/MainWindow.xaml.cs
@@ -22,6 +22,7 @@ using System.Collections.ObjectModel;
using System.Windows.Threading;
using System.Collections.Specialized;
using Hardcodet.Wpf.TaskbarNotification;
+using System.IO.Compression;
[assembly: XmlConfigurator(ConfigFile = "config/log4net.config", Watch = true)]
namespace chatclient
@@ -32,10 +33,11 @@ namespace chatclient
public partial class MainWindow : Window, INotifyPropertyChanged
{
LoginWindow Login = new();
- static string? receive;
+ //static string? receive;
public static string UserName { get; set; } = "?";
public static string? token = null;
public static string? UserId = null;
+ //private bool isLoadingHistory = false;
private static readonly ILog log = LogManager.GetLogger(typeof(MainWindow));
public static Socket? Client;
public static readonly HttpClient HttpClient = new HttpClient();
@@ -87,8 +89,6 @@ namespace chatclient
MessageScroller.ScrollToEnd();
}), DispatcherPriority.ContextIdle);
};
- //Loaded += MainWindow_Loaded;
- //Closed += MainWindow_Closed;
}
public static void StartReceive()
{
@@ -101,29 +101,64 @@ namespace chatclient
}
static void Receive()
{
- byte[] buffer = new byte[1024];
+ const int prefixSize = sizeof(int);
+ byte[] prefixBuffer = new byte[prefixSize];
+ MemoryStream receivedStream = new MemoryStream();
try
{
while (true)
{
- int num = Client!.Receive(buffer);
- if (num == 0) break;
- if (Client.Poll(100, SelectMode.SelectRead) && Client.Available == 0 || !Client.Connected)
+ //接收前缀长度
+ int prefixBytesRead = 0;
+ while (prefixBytesRead < prefixSize)
{
- log.Error("连接已断开");
- break;
+ int bytesRead = Client!.Receive(prefixBuffer, prefixBytesRead, prefixSize - prefixBytesRead, SocketFlags.None);
+ if (bytesRead == 0)
+ {
+ log.Info("连接已关闭");
+ return;
+ }
+ prefixBytesRead += bytesRead;
}
- receive = Encoding.UTF8.GetString(buffer, 0, num);
- response(receive);
+ //解析消息长度
+ int messageLength = BitConverter.ToInt32(prefixBuffer, 0);
+ if (messageLength <= 0 || messageLength > 10 * 1024 * 1024)
+ {
+ log.Error($"无效消息长度: {messageLength}");
+ continue;
+ }
+ //读取完整消息
+ byte[] messageBuffer = new byte[messageLength];
+ int totalBytesRead = 0;
+ while (totalBytesRead < messageLength)
+ {
+ int bytesRead = Client!.Receive(messageBuffer, totalBytesRead, messageLength - totalBytesRead, SocketFlags.None);
+ if (bytesRead == 0)
+ {
+ log.Info("连接已关闭");
+ return;
+ }
+ totalBytesRead += bytesRead;
+ }
+ //解压缩
+ byte[] decompressedData = Decompress(messageBuffer);
+ //处理消息
+ string message = Encoding.UTF8.GetString(decompressedData);
+ response(message);
}
}
+ catch (SocketException ex)
+ {
+ log.Error($"Socket错误: {ex.SocketErrorCode}");
+ }
catch (Exception ex)
{
- log.Error(ex);
+ log.Error($"接收错误: {ex.Message}");
}
finally
{
Client?.Close();
+ receivedStream.Dispose();
}
}
static void response(string msg)
@@ -165,6 +200,7 @@ namespace chatclient
mainWindow?.Messages.Add(chatmessage);
}
});
+ LoadHistoryMessages();
log.Info($"用户 {UserName} 登录成功(token:{token},userid:{UserId})");
}
else if (LoginResponse!.status == "error_0")
@@ -325,6 +361,45 @@ namespace chatclient
log.Error("反序列化聊天数据时返回了 null");
}
}
+ else if (Type.type == "history")
+ {
+ var historyResponse = JsonSerializer.Deserialize(msg);
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ var mainWindow = Application.Current.Windows.OfType().FirstOrDefault();
+ if (mainWindow == null) return;
+ if (historyResponse?.history != null)
+ {
+ foreach (var msgItem in historyResponse.history)
+ {
+ try
+ {
+ mainWindow.Dispatcher.Invoke(() =>
+ {
+ var chatmessage = new ChatMessage
+ {
+ Sender = msgItem.user ?? "未知用户",
+ MsgType = MessageType.Text,
+ Image = new BitmapImage(new Uri(
+ "pack://application:,,,/resource/user.png",
+ UriKind.Absolute)),
+ Content = msgItem.message ?? "(无内容)",
+ Timestamp = msgItem.timestamp ?? DateTime.Now,
+ Alignment = msgItem.userid == UserId ?
+ HorizontalAlignment.Right :
+ HorizontalAlignment.Left
+ };
+ mainWindow.Messages.Insert(0, chatmessage);
+ });
+ }
+ catch (Exception ex)
+ {
+ log.Error($"添加历史消息失败: {ex.Message}");
+ }
+ }
+ }
+ });
+ }
else if (Type.type == "ping") { }
else
{
@@ -346,6 +421,58 @@ namespace chatclient
log.Error("处理响应时发生错误", ex);
}
}
+ ///
+ /// 压缩数据
+ ///
+ ///
+ ///
+ private static byte[] Compress(byte[] data)
+ {
+ if (data.Length < 256)
+ return data;
+
+ using (var compressedStream = new MemoryStream())
+ {
+ using (var zipStream = new GZipStream(compressedStream, CompressionMode.Compress))
+ {
+ zipStream.Write(data, 0, data.Length);
+ }
+ return compressedStream.ToArray();
+ }
+ }
+ ///
+ /// 解压缩数据
+ ///
+ ///
+ ///
+ private static byte[] Decompress(byte[] compressedData)
+ {
+ if (compressedData.Length < 2 || compressedData[0] != 0x1F || compressedData[1] != 0x8B)
+ return compressedData; // 未压缩数据
+
+ using (var compressedStream = new MemoryStream(compressedData))
+ using (var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
+ using (var resultStream = new MemoryStream())
+ {
+ zipStream.CopyTo(resultStream);
+ return resultStream.ToArray();
+ }
+ }
+ ///
+ /// 发送消息
+ ///
+ ///
+ public static void SendWithPrefix(byte[] data)
+ {
+ byte[] compressedData = Compress(data);
+ byte[] lengthPrefix = BitConverter.GetBytes(compressedData.Length);
+ byte[] fullMessage = new byte[lengthPrefix.Length + compressedData.Length];
+
+ Buffer.BlockCopy(lengthPrefix, 0, fullMessage, 0, lengthPrefix.Length);
+ Buffer.BlockCopy(compressedData, 0, fullMessage, lengthPrefix.Length, compressedData.Length);
+ log.Info($"发送数据(长度:{data.Length},压缩后长度:{lengthPrefix.Length},总体长度:{fullMessage.Length})");
+ Client?.Send(fullMessage);
+ }
private async void SendMessage_Click(object sender, RoutedEventArgs e)
{
await SendMessage();
@@ -354,24 +481,10 @@ namespace chatclient
{
if (string.IsNullOrWhiteSpace(txtMessage.Text))
return;
-
// 获取当前选中的联系人
//var contact = cmbContacts.SelectedItem as Contact;
// 判断是否为群组,若是则收件人设为“所有人”,否则为联系人显示名
//string recipient = contact?.IsGroup == true ? "所有人" : contact?.DisplayName;
-
- // 弃用的方法
- // 创建新消息
- //var newMessage = new ChatMessage
- //{
- // Sender = "我",
- // Type = MessageType.Text,
- // Image = new BitmapImage(new Uri("pack://application:,,,/resource/user.png", UriKind.Absolute)), // 默认头像
- // Content = txtMessage.Text,
- // Timestamp = DateTime.Now,
- // Alignment = HorizontalAlignment.Right, // 自己发送的消息靠右
- // SenderColor = new SolidColorBrush(Colors.Blue)
- //};
var newChatMessage = new ChatData
{
type = "chat",
@@ -386,7 +499,7 @@ namespace chatclient
// 检查Socket是否可用
if (Client?.Connected == true)
{
- Client.Send(dataBytes);
+ SendWithPrefix(dataBytes);
Application.Current.Dispatcher.Invoke(() =>
{
txtMessage.Clear();
@@ -402,7 +515,7 @@ namespace chatclient
Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Client?.Connect(IPAddress.Parse(Server.ServerIP), Server.ServerPort);
StartReceive();
- Client?.Send(dataBytes);
+ SendWithPrefix(dataBytes);
Application.Current.Dispatcher.Invoke(() =>
{
txtMessage.Clear();
@@ -425,6 +538,38 @@ namespace chatclient
// 添加到消息列表
//Messages.Add(newMessage);
}
+ private static void LoadHistoryMessages(int offset = 0)
+ {
+ var historyRequest = new HistoryRequest
+ {
+ offset = offset,
+ count = 10,
+ chat_type = "group",
+ room_id = "global"
+ };
+ string requestData = JsonSerializer.Serialize(historyRequest);
+ byte[] data = Encoding.UTF8.GetBytes(requestData);
+ if (Client?.Connected == true)
+ {
+ try
+ {
+ SendWithPrefix(data);
+ }
+ catch (SocketException ex)
+ {
+ log.Error($"发送历史请求失败: {ex.SocketErrorCode}");
+ }
+ catch (Exception ex)
+ {
+ log.Error($"发送历史请求异常: {ex.Message}");
+ }
+ }
+ else
+ {
+ // 处理断开连接的情况
+ log.Warn("发送历史请求时客户端未连接");
+ }
+ }
private void QueueMessage(string message)
{
if (SnackbarThree.MessageQueue is { } messageQueue)
diff --git a/chatserver/Data/chatapi.cs b/chatserver/Data/chatapi.cs
index 5c9e194..7c6b583 100644
--- a/chatserver/Data/chatapi.cs
+++ b/chatserver/Data/chatapi.cs
@@ -6,12 +6,18 @@ 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";
@@ -21,21 +27,33 @@ namespace chatserver.Data
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";
@@ -47,6 +65,9 @@ namespace chatserver.Data
public MessageType? msgtype { get; set; } = MessageType.Text;
public DateTime? timestamp { get; set; } = DateTime.Now;
}
+ ///
+ /// 聊天数据请求类
+ ///
internal class ChatData
{
public required string message { get; set; } = "message";
@@ -54,4 +75,25 @@ namespace chatserver.Data
public required string userid { get; set; } = "Unid";
public string? token { get; set; } = null; // 添加token字段
}
+ ///
+ /// 历史记录请求类
+ ///
+ internal class HistoryRequest
+ {
+ public int offset { get; set; } = 0; // 分页偏移量
+ public int count { get; set; } = 10; // 请求数量
+ public string? chat_type { get; set; } = "group"; // group/private
+ public string? room_id { get; set; } = "global"; // 群聊房间ID
+ public string? receiver_id { get; set; } = null; // 私聊接收者ID
+ }
+
+ ///
+ /// 历史记录响应类
+ ///
+ internal class HistoryResponse
+ {
+ public string type { get; set; } = "history";
+ public List history { get; set; } = new List();
+ public int total_count { get; set; } // 总消息数
+ }
}
diff --git a/chatserver/Program.cs b/chatserver/Program.cs
index 1da1fd7..6ed120c 100644
--- a/chatserver/Program.cs
+++ b/chatserver/Program.cs
@@ -8,6 +8,7 @@ using chatserver.Data;
using System.Text.Json;
using System.Reflection;
using static log4net.Appender.FileAppender;
+using System.IO.Compression;
[assembly: XmlConfigurator(ConfigFile = "config/log4net.config", Watch = true)]
namespace chatserver
@@ -51,20 +52,47 @@ namespace chatserver
log.Info("正在打开数据库连接...");
User_db = new SQLiteConnection("Data Source=ServerUser.db;Version=3;"); //没有数据库则自动创建
User_db.Open();
- EnsureUsersTableExists(); // 确保users表存在
+ InitializeTable();
log.Info("数据库连接已打开");
}
static void HandleClient(Socket socket)
{
+ const int prefixSize = sizeof(int);
+ byte[] prefixBuffer = new byte[prefixSize];
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);
+ //读取长度前缀
+ int prefixBytesRead = 0;
+ while (prefixBytesRead < prefixSize)
+ {
+ int bytesRead = socket.Receive(prefixBuffer, prefixBytesRead, prefixSize - prefixBytesRead, SocketFlags.None);
+ if (bytesRead == 0)
+ return;
+ prefixBytesRead += bytesRead;
+ }
+ //解析消息长度
+ int messageLength = BitConverter.ToInt32(prefixBuffer, 0);
+ if (messageLength <= 0 || messageLength > 10 * 1024 * 1024)
+ {
+ log.Error($"无效消息长度: {messageLength}");
+ continue;
+ }
+ //读取完整消息
+ byte[] messageBuffer = new byte[messageLength];
+ int totalBytesRead = 0;
+ while (totalBytesRead < messageLength)
+ {
+ int bytesRead = socket.Receive(messageBuffer, totalBytesRead, messageLength - totalBytesRead, SocketFlags.None);
+ if (bytesRead == 0)
+ return;
+ totalBytesRead += bytesRead;
+ }
+ //解压缩
+ byte[] decompressedData = Decompress(messageBuffer);
+ string message = System.Text.Encoding.UTF8.GetString(decompressedData);
+ //处理信息
log.Info("Received message: " + message);
var Type = JsonSerializer.Deserialize(message);
if (Type != null)
@@ -79,28 +107,28 @@ namespace chatserver
{
log.Warn("用户名或密码不能为空");
var emptyResult = new SignResultData { type = "register", status = "error_-1", message = "用户名或密码不能为空" };
- socket.Send(JsonSerializer.SerializeToUtf8Bytes(emptyResult));
+ SendWithPrefix(socket,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));
+ SendWithPrefix(socket,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));
+ SendWithPrefix(socket,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)));
+ SendWithPrefix(socket,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);
@@ -111,7 +139,7 @@ namespace chatserver
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)));
+ SendWithPrefix(socket,System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(result)));
}
}
else if (Type.type == "login")
@@ -123,21 +151,21 @@ namespace chatserver
{
log.Warn("用户名或密码不能为空");
var emptyResult = new LoginResultData { type = "login", status = "error_-1", message = "用户名或密码不能为空" };
- socket.Send(JsonSerializer.SerializeToUtf8Bytes(emptyResult));
+ SendWithPrefix(socket,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));
+ SendWithPrefix(socket,JsonSerializer.SerializeToUtf8Bytes(lengthResult));
continue;
}
if (loginData.password.Length > 20)
{
log.Warn($"用户登录时 {loginData.username} 密码长度不符合要求");
var weakPwdResult = new LoginResultData { type = "login", status = "error_1", message = "密码长度不能超过20个字符" };
- socket.Send(JsonSerializer.SerializeToUtf8Bytes(weakPwdResult));
+ SendWithPrefix(socket,JsonSerializer.SerializeToUtf8Bytes(weakPwdResult));
continue;
}
var Authentication = UserAuthentication(loginData.username, loginData.password);
@@ -164,13 +192,13 @@ namespace chatserver
username = loginData.username,
userid = Authentication
};
- socket.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(result)));
+ SendWithPrefix(socket,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)));
+ SendWithPrefix(socket,System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(result)));
}
}
}
@@ -179,7 +207,7 @@ namespace chatserver
var chatData = JsonSerializer.Deserialize(message);
if (chatData != null && chatData.message != null)
{
- log.Info($"接收到聊天消息: {chatData.message}");
+ log.Info($"接收到聊天消息(长度: {chatData.message.Length} )");
if (Client.Count == 0)
{
log.Warn("没有客户端连接,取消发送消息。");
@@ -201,13 +229,13 @@ namespace chatserver
msgtype = MessageType.Text,
timestamp = DateTime.Now
};
- socket.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(errorData)));
+ SendWithPrefix(socket,System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(errorData)));
continue;
}
List clientsCopy;
List usersCopy;
- // 1. 获取集合快照
+ // 获取集合快照
lock (Client_lock)
{
clientsCopy = new List(Client); // 创建客户端列表副本
@@ -223,6 +251,31 @@ namespace chatserver
status = "succeed",
timestamp = DateTime.Now
};
+ Task.Run(() =>
+ {
+ try
+ {
+ using (var dbConnection = new SQLiteConnection("Data Source=ServerUser.db;Version=3;"))
+ {
+ dbConnection.Open();
+ using (var cmd = new SQLiteCommand(@"
+ INSERT INTO messages (type, room_id, sender_id, receiver_id, content)
+ VALUES (@type, @room_id, @sender_id, @receiver_id, @content)", dbConnection))
+ {
+ cmd.Parameters.AddWithValue("@type", "group");
+ cmd.Parameters.AddWithValue("@room_id", "global");
+ cmd.Parameters.AddWithValue("@sender_id", chatData.userid);
+ cmd.Parameters.AddWithValue("@receiver_id", DBNull.Value);
+ cmd.Parameters.AddWithValue("@content", chatData.message);
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ log.Error($"存储消息到数据库失败: {ex.Message}");
+ }
+ });
foreach (var user in usersCopy)
{
// 查找匹配的客户端
@@ -234,7 +287,7 @@ namespace chatserver
try
{
// ==== 修改:添加发送异常处理 ====
- targetClient.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(chatRegisterData)));
+ SendWithPrefix(targetClient,System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(chatRegisterData)));
}
catch (SocketException ex)
{
@@ -252,6 +305,59 @@ namespace chatserver
log.Warn("接收到无效的聊天消息");
}
}
+ else if (Type.type == "history")
+ {
+ var historyReq = JsonSerializer.Deserialize(message);
+ if (historyReq != null)
+ {
+ var response = new HistoryResponse();
+ string query = "SELECT * FROM messages WHERE ";
+ var parameters = new List();
+
+ // 构建查询条件
+ if (historyReq.chat_type == "group")
+ {
+ query += "type = 'group' AND room_id = @room_id ";
+ parameters.Add(new SQLiteParameter("@room_id", historyReq.room_id ?? "global"));
+ }
+ // else if (historyReq.chat_type == "private")
+ // {
+ // query += @"(type = 'private' AND
+ //((sender_id = @userid1 AND receiver_id = @userid2) OR
+ // (sender_id = @userid2 AND receiver_id = @userid1))) ";
+ // parameters.Add(new SQLiteParameter("@userid1", historyReq.sender_id));
+ // parameters.Add(new SQLiteParameter("@userid2", historyReq.receiver_id));
+ // }
+
+ // 获取总消息数
+ string countQuery = query.Replace("SELECT *", "SELECT COUNT(*)");
+ using var countCmd = new SQLiteCommand(countQuery, User_db);
+ countCmd.Parameters.AddRange(parameters.ToArray());
+ response.total_count = Convert.ToInt32(countCmd.ExecuteScalar());
+
+ // 获取分页消息
+ query += "ORDER BY timestamp DESC LIMIT @count OFFSET @offset";
+ using var cmd = new SQLiteCommand(query, User_db);
+ cmd.Parameters.AddRange(parameters.ToArray());
+ cmd.Parameters.AddWithValue("@count", historyReq.count);
+ cmd.Parameters.AddWithValue("@offset", historyReq.offset);
+
+ using var reader = cmd.ExecuteReader();
+ while (reader.Read())
+ {
+ response.history.Add(new ChatRegisterData
+ {
+ type = "chat",
+ userid = reader["sender_id"].ToString() ?? "Unid",
+ user = GetUsernameByUserId(reader["sender_id"].ToString() ?? "Unid"),
+ msgtype = MessageType.Text,
+ message = reader["content"].ToString(),
+ timestamp = Convert.ToDateTime(reader["timestamp"])
+ });
+ }
+ SendWithPrefix(socket,JsonSerializer.SerializeToUtf8Bytes(response));
+ }
+ }
else
{
log.Warn("未知的请求类型: " + Type.type);
@@ -285,6 +391,59 @@ namespace chatserver
}
}
///
+ /// 压缩数据
+ ///
+ ///
+ ///
+ private static byte[] Compress(byte[] data)
+ {
+ if (data.Length < 256)
+ return data;
+
+ using (var compressedStream = new MemoryStream())
+ {
+ using (var zipStream = new GZipStream(compressedStream, CompressionMode.Compress))
+ {
+ zipStream.Write(data, 0, data.Length);
+ }
+ return compressedStream.ToArray();
+ }
+ }
+ ///
+ /// 解压缩数据
+ ///
+ ///
+ ///
+ private static byte[] Decompress(byte[] compressedData)
+ {
+ if (compressedData.Length < 2 || compressedData[0] != 0x1F || compressedData[1] != 0x8B)
+ return compressedData;
+
+ using (var compressedStream = new MemoryStream(compressedData))
+ using (var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
+ using (var resultStream = new MemoryStream())
+ {
+ zipStream.CopyTo(resultStream);
+ return resultStream.ToArray();
+ }
+ }
+ ///
+ /// 发送数据
+ ///
+ ///
+ ///
+ private static void SendWithPrefix(Socket socket, byte[] data)
+ {
+ byte[] compressedData = Compress(data);
+ byte[] lengthPrefix = BitConverter.GetBytes(compressedData.Length);
+ byte[] fullMessage = new byte[lengthPrefix.Length + compressedData.Length];
+
+ Buffer.BlockCopy(lengthPrefix, 0, fullMessage, 0, lengthPrefix.Length);
+ Buffer.BlockCopy(compressedData, 0, fullMessage, lengthPrefix.Length, compressedData.Length);
+ log.Info($"发送数据(长度:{data.Length},压缩后长度:{lengthPrefix.Length},总体长度:{fullMessage.Length})");
+ socket.Send(fullMessage);
+ }
+ ///
/// 查询User_db是否有相同用户名
///
///
@@ -311,7 +470,7 @@ namespace chatserver
return result != null ? result.ToString()! : string.Empty;
}
// 在ChatServer类中添加一个方法用于初始化users表
- private static void EnsureUsersTableExists()
+ private static void InitializeTable()
{
using var cmd = new SQLiteCommand(@"
CREATE TABLE IF NOT EXISTS users (
@@ -320,12 +479,34 @@ namespace chatserver
password TEXT NOT NULL
)", User_db);
cmd.ExecuteNonQuery();
+ // 新增消息表
+ using var cmd2 = new SQLiteCommand(@"
+ CREATE TABLE IF NOT EXISTS messages (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ type TEXT NOT NULL CHECK(type IN ('group', 'private')),
+ room_id TEXT,
+ sender_id TEXT NOT NULL REFERENCES users(userid),
+ receiver_id TEXT REFERENCES users(userid),
+ content TEXT NOT NULL,
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
+ )", User_db);
+ cmd2.ExecuteNonQuery();
+
+ // 创建索引提高查询效率
+ using var cmd3 = new SQLiteCommand(@"
+ CREATE INDEX IF NOT EXISTS idx_messages_timestamp
+ ON messages (timestamp DESC)", User_db);
+ cmd3.ExecuteNonQuery();
+ using var cmd4 = new SQLiteCommand(@"
+ CREATE INDEX IF NOT EXISTS idx_messages_room
+ ON messages (room_id, timestamp DESC)", User_db);
+ cmd4.ExecuteNonQuery();
}
///
/// 根据userid查询对应的用户名
///
///
- /// 用户名,如果不存在则返回
+ /// 用户名,如果userid为空或为"Unid",返回默认用户名"Unnamed"
static string GetUsernameByUserId(string userid)
{
if (string.IsNullOrEmpty(userid) || userid == "Unid")