# netchat **Repository Path**: hlw422/netchat ## Basic Information - **Project Name**: netchat - **Description**: 一个基于 .NET 的全栈实时聊天应用,包含 ASP.NET Core Web API 后端和 Avalonia 桌面客户端。支持单聊、群聊、文件/图片传输、好友管理、离线消息等即时通讯功能。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-01 - **Last Updated**: 2026-07-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # NetChat 一个基于 .NET 的全栈实时聊天应用,包含 ASP.NET Core Web API 后端和 Avalonia 桌面客户端。支持单聊、群聊、文件/图片传输、好友管理、离线消息等即时通讯功能。 ## 目录 - [功能特性](#功能特性) - [技术架构](#技术架构) - [项目结构](#项目结构) - [快速开始](#快速开始) - [环境要求](#环境要求) - [数据库配置](#数据库配置) - [启动后端](#启动后端) - [启动桌面客户端](#启动桌面客户端) - [API 接口文档](#api-接口文档) - [SignalR 实时通信](#signalr-实时通信) - [数据库设计](#数据库设计) - [桌面客户端](#桌面客户端) - [测试](#测试) - [配置说明](#配置说明) --- ## 功能特性 ### 核心功能 | 功能 | 说明 | |------|------| | **用户认证** | 注册 / 登录,JWT Token 认证 | | **单聊** | 好友之间 1v1 实时消息 | | **群聊** | 创建群组、添加/移除成员、群内消息 | | **消息类型** | 文本、图片、文件 | | **文件传输** | 上传/下载文件,支持图片预览 | | **好友管理** | 搜索用户、发送/处理好友请求、删除好友 | | **在线状态** | 实时显示好友在线/离线状态 | | **离线消息** | 上线自动推送未读消息 | | **消息确认** | 消息发送状态回执 | | **表情符号** | 内置 40+ 常用 Emoji | ### 桌面客户端特性 - 🎨 **微信风格 UI**:仿微信的聊天气泡、颜色主题 - 📱 **多会话管理**:左侧会话列表,支持未读消息计数 - 👥 **通讯录视图**:好友列表、群组列表、创建群组 - 🔍 **用户搜索**:通过昵称搜索和添加好友 - 📎 **文件/图片发送**:选择本地文件直接发送 - 😀 **表情选择器**:Emoji 面板 - ↩ **退出登录**:清除本地 Token --- ## 技术架构 ``` ┌──────────────────────────────────────────────────────┐ │ NetChat.Desktop │ │ Avalonia UI + ReactiveUI (MVVM) │ │ SignalR Client (net8.0) │ │ HTTP REST + WebSocket │ ├──────────────────────────────────────────────────────┤ │ NetChat.Api │ │ ASP.NET Core Web API (net6.0) │ │ Controllers / SignalR Hub / Middleware │ │ Swagger UI │ ├──────────────────────────────────────────────────────┤ │ NetChat.Application │ │ 应用服务层 — Business Logic │ │ Services / DTOs (record types) │ ├──────────────────────┬───────────────────────────────┤ │ NetChat.Domain │ NetChat.Infrastructure │ │ ──────────────── │ ──────────────────────── │ │ 实体 / 枚举 / │ EF Core (PostgreSQL) │ │ 接口 (无外部依赖) │ Repository 实现 │ │ │ Redis (在线状态/缓存) │ │ │ JWT Token 生成 │ │ │ 本地文件存储 │ └──────────────────────┴───────────────────────────────┘ ``` ### 分层说明 | 层 | 项目 | 职责 | 依赖 | |----|------|------|------| | **表现层** | `NetChat.Api` | REST API 控制器、SignalR Hub、认证中间件、Swagger | Application | | **应用层** | `NetChat.Application` | 业务逻辑编排、DTO 定义、服务接口 | Domain, Infrastructure | | **领域层** | `NetChat.Domain` | 核心实体、值对象、枚举、仓储接口(无框架依赖) | 无 | | **基础设施层** | `NetChat.Infrastructure` | EF Core DbContext、仓储实现、Redis 缓存、JWT、文件存储 | Domain | | **桌面客户端** | `NetChat.Desktop` | Avalonia UI + ReactiveUI MVVM、SignalR/HTTP 客户端 | 无后端依赖 | --- ## 项目结构 ``` NetChat/ ├── NetChat.slnx # 后端解决方案文件 ├── src/ │ ├── NetChat.Api/ # 🌐 Web API 层 │ │ ├── Controllers/ │ │ │ ├── AuthController.cs # 注册 / 登录 │ │ │ ├── FriendController.cs # 好友管理 │ │ │ ├── MessageController.cs # 聊天历史 │ │ │ ├── GroupController.cs # 群组管理 │ │ │ └── FileController.cs # 文件上传 / 下载 │ │ ├── Hubs/ │ │ │ └── ChatHub.cs # SignalR 实时通信中枢 │ │ ├── Extensions/ │ │ │ ├── ServiceCollectionExtensions.cs # DI 注册 │ │ │ └── ApplicationBuilderExtensions.cs # 中间件配置 │ │ ├── Middleware/ │ │ │ └── ExceptionMiddleware.cs # 全局异常处理 │ │ ├── Program.cs # 应用入口 │ │ └── appsettings.json # 配置文件 │ │ │ ├── NetChat.Application/ # 📋 应用服务层 │ │ ├── Services/ │ │ │ ├── AuthService.cs # 认证逻辑 │ │ │ ├── MessageService.cs # 消息业务 │ │ │ ├── FriendService.cs # 好友业务 │ │ │ ├── GroupService.cs # 群组业务 │ │ │ ├── FileService.cs # 文件业务 │ │ │ └── OnlineStatusService.cs # 在线状态 │ │ └── DTOs/ │ │ ├── AuthDto.cs # 认证 DTO │ │ ├── MessageDto.cs # 消息 DTO (record) │ │ ├── FriendDto.cs # 好友 DTO (record) │ │ └── GroupDto.cs # 群组 DTO (record) │ │ │ ├── NetChat.Domain/ # 🧠 领域层 │ │ ├── Entities/ │ │ │ ├── User.cs # 用户实体 │ │ │ ├── Message.cs # 消息实体 │ │ │ ├── Friend.cs # 好友实体 │ │ │ ├── FriendRequest.cs # 好友请求实体 │ │ │ ├── Group.cs # 群组实体 │ │ │ └── GroupMember.cs # 群组成员实体 │ │ ├── Enums/ │ │ │ ├── MessageStatus.cs # Sent / Delivered / Read │ │ │ ├── MessageType.cs # Text / Image / File │ │ │ ├── FriendRequestStatus.cs # Pending / Accepted / Rejected │ │ │ └── UserOnlineStatus.cs # Offline / Online │ │ └── Interfaces/ │ │ ├── IUserRepository.cs # 用户仓储接口 │ │ ├── IMessageRepository.cs # 消息仓储接口 │ │ ├── IFriendRepository.cs # 好友仓储接口 │ │ ├── IFriendRequestRepository.cs │ │ ├── IGroupRepository.cs # 群组仓储接口 │ │ ├── IUnitOfWork.cs # 工作单元接口 │ │ ├── IJwtTokenGenerator.cs # JWT 生成器接口 │ │ ├── ICurrentUserProvider.cs # 当前用户提供者 │ │ └── IFileStorage.cs # 文件存储接口 │ │ │ ├── NetChat.Infrastructure/ # 🔧 基础设施层 │ │ ├── Data/ │ │ │ ├── AppDbContext.cs # EF Core 数据库上下文 │ │ │ └── Configurations/ # 6 个实体映射配置 │ │ ├── Repositories/ │ │ │ ├── BaseRepository.cs # 泛型基类 │ │ │ ├── UserRepository.cs │ │ │ ├── MessageRepository.cs │ │ │ ├── FriendRepository.cs │ │ │ ├── FriendRequestRepository.cs │ │ │ └── GroupRepository.cs │ │ ├── Cache/ │ │ │ ├── IRedisCacheService.cs │ │ │ └── RedisCacheService.cs # Redis 在线状态 / Pub/Sub │ │ ├── Auth/ │ │ │ ├── JwtTokenGenerator.cs # HMAC-SHA256 JWT 生成 │ │ │ └── CurrentUserProvider.cs │ │ ├── Storage/ │ │ │ └── LocalFileStorage.cs # 本地文件存储 │ │ └── UnitOfWork.cs │ │ │ └── NetChat.Desktop/ # 🖥️ 桌面客户端 │ ├── NetChat.Desktop.slnx │ ├── Program.cs # Avalonia 启动入口 │ ├── App.axaml / App.xaml.cs # 应用初始化 / 登录路由 │ ├── Views/ │ │ ├── LoginWindow.axaml # 登录 / 注册界面 │ │ ├── MainWindow.axaml # 主聊天界面 │ │ └── ContactListView.axaml # 通讯录视图 │ ├── ViewModels/ │ │ ├── LoginViewModel.cs │ │ ├── MainViewModel.cs # 会话管理 / 未读计数 │ │ ├── ChatViewModel.cs # 消息发送 / 接收 / 历史 │ │ └── ContactListViewModel.cs # 好友 / 群组管理 │ ├── Services/ │ │ ├── ApiService.cs # REST API 客户端 │ │ ├── SignalRService.cs # SignalR 实时通信客户端 │ │ └── AuthService.cs # 认证 Token 存储 │ ├── Models/ │ │ ├── Dtos.cs # 客户端数据传输对象 │ │ └── SessionItem.cs # 会话列表项模型 │ ├── Converters/ │ │ └── BoolConverters.cs # XAML 值转换器 │ └── Themes/ │ └── WeChatTheme.axaml # 微信风格主题 │ └── tests/ ├── NetChat.Api.Tests/ # 🔬 单元测试 (xUnit + Moq) │ ├── MessageServiceTests.cs │ └── FriendServiceTests.cs ├── NetChat.IntegrationTest/ # 🔗 集成测试 (SignalR Client) ├── CleanDb/ # 🧹 数据库清理工具 ├── test_e2e.ps1 # 📜 E2E 测试脚本 └── test_e2e_clean.ps1 # 📜 清理版 E2E 脚本 ``` --- ## 快速开始 ### 环境要求 | 组件 | 版本 | 说明 | |------|------|------| | .NET SDK | 6.0 + 8.0 | 后端用 6.0,桌面端用 8.0 | | PostgreSQL | 13+ | 主数据库 | | Redis | 6.0+ | 在线状态缓存 | | Windows / macOS / Linux | — | 跨平台支持 | ### 数据库配置 1. **安装 PostgreSQL** 并创建数据库: ```sql CREATE DATABASE netchat; ``` 2. **配置连接字符串** — 编辑 `src/NetChat.Api/appsettings.json`: ```json { "ConnectionStrings": { "DefaultConnection": "Host=localhost;Port=5432;Database=netchat;Username=postgres;Password=your_password" } } ``` 3. **自动迁移** — 应用启动时会自动调用 `EnsureCreated()` 创建所有表。 ### 启动后端 ```bash cd src/NetChat.Api dotnet run --urls "http://localhost:5194" ``` 启动后可访问: - **API 服务**:`http://localhost:5194` - **Swagger UI**:`http://localhost:5194/swagger` ### 启动桌面客户端 ```bash cd src/NetChat.Desktop dotnet run ``` > 桌面客户端默认连接 `http://localhost:5194`,确保后端先启动。 --- ## API 接口文档 ### 认证接口(匿名访问) | 方法 | 路径 | 说明 | |------|------|------| | `POST` | `/api/auth/register` | 用户注册 | | `POST` | `/api/auth/login` | 用户登录(返回 JWT Token) | **注册示例:** ```json { "nickname": "Alice", "password": "123456" } ``` **登录响应:** ```json { "token": "eyJhbGciOiJIUzI1NiIs...", "user": { "id": "3fa85f64-...", "nickname": "Alice" } } ``` ### 好友管理接口 | 方法 | 路径 | 说明 | |------|------|------| | `GET` | `/api/friend/list` | 获取好友列表 | | `DELETE` | `/api/friend/{friendId}` | 删除好友 | | `GET` | `/api/friend/search?nickname=xxx` | 搜索用户 | | `POST` | `/api/friend/request` | 发送好友请求 | | `GET` | `/api/friend/requests` | 获取待处理好友请求 | | `POST` | `/api/friend/request/{requestId}/handle` | 处理好友请求 | ### 消息接口 | 方法 | 路径 | 说明 | |------|------|------| | `GET` | `/api/message/history/{userId}` | 单聊历史记录 | | `GET` | `/api/message/group-history/{groupId}` | 群聊历史记录 | ### 群组接口 | 方法 | 路径 | 说明 | |------|------|------| | `POST` | `/api/group` | 创建群组 | | `GET` | `/api/group/mine` | 获取我的群组列表 | | `GET` | `/api/group/{groupId}` | 获取群组详情 | | `POST` | `/api/group/{groupId}/members` | 添加群成员 | | `DELETE` | `/api/group/{groupId}/members/{userId}` | 移除群成员 | ### 文件接口 | 方法 | 路径 | 说明 | |------|------|------| | `POST` | `/api/file/upload` | 上传文件(最大 50MB) | | `GET` | `/api/file/download/{fileName}` | 下载文件 | --- ## SignalR 实时通信 ### Hub 端点 ``` /chatHub?access_token={JWT_TOKEN} ``` ### 客户端 → 服务端方法 | 方法 | 参数 | 说明 | |------|------|------| | `SendMessage` | `SendMessageRequest` | 发送单聊/群聊消息 | | `SendFriendRequest` | `Guid receiverId` | 发送好友请求 | | `HandleFriendRequest` | `Guid requestId, bool accept` | 处理好友请求 | | `JoinGroup` | `Guid groupId` | 加入群组(SignalR Group) | | `LeaveGroup` | `Guid groupId` | 离开群组 | | `Ping` | — | 心跳保活 | ### 服务端 → 客户端事件 | 事件名 | 数据类型 | 说明 | |--------|----------|------| | `ReceiveMessage` | `MessageResponse` | 收到单聊消息 | | `ReceiveGroupMessage` | `MessageResponse` | 收到群聊消息 | | `MessageSent` | `MessageResponse` | 消息发送确认 | | `FriendOnlineStatusChanged` | `(Guid userId, int status)` | 好友上下线通知 | | `FriendRequestReceived` | `FriendRequestResponse` | 收到好友请求 | | `FriendListUpdated` | `List` | 好友列表变更 | | `OfflineMessages` | `List` | 上线推送离线消息 | | `Pong` | — | 心跳响应 | --- ## 数据库设计 ### ER 图 ``` ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │ Users │ │ Messages │ │ Groups │ ├──────────┤ ├──────────────┤ ├──────────────┤ │ Id (PK) │──┐ │ Id (PK, 自增)│ │ Id (PK) │ │ Nickname │ │ │ SenderId ───│──┐ │ Name │ │ PasswordHash│ │ ReceiverId │ │ │ OwnerId ──┐ │ │ Avatar │ │ │ GroupId │ │ │ CreatedAt │ │ │ CreatedAt│ │ │ Type │ │ └─────────────│──┘ └──────────┘ │ │ Content │ │ │ │ │ FileUrl │ │ ┌──────────────┐ ├───→│ FileName │ │ │GroupMembers │ │ │ FileSize │ │ ├──────────────┤ │ │ Status │ │ │ GroupId (PK) │ │ │ CreatedAt ◄──┘ │ │ UserId (PK) │ │ └──────────────┘ │ │ JoinedAt │ │ │ └──────────────┘ │ ┌──────────────┐ │ │ │ Friends │ │ ┌──────────────┐ │ ├──────────────┤ │ │FriendRequests│ │ │ UserId (PK) │◄─┘ ├──────────────┤ └───→│ FriendId(PK) │ │ Id (PK) │ │ CreatedAt │ │ SenderId │ └──────────────┘ │ ReceiverId │ │ Status │ │ CreatedAt │ │ ResolvedAt │ └──────────────┘ ``` ### 关键索引 | 表 | 索引 | 用途 | |----|------|------| | `Messages` | `(ReceiverId, Status, CreatedAt)` | 离线消息查询 | | `Messages` | `(SenderId, ReceiverId, CreatedAt)` | 单聊历史 | | `Messages` | `(GroupId, CreatedAt)` | 群聊历史 | | `Users` | `Nickname` (UNIQUE) | 昵称唯一、搜索 | --- ## 桌面客户端 ### 架构(MVVM) ``` View (XAML) ←→ ViewModel (ReactiveUI) ←→ Services (API/SignalR) ↑ ↑ ↑ Avalonia UI ReactiveObject HttpClient + HubConnection ``` ### 主要 ViewModel | ViewModel | 职责 | |-----------|------| | `LoginViewModel` | 登录/注册,Token 存储 | | `MainViewModel` | 会话列表管理、未读计数、SignalR 事件总控 | | `ChatViewModel` | 消息收发、聊天历史加载、文件/图片发送、乐观UI | | `ContactListViewModel` | 好友搜索与添加、好友列表、群组管理 | ### 主题 自定义微信风格主题(`WeChatTheme.axaml`),包括: - 绿色主题色 (`#07C160`) - 聊天气泡(绿色发送 / 白色接收,圆角不对称) - 侧边栏菜单 - 统一的字体和间距 ### SignalR 客户端特性 - 自动重连(指数退避) - 心跳保活(30 秒 Ping/Pong) - 离线消息自动推送 - 好友状态实时更新 --- ## 测试 ### 单元测试 ```bash cd tests/NetChat.Api.Tests dotnet test ``` 测试覆盖: - `MessageServiceTests`:消息发送、历史查询、离线消息、状态更新 - `FriendServiceTests`:好友请求发送/处理、好友列表、好友删除 ### 集成测试 ```bash cd tests/NetChat.IntegrationTest dotnet run ``` 模拟多用户完整交互流程: 1. 用户注册(Alice / Bob) 2. 登录获取 JWT 3. 建立 SignalR 连接 4. 发送/接受好友请求 5. 实时单聊消息 6. 创建群组并群聊 7. 验证离线消息 ### E2E 测试脚本 ```powershell # 确保后端已启动,然后运行: cd tests ./test_e2e.ps1 ``` Shell 脚本测试: 1. 注册两个用户 2. 登录获取 Token 3. 发送好友请求并接受 4. 验证好友列表 5. 发送消息并检查历史 ### 数据库清理 ```bash cd tests/CleanDb dotnet run ``` --- ## 配置说明 ### appsettings.json(后端) ```json { "ConnectionStrings": { "DefaultConnection": "Host=localhost;Port=5432;Database=netchat;Username=postgres;Password=postgres" }, "Redis": { "ConnectionString": "localhost:6379" }, "JwtSettings": { "SecretKey": "YourSuperSecretKeyForJwtTokenGenerationThatIsAtLeast32Bytes!", "Issuer": "NetChat", "Audience": "NetChat.Client", "ExpiryMinutes": 1440 }, "FileStorage": { "BasePath": "./uploads", "BaseUrl": "/api/file/download/", "MaxFileSize": 52428800 }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } } } ``` | 配置项 | 说明 | 默认值 | |--------|------|--------| | `ConnectionStrings.DefaultConnection` | PostgreSQL 连接字符串 | `localhost:5432` | | `Redis.ConnectionString` | Redis 连接字符串 | `localhost:6379` | | `JwtSettings.SecretKey` | JWT HMAC-SHA256 密钥(至少 32 字节) | — | | `JwtSettings.ExpiryMinutes` | Token 过期时间(分钟) | `1440`(24 小时) | | `FileStorage.BasePath` | 文件存储根目录 | `./uploads` | | `FileStorage.MaxFileSize` | 上传文件最大字节数 | `52428800`(50 MB) | ### 桌面客户端 API 地址 在 `ApiService.cs` 中,默认基地址为: ```csharp private const string BaseUrl = "http://localhost:5194"; ``` 修改此地址可连接到不同的后端服务器。 --- ## 开发指南 ### 添加新功能 1. **Domain 层** — 在 `Entities/` 创建实体,在 `Interfaces/` 定义仓储接口 2. **Infrastructure 层** — 实现仓储、添加 EF Core 配置映射 3. **Application 层** — 创建服务接口和实现、定义 DTO 4. **Api 层** — 添加 Controller 端点(或通过 SignalR Hub) 5. **Desktop 层** — 更新 ViewModel、Service、XAML 视图 ### 代码风格 - **实体 ID**:`Guid` 类型 - **DTO**:使用 C# `record` 类型(不可变) - **依赖注入**:通过构造函数注入,在 `ServiceCollectionExtensions` 注册 - **MVVM**:ViewModel 继承 `ReactiveObject`,属性通过 `RaiseAndSetIfChanged` 通知 - **异步**:全部使用 `async/await` ### 调试 桌面客户端的滚动相关日志会输出到 Visual Studio 的 **Output → Debug** 窗口: ``` [Scroll] ChatScrollViewer found. ActualHeight=500 [Scroll] Message added. Scheduling scroll... [Scroll] Extent=1200 Viewport=500 Offset.Y=0 Target=700 [Scroll] SCROLLED to Y=700 ``` --- ## 许可证 MIT License