项目名称：chat_room 聊天室项目

一、项目整体结构

这个项目目前分成两个主要部分：

1. 服务端
路径：D:\Network\chat_room\chat
技术栈：Java + Gradle + Java-WebSocket
作用：负责接收客户端 WebSocket 连接、管理在线用户、处理登录、群聊、私聊、在线列表等消息路由。

2. 客户端
路径：D:\Network\chat_room\user
技术栈：Android Java + Gradle
作用：作为手机 App 连接服务端，发送登录消息、发送聊天消息、接收服务端推送消息，并在界面上展示。


二、通信方式

整个项目的通信方式是 WebSocket。

客户端通过 WebSocket 连接服务端。
当前对外使用的地址逻辑是：

- 服务端 Java 程序实际监听 8080
- 服务器上的 Nginx 监听 80
- Nginx 把 80 的 WebSocket 请求反向代理到 127.0.0.1:8080

所以客户端默认连接地址是：

ws://124.220.100.95

客户端不需要写 8080，直接走 80 即可。


三、消息协议

服务端和客户端之间传输的消息统一使用 JSON。

核心消息结构是 WsMessage，字段如下：

- type：消息类型
- from：发送者用户名
- to：接收者用户名，私聊时使用
- content：消息内容
- timestamp：时间戳

示例：

{
  "type": "ALL",
  "from": "alice",
  "to": null,
  "content": "hello",
  "timestamp": 1710000000000
}

当前支持的消息类型在 MessageType 中定义：

- LOGIN：登录
- ALL：群聊消息
- PRIVATE：私聊消息
- ONLINE：在线用户列表
- HISTORY：历史消息
- QUIT：退出
- SYSTEM：系统消息
- ERROR：错误消息


四、服务端逻辑

1. 启动入口

文件：
chat\src\main\java\com\campus\chat\ChatApplication.java

作用：
- 读取监听地址和端口
- 默认 host = 0.0.0.0
- 默认 port = 8080
- 创建 ChatWebSocketServer
- 启动后一直阻塞运行


2. WebSocket 服务主类

文件：
chat\src\main\java\com\campus\chat\server\ChatWebSocketServer.java

作用：
- 接收客户端连接
- 接收客户端关闭
- 接收客户端文本消息
- 解码 JSON
- 把消息交给消息路由器处理

主要流程如下：

1. onOpen
   有新客户端连上时，先打印连接日志。

2. onMessage
   收到字符串消息后：
   - 用 JsonMessageCodec 把 JSON 转成 WsMessage
   - 根据当前连接查找对应会话
   - 找不到会话时，构造一个临时 SessionInfo(null, conn)
   - 把消息交给 ChatMessageRouter.route() 处理
   - 如果解析失败或处理异常，返回 ERROR 类型消息

3. onClose
   连接断开时：
   - 从在线会话管理器中移除
   - 如果该连接已经登录过用户名，则向所有人广播“某某 disconnected”


3. 会话管理

文件：
chat\src\main\java\com\campus\chat\server\ChatRoomManager.java

作用：
- 维护连接和用户之间的映射关系

内部维护两张表：

1. sessionsBySocket
   key = WebSocket 连接
   value = SessionInfo

2. sessionsByUsername
   key = 用户名
   value = SessionInfo

这个类负责：
- register：注册登录用户
- remove：移除断开连接
- findBySocket：按连接查会话
- findByUsername：按用户名查会话
- listSessions：获取当前在线用户列表
- sockets：获取所有在线连接，方便广播


4. 消息路由

文件：
chat\src\main\java\com\campus\chat\server\ChatMessageRouter.java

这是服务端真正的业务核心。

收到消息后，route() 会按 type 分发：

- LOGIN -> handleLogin()
- ALL -> broadcast()
- PRIVATE -> privateChat()
- ONLINE -> sendOnlineList()
- HISTORY -> sendHistory()
- QUIT -> handleQuit()


5. 登录逻辑

handleLogin() 的逻辑：

1. 读取 from 作为用户名
2. 如果用户名为空，返回系统消息：Username is required.
3. 如果用户名已被别的连接占用，返回系统消息：Username already in use.
4. 如果用户名可用，注册到 ChatRoomManager
5. 重新按 socket 取回真正已注册的 SessionInfo
6. 给当前用户发送欢迎消息
7. 给所有在线用户广播“xxx joined the chat.”
8. 给当前用户发送在线用户列表

这里的关键修复点是第 5 步。
以前 register 完成后仍然拿旧的 sender 对象继续发消息，旧对象里的 username 还是 null，导致登录后的状态不一致。
现在改成 register 之后重新从 roomManager.findBySocket() 取回已注册会话，再继续发 Welcome 和 ONLINE。


6. 群聊逻辑

broadcast() 的逻辑：

1. 确保当前连接已经先 LOGIN
2. 构造一个新的 ALL 消息
3. from 使用当前登录用户名
4. content 使用用户发送的内容
5. 广播给所有在线连接


7. 私聊逻辑

privateChat() 的逻辑：

1. 确保当前连接已经登录
2. 读取 to 作为目标用户名
3. 如果 to 为空，返回系统错误
4. 如果目标用户不在线，返回系统错误
5. 如果目标在线，则构造 PRIVATE 消息
6. 先发给目标用户
7. 再发给发送者自己


8. 在线列表逻辑

sendOnlineList() 的逻辑：

1. 获取所有 SessionInfo
2. 取出用户名
3. 拼成一个逗号分隔的字符串
4. 以 ONLINE 类型消息发回当前用户

目前在线列表的数据格式还是一个字符串，不是结构化 JSON 数组。


9. 历史消息逻辑

sendHistory() 目前还没有真正实现。

现在的行为是：
- 如果客户端发 HISTORY
- 服务端只返回一条系统消息：History is not implemented yet.


五、客户端逻辑

1. 主界面入口

文件：
user\app\src\main\java\com\example\user\MainActivity.java

这是 Android 客户端的核心控制器。

现在的主界面已经不是“调试面板”模式，而是更接近正常聊天页：

- 顶部只显示标题和连接状态
- 页面不再显示服务器地址输入框
- 页面不再显示昵称输入框
- 提供设置按钮用于修改昵称
- 提供重连按钮用于手动重连
- 提供消息列表、输入框和发送按钮


2. 客户端启动流程

App 打开后：

1. 初始化状态栏、按钮、消息列表
2. 读取本地保存的昵称
3. 如果本地没有昵称，则弹窗要求先设置昵称
4. 如果已有昵称，则自动连接 ws://124.220.100.95

也就是说：
- 服务器地址已经内置
- 昵称不再放在主屏幕上
- 聊天主屏更接近普通 IM 应用


3. 客户端连接逻辑

connectToServer() 的逻辑：

1. 如果正在连接、已经连接或昵称为空，则直接返回或弹昵称框
2. 关闭旧连接，取消重连任务
3. 构造内置服务器 URI
4. 设置状态为 connecting
5. 创建 ChatWebSocketClient
6. onOpen 后自动发送 LOGIN

连接过程中：
- isConnecting = true
- isConnected = false
- isLoggedIn = false
- 发送按钮禁用


4. 客户端连接成功后的逻辑

onOpen() 中会做这些事：

1. 设置 isConnected = true
2. 状态显示改成“登录中”
3. 自动发送 LOGIN 消息

客户端现在不会在 WebSocket 刚连上时立刻启用发送按钮，而是要等服务端真正回 Welcome 或 ONLINE 之后才认为登录完成。


5. 客户端接收消息逻辑

handleIncomingMessage() 的逻辑：

1. 把服务端返回的 JSON 转成客户端自己的 WsMessage
2. 先调用 consumeInternalMessage() 处理登录状态相关消息
3. 如果是 SYSTEM 或 ERROR，则作为系统消息显示
4. 否则按 from 是否等于当前用户，分成 incoming 和 outgoing 展示


6. 客户端发送消息逻辑

sendCurrentMessage() 的逻辑：

1. 如果 WebSocket 未打开，或者还没完成登录，则不给发送
2. 读取输入框内容
3. 如果内容为空，不发送
4. 构造 ALL 类型 WsMessage
5. 通过 ChatWebSocketClient.sendMessage() 发给服务端
6. 清空输入框

这里也做了一处修正：
以前客户端本地会先插入一条自己的消息，服务端广播回来后又显示一次，容易造成重复显示。
现在改成只依赖服务端真正广播回来的消息来显示。


7. 客户端昵称逻辑

当前新增了昵称弹窗和本地保存逻辑：

- 首次进入必须设置昵称
- 昵称保存到 SharedPreferences
- 如果服务端返回 Username already in use.
  客户端会弹窗要求重新换昵称

同时默认昵称也不再固定写成 guest，而是根据设备信息自动生成一个更容易区分的默认值。
这样能避免两台手机都叫 guest，导致第二台手机表面连上了 WebSocket，但其实登录失败。


8. 客户端网络包装类

文件：
user\app\src\main\java\com\example\user\client\ChatWebSocketClient.java

作用：
- 继承 Java-WebSocket 的 WebSocketClient
- 把 onOpen / onMessage / onClose / onError 回调转给 MainActivity
- sendMessage() 负责把 WsMessage 转成 JSON 再发送


9. 客户端消息展示

文件：
user\app\src\main\java\com\example\user\ui\ChatMessageAdapter.java

作用：
- RecyclerView 适配器
- 把消息列表渲染成聊天气泡

显示规则：
- system -> 居中
- incoming -> 靠左
- outgoing -> 靠右


六、部署逻辑

当前线上部署方式如下：

1. Java 服务端
- 在 Linux 服务器上运行
- 部署目录：/opt/chat
- systemd 服务名：chat
- 启动命令：/usr/bin/env bash /opt/chat/bin/chat
- 实际 Java 监听端口：8080

2. 反向代理
- Nginx 监听 80
- 把 WebSocket 请求转发到 127.0.0.1:8080

3. 对外访问地址
- ws://124.220.100.95


七、这次故障修复后的真实链路

1. 用户打开 App
2. App 读取本地昵称
3. 如果没有昵称，先弹窗要求设置
4. App 自动连接 ws://124.220.100.95
5. Nginx 接收 80 端口 WebSocket 请求
6. Nginx 转发到本机 8080
7. Java WebSocket 服务收到连接
8. 客户端连接成功后自动发送 LOGIN
9. 服务端注册用户名
10. 服务端正确回 Welcome、joined、ONLINE
11. 客户端收到回包后把 isLoggedIn 设为 true
12. 发送按钮解锁
13. 用户发送消息
14. 服务端广播 ALL
15. 其他客户端收到消息并展示


八、当前已经具备的能力

目前这套项目已经具备：

- Android 客户端连接 WebSocket
- 自动连接服务器
- 首次弹窗设置昵称
- 本地保存昵称
- 自动发送登录消息
- 登录成功后才允许发送
- 群聊消息广播
- 私聊消息路由
- 在线会话管理
- 系统消息显示
- 用户断开广播
- Nginx 反代 WebSocket
- systemd 托管服务端


九、当前还没有完成的部分

目前还存在这些限制：

1. 历史消息未实现
- 没有数据库
- 没有文件落盘
- HISTORY 只有占位回复

2. 在线列表只是字符串
- 还不是结构化数据
- 客户端也还没做在线用户面板

3. 客户端当前默认是群聊
- 没有私聊选择 UI
- 虽然服务端支持 PRIVATE，但客户端界面还没接出来

4. 用户体系很简单
- 只有昵称
- 没有账号密码
- 没有鉴权

5. 没有消息可靠性机制
- 没有消息 ack
- 没有重发
- 没有离线消息

6. 没有正式日志系统
- 主要还是 println
- 没有统一日志级别和日志文件策略


十、后续建议

如果要把这个项目继续往真正可用的聊天产品推进，建议按这个顺序做：

1. 先做服务端历史消息持久化
2. 再做客户端在线用户列表
3. 再把私聊 UI 做出来
4. 再做登录鉴权
5. 再做消息状态、重连、离线消息
6. 最后补 HTTPS / WSS 和正式域名


十一、总结

这个项目现在本质上是一个“WebSocket 即时聊天系统”。

结构上：
- Java 服务端负责连接管理和消息路由
- Android 客户端负责连接、发送、接收和展示
- Nginx 负责把外部 80 端口流量转发给 8080 的 Java 服务

业务上：
- 登录靠 LOGIN 消息
- 群聊靠 ALL
- 私聊靠 PRIVATE
- 在线列表靠 ONLINE
- 系统提示靠 SYSTEM

当前它已经能作为一个基础聊天室运行，并且这次“卡在登录中、发送按钮灰色、对方收不到消息”的主链路问题已经修复。
