详细实现计划:Colleen Chat Room App Bug修复与功能增强
第一部分:Bug修复
Bug 1: 点击右上角退出后很快又会重新连接
根因分析: disconnect() 方法(chat.js:165-180)先调用 this.ws.close(),然后立即将 this.ws = null。但 ws.close() 是异步的 — 关闭操作触发 onclose 事件,而 onclose 处理器绑定的是 onDisconnected() 方法(chat.js:153-163),该方法会尝试自动重连。因此:用户点击退出 → disconnect() 调用 ws.close() → 触发 onclose → onDisconnected() 执行自动重连逻辑。
修复方案:
在
ChatClient中增加一个intentionalDisconnect标志位disconnect()方法在关闭前设置this.intentionalDisconnect = trueonDisconnected()方法检查此标志位,如果是主动断开则不执行重连逻辑在
connect()成功时重置该标志位
涉及文件: src/main/resources/static/js/chat.js
Bug 2: 其他用户进入房间后,当前用户列表不会刷新
根因分析: 当 user_joined 或 user_left 事件到达时,handleUserJoined() 和 handleUserLeft() 调用 this.loadUsersList()(chat.js:310-320)。但 loadUsersList() 方法体是空的(只有一行注释),所以实际上不会刷新用户列表。服务端确实广播了 UserJoined 事件(ChatService.kt:65),但客户端没有利用该数据。
修复方案(方案A - 直接利用事件数据,更高效):
在客户端维护一个
this.users数组当收到
users事件时,初始化this.users并渲染当收到
user_joined事件时,将新用户添加到this.users数组并重新渲染用户列表当收到
user_left事件时,从this.users数组中移除该用户并重新渲染
涉及文件: src/main/resources/static/js/chat.js
Bug 3: 上传文件时,如果文件名太长则会溢出
根因分析: CSS 中 .file-name(style.css:594-598)没有设置文本溢出处理。.message-file 容器有 max-width: 400px,但 .file-name 内容可以超出容器宽度。
修复方案:
为
.file-name添加 CSS 属性:overflow: hidden; text-overflow: ellipsis; white-space: nowrap;同时限制
.file-info的宽度以确保 flex 布局中文本被正确截断:添加min-width: 0;(允许 flex 子项收缩到小于内容宽度)
涉及文件: src/main/resources/static/css/style.css
第二部分:新功能实现
Feature 1: Private Messages(私信功能)
后端变更:
数据库 Schema(V2 migration): 新增
private_messages表:id, sender_id, receiver_id, message_type, content, file_url, file_name, file_size, mime_type, thumbnail_url, is_read, created_at。添加索引(sender_id, receiver_id, created_at)Models.kt: 新增
PrivateMessage数据类、WsEvent.PrivateMessage、WsEvent.PrivateHistory、WsMessagePayload中新增targetUserId字段PrivateMessageRepository.kt: 新增仓库类 —
saveMessage(),getConversation(userId1, userId2, limit, beforeId),markAsRead(messageId, userId),getUnreadCount(userId)ChatService.kt: 新增
sendPrivateMessage()方法 — 通过userConnections找到接收者连接并直接发送,同时发送给发送者确认;新增getPrivateHistory()方法ChatController.kt: 在
onMessage处理器中新增"private_message"类型处理
前端变更:
index.html: 在用户侧边栏中为每个用户添加"发送私信"点击事件;新增私信面板 UI(可作为侧边滑出面板或模态框),包含对话列表和聊天窗口
chat.js: 新增
openPrivateChat(userId, username),sendPrivateMessage(),handlePrivateMessage(data)方法;处理private_message和private_historyWebSocket 事件style.css: 新增私信面板样式 —
.private-chat-panel,.conversation-list,.private-messages等
Feature 2: User Mentions(@提及功能)
后端变更:
ChatService.kt:
sendTextMessage()方法中解析消息内容中的@username模式;为被提及的用户发送专门的通知事件WsEvent.MentionModels.kt: 新增
WsEvent.Mention数据类(包含消息ID、提及者信息)
前端变更:
chat.js:
消息输入框中实现
@触发的自动补全弹出面板(使用当前this.users列表)createMessageElement()中解析并高亮@username(添加特殊样式和可点击性)处理
mention事件 — 显示桌面通知 + toast
style.css: 新增
.mention-highlight样式(使用--color-info颜色高亮);.mention-autocomplete弹出面板样式
Feature 3: Message Editing/Deletion(消息编辑/删除)
后端变更:
数据库 Schema(V2 migration): 在
messages表中添加edited_at TIMESTAMP,is_deleted BOOLEAN DEFAULT FALSE列MessageRepository.kt: 新增
updateMessage(messageId, userId, content)和softDeleteMessage(messageId, userId)方法;getRecentMessages()中处理已删除消息(显示为"此消息已被删除")ChatService.kt: 新增
editMessage(roomId, user, messageId, newContent)和deleteMessage(roomId, user, messageId)方法;编辑/删除后广播WsEvent.MessageEdited/WsEvent.MessageDeleted事件到房间Models.kt: 新增
WsEvent.MessageEdited,WsEvent.MessageDeleted数据类;ChatMessage.Text增加可选editedAt字段ChatController.kt: 在
onMessage中处理"edit"和"delete"类型
前端变更:
chat.js:
消息元素上添加编辑/删除按钮(仅显示在自己发送的消息上,hover 显示)
编辑:点击后将消息内容回填到输入框,输入框上方显示"正在编辑消息"提示,发送时带上消息ID
删除:点击后确认弹窗,确认后发送删除请求
处理
message_edited和message_deleted事件 — 更新/移除对应DOM元素消息元素需要
data-message-id属性以便定位
style.css: 新增消息操作按钮样式、编辑状态指示样式、已删除消息样式
Feature 4: Rich Text(富文本 — Markdown/代码块支持)
后端变更: 无需后端变更 — Markdown 渲染在客户端完成,后端原样存储文本内容。
前端变更:
index.html: 引入轻量级 Markdown 库 — 推荐
marked.js(通过 CDN)+highlight.js(代码高亮)chat.js:
在
createMessageElement()中,对text类型消息内容使用marked.parse()渲染(先转义再解析,使用 DOMPurify 或 marked 内置 sanitizer 防止 XSS)配置 marked:启用 GFM(GitHub Flavored Markdown)、代码高亮
输入框的 hint 更新为提示支持 Markdown 语法
style.css: 新增 Markdown 渲染后元素的样式 —
code,pre,blockquote,em,strong,ul,ol,a等在终端主题下的适配
Feature 5: Search(搜索消息历史)
后端变更:
MessageRepository.kt: 新增
searchMessages(roomId, query, limit, offset)方法 — 使用 SQLLIKE或 SQLite FTS(全文搜索)查询content字段ChatService.kt: 新增
searchMessages(roomId, query)方法ApiController.kt: 新增 REST endpoint
GET /api/rooms/{roomId}/messages/search?q={query}&limit=50&offset=0,或者通过 WebSocket 实现搜索请求/响应
前端变更:
index.html: 在聊天头部或输入面板上方添加搜索栏(可折叠/展开的搜索输入框 + 结果面板)
chat.js:
新增
searchMessages(query)方法 — 通过 REST API 发起搜索搜索结果显示在覆盖层或侧面板中,关键词高亮
点击搜索结果可定位到该消息(如果在当前加载的消息中)
style.css: 搜索栏样式、搜索结果面板样式、高亮样式
Feature 6: User Profiles(用户档案 — 头像、简介、状态消息)
后端变更:
数据库 Schema(V2 migration): 在
users表中添加bio TEXT,status TEXT列Models.kt:
User数据类新增bio: String?,status: String?字段UserRepository.kt: 新增
updateProfile(userId, displayName?, avatarUrl?, bio?, status?)方法ApiController.kt: 新增
GET /api/users/{userId}获取用户信息;新增PUT /api/users/{userId}/profile更新个人资料(含头像上传)ChatService.kt: 用户更新资料后广播
WsEvent.UserUpdated事件
前端变更:
index.html: 新增用户资料弹窗/面板(点击用户头像或用户列表中的用户打开);新增个人资料编辑面板
chat.js:
openUserProfile(userId)方法 — 获取并显示用户信息头像渲染:如果有
avatarUrl,显示图片;否则显示首字母用户状态显示在用户列表中
style.css: 用户资料弹窗样式、头像图片样式、状态指示器样式
Feature 7: Room Permissions(房间权限 — 管理员/版主角色)
后端变更:
数据库 Schema(V2 migration): 在
room_members表中添加role TEXT DEFAULT 'member' CHECK(role IN ('owner', 'admin', 'moderator', 'member'))列Models.kt: 新增
RoomMember数据类(包含用户信息和角色);User或新数据类中携带角色信息RoomMemberRepository.kt: 新增
updateMemberRole(roomId, userId, role)、getMemberRole(roomId, userId)方法;getRoomMembers()返回包含角色信息ChatService.kt:
新增权限检查逻辑:踢人(
kickUser)、禁言(muteUser)、删除他人消息(deleteMessage扩展权限检查)房间创建者自动成为 owner
新增
setUserRole()方法(仅 owner/admin 可操作)
ChatController.kt: 处理新的 WebSocket 消息类型:
"kick","mute","set_role"
前端变更:
chat.js:
用户列表中显示角色徽章(owner/admin/mod)
管理操作菜单(右键或hover按钮)— 仅对有权限的用户显示
处理被踢出/被禁言的事件
style.css: 角色徽章样式、管理操作菜单样式
Feature 8: Message Threading(消息线程 — 回复特定消息)
后端变更:
数据库 Schema(V2 migration): 在
messages表中添加reply_to_id INTEGER REFERENCES messages(id)列Models.kt: 所有
ChatMessage子类新增replyTo: ChatMessage?可选字段(包含被回复消息的摘要信息);WsMessagePayload新增replyToId: Int?字段MessageRepository.kt:
保存消息时支持
replyToId参数getRecentMessages()中通过自连接或额外查询加载被回复消息的摘要
ChatService.kt: 消息发送方法增加
replyToId参数透传
前端变更:
chat.js:
消息元素上添加"回复"按钮(hover 显示)
点击回复后,输入框上方显示"正在回复 @username: 消息预览..."提示条(可取消)
发送时附带
replyToIdcreateMessageElement()中渲染引用的原消息(缩进+半透明的消息预览块)点击引用块可滚动定位到原消息
style.css: 回复引用块样式(
.message-reply-preview)、回复按钮样式、回复指示线样式
第三部分:功能增强
Enhancement 1: 移除100条消息限制,实现滚动加载历史消息
后端变更:
MessageRepository.kt:
getRecentMessages()保持不变(已支持limit参数);新增getMessagesBefore(roomId, beforeId, limit)方法 — 查询id < beforeId的消息,按created_at DESC排序,取limit条后反转ChatService.kt:
getMessageHistory()改为加载最近 30 条而非 100 条;新增getOlderMessages(roomId, beforeId, limit = 30)方法ChatController.kt: 在
onMessage处理器中新增"load_history"类型 — 接收beforeId参数,返回WsEvent.History事件(附带hasMore标志)Models.kt:
WsEvent.History新增hasMore: Boolean字段
前端变更:
chat.js:
维护
this.oldestMessageId记录当前加载的最老消息ID和this.hasMoreHistory标志初始加载时记录最老消息的 ID
监听
messages-container的scroll事件 — 当scrollTop接近 0(如 < 50px)时触发加载更多加载时显示顶部 loading 指示器
发送
{ type: "load_history", beforeId: this.oldestMessageId }消息收到历史消息后,
prepend到消息容器顶部(保持当前滚动位置不变)处理
history事件时区分初始加载和追加加载增加
this.loadingHistory防抖标志防止重复请求
style.css: 顶部加载指示器样式
实施顺序建议
Phase 1 — Bug修复(优先):
Bug 1: 退出后重连问题
Bug 2: 用户列表不刷新
Bug 3: 文件名溢出
Phase 2 — 数据库迁移与基础设施: 4. 创建 V2 migration 脚本(一次性添加所有新列/表) 5. 更新 Models.kt 添加所有新字段
Phase 3 — 核心功能增强: 6. Enhancement 1: 历史消息滚动加载 7. Feature 4: Rich Text (Markdown) 8. Feature 5: Search 9. Feature 3: Message Editing/Deletion
Phase 4 — 社交功能: 10. Feature 2: User Mentions 11. Feature 8: Message Threading 12. Feature 1: Private Messages
Phase 5 — 用户管理: 13. Feature 6: User Profiles 14. Feature 7: Room Permissions
数据库 V2 Migration 脚本概要
SQL
-- V2__Add_features.sql
-- Message editing/deletion
ALTER TABLE messages ADD COLUMN edited_at TIMESTAMP;
ALTER TABLE messages ADD COLUMN is_deleted INTEGER DEFAULT 0;
-- Message threading
ALTER TABLE messages ADD COLUMN reply_to_id INTEGER REFERENCES messages(id);
-- User profiles
ALTER TABLE users ADD COLUMN bio TEXT;
ALTER TABLE users ADD COLUMN status TEXT;
-- Room permissions
ALTER TABLE room_members ADD COLUMN role TEXT DEFAULT 'member' CHECK(role IN ('owner', 'admin', 'moderator', 'member'));
-- Private messages
CREATE TABLE private_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sender_id INTEGER NOT NULL,
receiver_id INTEGER NOT NULL,
message_type TEXT NOT NULL CHECK(message_type IN ('text', 'image', 'file')),
content TEXT,
file_url TEXT,
file_name TEXT,
file_size INTEGER,
mime_type TEXT,
thumbnail_url TEXT,
is_read INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (receiver_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX idx_private_messages_participants ON private_messages(sender_id, receiver_id, created_at);
CREATE INDEX idx_private_messages_receiver ON private_messages(receiver_id, is_read);
CREATE INDEX idx_messages_reply_to ON messages(reply_to_id);新增 WebSocket 消息类型总览