Compare commits

..

5 Commits

Author SHA1 Message Date
b5ebe51aa1 Merge branch 'master' of http://175.24.191.172:1145/D_7/ChatX 2025-06-21 09:48:30 +08:00
e21a07db59 修复发送者端点获取逻辑
在 `Program.cs` 文件中,修改了获取发送者完整端点的代码。原来的代码使用 `socket.RemoteEndPoint?.ToString() ?? "Unknown"`,而现在修改为 `socket.RemoteEndPoint?.ToString() ?? "Unknown:0"`。这个变化确保在没有可用的远程端点时,返回的字符串包含端口信息(默认为0),以便于后续的验证逻辑。
2025-06-21 09:47:55 +08:00
b7d5c2584a 优化项目配置与用户设置管理
- 修改 `MainWindow.xaml.cs` 中的登录响应逻辑,确保获取 `token` 并检查聊天消息状态。
- 更改 `chatclient.csproj` 的目标框架为 `net8.0-windows7.0`,并设置调试类型为 `embedded`。
- 在 `App.config` 中添加用户设置配置,支持聊天信息保存上限。
- 新增 `User` 类于 `ChatData.cs`,存储用户信息。
- 在 `Program.cs` 中实现客户端连接的锁定机制,确保线程安全。
- 修正 `log4net.config` 中的日志文件路径格式。
- 新增 `Settings1.Designer.cs` 和 `Settings1.settings` 文件以管理用户设置。
2025-06-21 09:11:49 +08:00
0dc49cb1dd 编辑文件位置 2025-06-20 20:23:59 +08:00
b6c6d7531f 添加了HarmonyOS Sans作为ui文本字体。 2025-06-15 15:10:30 +08:00
22 changed files with 247 additions and 57 deletions

View File

@ -4,7 +4,7 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:local="clr-namespace:chatclient"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<materialDesign:BundledTheme BaseTheme="Light" PrimaryColor="DeepPurple" SecondaryColor="Lime" />

View File

@ -87,6 +87,12 @@
</Setter>
</Style>
<Style x:Key="MaterialDesignLightSeparator" TargetType="Separator" BasedOn="{StaticResource {x:Type Separator}}">
<Setter Property="Background" Value="#F0252526"/>
<Setter Property="BorderBrush" Value="#F0252526"/>
<Setter Property="Height" Value="1"/>
</Style>
<!-- 托盘菜单样式 -->
<Style x:Key="MaterialTrayMenu" TargetType="ContextMenu">
<!--<Setter Property="Background" Value="{DynamicResource MaterialDesignPaper}"/>-->

View File

@ -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["Separator"] });
contextMenu.Items.Add(new Separator { Style = (Style)Application.Current.Resources["MaterialDesignLightSeparator"] });
//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"] });

View File

@ -5,6 +5,7 @@ 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 int ServerPort = 52006;
}

View File

@ -43,13 +43,6 @@
<StackPanel Orientation="Horizontal">
<TextBlock Text="聊天室" FontSize="16" FontWeight="Bold"
VerticalAlignment="Center" Margin="5,0" Cursor="Hand"/>
<!--<Button x:Name="btnRefresh" Style="{StaticResource MaterialDesignIconButton}"
ToolTip="刷新列表" Click="RefreshContacts_Click" Margin="5,0,10,0">
<materialDesign:PackIcon Kind="Refresh"/>
</Button>-->
<!--<ComboBox x:Name="cmbContacts" Width="150" Margin="5,0"
materialDesign:HintAssist.Hint="选择联系人"
DisplayMemberPath="DisplayName"/>-->
</StackPanel>
</materialDesign:Card>
@ -144,11 +137,20 @@
<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 &amp; 绪山七寻" Margin="0,0,0,8"/>
<TextBlock Text="本项目基于 .NET 8 和 MaterialDesignInXAML。" TextWrapping="Wrap" Margin="0,0,0,8"/>
<TextBlock Text="关于" x:Name="About" FontSize="20" FontWeight="Bold" Margin="0,0,0,12"/>
<TextBlock Text="ChatX 聊天室" FontSize="16" Margin="0,0,0,2"/>
<TextBlock Text="版本 0.0.2" Margin="0,0,0,2"/>
<TextBlock Text="作者DZY &amp; 绪山七寻" Margin="0,0,0,10"/>
<TextBlock Text="使用的第三方资源" FontSize="16" Margin="0,0,0,2"/>
<TextBlock Text="ChatX" Margin="0,0,0,2" FontSize="13"/>
<TextBlock Text="├─ .NET 8.0" Margin="0,0,0,2"/>
<TextBlock Text="├─ WPF" Margin="0,0,0,2"/>
<TextBlock Text="├─ HarmonyOS Sans" Margin="0,0,0,2"/>
<TextBlock Text="├─ MaterialDesignThemes" Margin="0,0,0,2"/>
<TextBlock Text="│ ├─ MaterialDesignThemes.MahApps" Margin="0,0,0,2"/>
<TextBlock Text="│ └─ MaterialDesignThemes.Wpf" Margin="0,0,0,2"/>
<TextBlock Text="├─ log4net" Margin="0,0,0,2"/>
<TextBlock Text="└─ Hardcodet.NotifyIcon.Wpf" Margin="0,0,0,2"/>
</StackPanel>
</TabItem>
</TabControl>

View File

@ -139,9 +139,9 @@ namespace chatclient
var LoginResponse = JsonSerializer.Deserialize<LoginResultData>(msg);
if (LoginResponse!.status == "succeed" && LoginResponse != null)
{
//token = LoginResponse.token;
UserId = LoginResponse.userid ?? "Unid";
UserName = LoginResponse.username ?? "Unnamed";
token = LoginResponse.token ?? "NoToken";
Application.Current.Dispatcher.Invoke(() =>
{
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
@ -273,36 +273,50 @@ namespace chatclient
{
Application.Current.Dispatcher.Invoke(() =>
{
// 处理聊天消息
if (chat.userid == UserId)
if (chat.status == "succeed")
{
var chatmessage = new ChatMessage
// 处理聊天消息
if (chat.userid == UserId)
{
Sender = chat.user ?? "未知用户",
MsgType = MessageType.Text,
Image = new BitmapImage(new Uri(chat.avatar ?? "pack://application:,,,/resource/user.png")),
Content = chat.message ?? "(无内容)",
Timestamp = chat.timestamp ?? DateTime.Now,
Alignment = HorizontalAlignment.Right,
SenderColor = new SolidColorBrush(Colors.Blue)
};
var chatmessage = new ChatMessage
{
Sender = chat.user ?? "未知用户",
MsgType = MessageType.Text,
Image = new BitmapImage(new Uri(chat.avatar ?? "pack://application:,,,/resource/user.png")),
Content = chat.message ?? "(无内容)",
Timestamp = chat.timestamp ?? DateTime.Now,
Alignment = HorizontalAlignment.Right,
SenderColor = new SolidColorBrush(Colors.Blue)
};
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
mainWindow?.Messages.Add(chatmessage);
}
else
{
var chatmessage = new ChatMessage
{
Sender = chat.user ?? "未知用户",
MsgType = MessageType.Text,
Image = new BitmapImage(new Uri(chat.avatar ?? "pack://application:,,,/resource/user.png")),
Content = chat.message ?? "(无内容)",
Timestamp = chat.timestamp ?? DateTime.Now,
Alignment = HorizontalAlignment.Left,
SenderColor = new SolidColorBrush(Colors.Black)
};
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
mainWindow?.Messages.Add(chatmessage);
}
}
else if (chat.status == "error_0")
{
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
mainWindow?.Messages.Add(chatmessage);
mainWindow?.QueueMessage("登录已退出");
}
else
{
var chatmessage = new ChatMessage
{
Sender = chat.user ?? "未知用户",
MsgType = MessageType.Text,
Image = new BitmapImage(new Uri(chat.avatar ?? "pack://application:,,,/resource/user.png")),
Content = chat.message ?? "(无内容)",
Timestamp = chat.timestamp ?? DateTime.Now,
Alignment = HorizontalAlignment.Left,
SenderColor = new SolidColorBrush(Colors.Black)
};
log.Error("反序列化聊天数据时返回了 null");
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
mainWindow?.Messages.Add(chatmessage);
mainWindow?.QueueMessage("服务器返回了错误的聊天数据");
}
});
}
@ -364,7 +378,7 @@ namespace chatclient
message = txtMessage.Text,
msgtype = MessageType.Text,
userid = UserId ?? "Unid", // 使用UserId作为发送者ID
token = token // 添加当前token
token = token
};
string ChatJsonData = JsonSerializer.Serialize(newChatMessage);
byte[] dataBytes = Encoding.UTF8.GetBytes(ChatJsonData);

View File

@ -2,19 +2,21 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<TargetFramework>net8.0-windows7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<ApplicationIcon>resource\chat.ico</ApplicationIcon>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType>
<DebugType>embedded</DebugType>
<Optimize>False</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>full</DebugType>
<DebugType>embedded</DebugType>
</PropertyGroup>
<ItemGroup>

Binary file not shown.

View File

@ -2,6 +2,16 @@
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="chatserver.config.Settings1" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<log4net configSource="config\log4net.config" />
<userSettings>
<chatserver.config.Settings1>
<setting name="聊天信息保存上限" serializeAs="String">
<value>100</value>
</setting>
</chatserver.config.Settings1>
</userSettings>
</configuration>

View File

@ -13,4 +13,11 @@ namespace chatserver.Data
File,//文件
System//系统信息
}
public class User
{
public required string UserId { get; set; } = "UnId";
public required string LoginIP { get; set; } = "Unnamed";
public string? Avatar { get; set; }
public required string token { get; set; }
}
}

View File

@ -7,6 +7,7 @@ using System.Data.SQLite;
using chatserver.Data;
using System.Text.Json;
using System.Reflection;
using static log4net.Appender.FileAppender;
[assembly: XmlConfigurator(ConfigFile = "config/log4net.config", Watch = true)]
namespace chatserver
@ -14,8 +15,9 @@ namespace chatserver
internal class ChatServer
{
private static readonly ILog log = LogManager.GetLogger(typeof(ChatServer));
private static readonly object Client_lock = new object();
private static List<Socket> Client = new();
private static List<string> token = new();
private static List<User> LoginUser = new();
private static Socket? Server;
private static SQLiteConnection? User_db;
@ -33,8 +35,8 @@ namespace chatserver
try
{
Socket client = Server.Accept();
Client.Add(client);
log.Info($"用户{client.RemoteEndPoint}连接");
lock (Client_lock) { Client.Add(client); }
log.Info($"用户 {client.RemoteEndPoint} 连接");
Thread thread = new Thread(() => HandleClient(client));
thread.Start();
}
@ -57,7 +59,7 @@ namespace chatserver
try
{
while (true)
{
byte[] buffer = new byte[1024];
int received = socket.Receive(buffer);
@ -122,25 +124,43 @@ namespace chatserver
log.Warn("用户名或密码不能为空");
var emptyResult = new LoginResultData { type = "login", status = "error_-1", message = "用户名或密码不能为空" };
socket.Send(JsonSerializer.SerializeToUtf8Bytes(emptyResult));
continue; // 如果用户名或密码为空,则跳过登录流程
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; // 如果用户名长度不符合要求,则跳过登录流程
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));
continue;
}
var Authentication = UserAuthentication(loginData.username, loginData.password);
if (Authentication != "")
{
log.Info($"用户 {loginData.username} 登录成功 (id:{Authentication})");
var timedUlid = Ulid.NewUlid(DateTimeOffset.UtcNow);
lock (Client_lock)
{
LoginUser.Add(new User
{
UserId = Authentication,
LoginIP = socket.RemoteEndPoint?.ToString() ?? "Unknown:0",
Avatar = null,
token = timedUlid.ToString()
});
}
var result = new LoginResultData
{
type = "login",
status = "succeed",
message = "登录成功",
//token = Authentication,
token = timedUlid.ToString(),
username = loginData.username,
userid = Authentication
};
@ -160,6 +180,39 @@ namespace chatserver
if (chatData != null && chatData.message != null)
{
log.Info($"接收到聊天消息: {chatData.message}");
if (Client.Count == 0)
{
log.Warn("没有客户端连接,取消发送消息。");
return;
}
// 获取发送者完整端点IP: 端口)
string senderEndpoint = socket.RemoteEndPoint?.ToString() ?? "Unknown:0";
// ==== 修改使用完整端点验证非IP部分====
var loginUser = LoginUser.FirstOrDefault(u => u.LoginIP == senderEndpoint && u.UserId == chatData.userid);
if (loginUser == null || loginUser.token != chatData.token)
{
log.Warn($"聊天消息验证失败:端点 {senderEndpoint} 未登录或 token 不匹配");
var errorData = new ChatRegisterData
{
type = "chat",
userid = chatData.userid ?? "Unid",
status = "error_0",
message = "未登录或token无效无法发送消息",
msgtype = MessageType.Text,
timestamp = DateTime.Now
};
socket.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(errorData)));
continue;
}
List<Socket> clientsCopy;
List<User> usersCopy;
// 1. 获取集合快照
lock (Client_lock)
{
clientsCopy = new List<Socket>(Client); // 创建客户端列表副本
usersCopy = new List<User>(LoginUser); // 创建用户列表副本
}
var chatRegisterData = new ChatRegisterData
{
type = "chat",
@ -167,12 +220,31 @@ namespace chatserver
user = GetUsernameByUserId(chatData.userid!),
message = chatData.message,
msgtype = chatData.msgtype ?? MessageType.Text,
// 替换为
status = "succeed",
timestamp = DateTime.Now
};
foreach (var client in Client)
foreach (var user in usersCopy)
{
client.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(chatRegisterData)));
// 查找匹配的客户端
var targetClient = clientsCopy.FirstOrDefault(c =>
c.RemoteEndPoint?.ToString() == user.LoginIP);
if (targetClient != null)
{
try
{
// ==== 修改:添加发送异常处理 ====
targetClient.Send(System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(chatRegisterData)));
}
catch (SocketException ex)
{
log.Error($"发送消息到 {user.LoginIP} 失败: {ex.SocketErrorCode}");
}
catch (Exception ex)
{
log.Error($"发送消息异常: {ex.Message}");
}
}
}
}
else
@ -201,9 +273,15 @@ namespace chatserver
}
finally
{
log.Info($"用户{socket.RemoteEndPoint}已断开连接");
socket.Close();
Client.Remove(socket);
lock (Client_lock)
{
log.Info($"用户 {socket.RemoteEndPoint} 已断开连接");
var disconnectedIp = socket.RemoteEndPoint?.ToString();
if (!string.IsNullOrEmpty(disconnectedIp))
{
LoginUser.RemoveAll(u => u.LoginIP.StartsWith(disconnectedIp));
}
}
}
}
/// <summary>

View File

@ -7,6 +7,14 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<ItemGroup>
<None Remove="config\log4net.config" />
</ItemGroup>
@ -23,4 +31,19 @@
<PackageReference Include="Ulid" Version="1.3.4" />
</ItemGroup>
<ItemGroup>
<Compile Update="config\Settings1.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings1.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="config\Settings1.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings1.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
</Project>

38
chatserver/config/Settings1.Designer.cs generated Normal file
View File

@ -0,0 +1,38 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
//
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace chatserver.config {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.13.0.0")]
internal sealed partial class Settings1 : global::System.Configuration.ApplicationSettingsBase {
private static Settings1 defaultInstance = ((Settings1)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings1())));
public static Settings1 Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("100")]
public int {
get {
return ((int)(this["聊天信息保存上限"]));
}
set {
this["聊天信息保存上限"] = value;
}
}
}
}

View File

@ -0,0 +1,9 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="chatserver.config" GeneratedClassName="Settings1">
<Profiles />
<Settings>
<Setting Name="聊天信息保存上限" Type="System.Int32" Scope="User">
<Value Profile="(Default)">100</Value>
</Setting>
</Settings>
</SettingsFile>

View File

@ -22,7 +22,7 @@
<!-- 日志文件编码 -->
<encoding value="utf-8" />
<!-- 日志文件目录 -->
<file value="log\\" />
<file value="log\" />
<!-- 是否追加到文件 -->
<appendToFile value="true" />
<!-- 按日期滚动日志文件 -->