bujinyuan
|
59f824ad27
|
feat(phase2e-2): 会议室主页与核心 UI 组件落地(Task 11)
新增页面:
- pages/meeting/room.vue:会议主容器,组装 VideoGrid/Toolbar/MemberPanel/InviteDialog
* 顶部条:标题/会议号/复制、连接状态 pill、网络徽标、计时器
* 主区:按成员数自适应网格渲染 VideoTile
* 底部:麦/摄/邀请/成员/聊天/挂断工具条
* 离会确认弹窗:主持人可选"结束会议 / 仅自己离开"
* onUnload 兜底调用 leave 避免脏状态残留
新增组件(components/meeting/):
- VideoTile.vue:原生 video/audio 挂载(H5)+ 名字/静音/主持人/说话者边框
- VideoGrid.vue:1/2/3-4/5-9/10+ 响应式 CSS Grid 布局
- MeetingToolbar.vue:6 按钮工具条,loading 态 + 徽标
- MemberPanel.vue:右侧滑入抽屉,主持人可转让/踢出
- InviteDialog.vue:居中弹窗,一键复制会议号/链接/邀请文案
- NetworkBadge.vue:4 格信号柱,level 0-4 分色
配套改动:
- preview.vue:入会成功后跳 /pages/meeting/room?code=xxx(原先指向 debug)
- pages.json:注册 room 路由,navigationStyle=custom 隐藏默认导航
- store/meeting.js:remoteConsumers 外层 slot 解除 markRaw,使
slot.audio/slot.video 赋值能驱动 VideoTile 响应式重渲染
验证:Playwright E2E 完成 create → preview → room 全链路
- 视频块、会议号、连接状态、计时器、工具条渲染正确
- 邀请弹窗、成员抽屉、静音切换、离会确认均工作正常
Made-with: Cursor
|
2026-04-22 14:41:27 +08:00 |
|
bujinyuan
|
eb3857a37a
|
feat(phase2e-2): 前端创建/加入/设备预览 3 页落地(Task 10)
页面
- pages/meeting/create.vue:创建表单,标题(默认"{昵称}的会议"/40 字限制) + 密码(4-16 位)
+ 入会静音开关 + 允许聊天开关。提交时表单暂存到 store.draftCreatePayload,跳 preview 页
- pages/meeting/join.vue:会议号输入(9 位纯数字/带连字符均可,自动格式化 XXX-XXX-XXX)
+ 可选密码。支持 URL query code/password 回填(邀请链接一键入会入口)
- pages/meeting/preview.vue:设备预览页
- navigator.mediaDevices.enumerateDevices() 列出摄像头/麦克风/扬声器
- getUserMedia({video,audio}) 本地画面预览(用原生 <video> 避开 uni-input 对 srcObject 的限制)
- AudioContext.AnalyserNode 以 rAF 采样 RMS,渲染渐变音量条
- 显示名输入 + 入会默认开麦/开摄像头开关
- 权限被拒/浏览器不支持 WebRTC 时给清晰错误提示
- 离开页时释放 preview stream 与 AudioContext,避免占用摄像头
Store 扩展(meeting.js)
- 新增 state:draftCreatePayload(create→preview 暂存) + devicePreview(设备选择结果)
- createAndEnter(payload, mediaPrefs?) / joinAndEnter(code, password, mediaPrefs?) 新增
可选 mediaPrefs 参数:{ startAudio, startVideo, audioDeviceId, videoDeviceId }
- _afterJoined 内按 mediaPrefs 入会后自动 startLocalAudio/Video(失败仅告警不阻断)
- startLocalAudio/Video 支持可选 deviceId,映射到 getUserMedia 的 deviceId.exact 约束
路由
- pages.json 注册 create/join/preview 3 条路由(均非 TabBar 页,从 URL 或后续入口跳入)
临时占位
- preview 页加入成功后暂时 redirectTo 到 debug.vue(Task 11 room 页待落地时改为 /pages/meeting/room)
Playwright 验证(真实 macOS 设备)
- create → preview:表单 "新昵称的会议" 正确携带到 preview 页
- preview 页:FaceTime 摄像头 + MacBook Pro 麦克风枚举成功,video readyState=4,音量条工作正常
- 点"加入会议":createAndEnter 成功,debug 页显示 localState=connected,主持人=是
mediaPrefs 生效——麦克风/摄像头按钮显示"关闭麦克风/关闭摄像头"说明自动推流成功
- join 页:URL ?code=123-456-789&password=secret 正确预填,"下一步" 按钮启用
Made-with: Cursor
|
2026-04-22 14:20:57 +08:00 |
|
bujinyuan
|
467135e0d5
|
feat(phase2e-2): WS 稳定性改进——重连后自动重绑定房间 + 多端登录抖动检测
背景
- Task 9 联调发现同账号多 tab 登录会触发后端单设备踢下线机制,WS 每秒被关闭一次,
所有 meeting.* 的 sendWithAck 都因 "连接已断开" 而失败,表现为"开启音视频提示失败"
- 同时 WS 断线重连后后端对该连接的 roomCode 绑定丢失,会议广播不再送达本端
改动
- store/meeting.js:_registerListeners 增加 WS 生命周期监听
- _disconnected:从 CONNECTED/CONNECTING 切到 RECONNECTING,UI 可展示"重连中…"
- _connected:在 RECONNECTING 状态自动重发 meeting.room.join 重绑定房间,并刷新参与者列表
- services/websocket.js:_recordCloseForFlapping 做 10s 滑窗 flapping 检测
- 窗口内累计 >=3 次非主动断连 → 触发 _flapping 事件(含 count/windowMs)
- 15s 冷却避免抖动期间重复告警
- debug.vue:新增 WS 状态指示灯(已连接/重连中/已断)+ flapping 警告横幅
- 订阅 _connected / _disconnected / _flapping 三个 WS 内置事件,onBeforeUnmount 注销
验证(Playwright)
- 单次断连:testuser1 创建会议后手动 close WS → 指示灯 reconnecting,1s 后重连
- 日志链 "WS 已断开 → 进入 reconnecting → 连接成功 → 重绑定房间 307-857-909 → 恢复 connected" 全通
- 4 次连续断连:第 3 次触发 flapping,UI 横幅 "⚠ 检测到异常重连(3 次/10s),可能同账号在其他端登录"
- 每次断连后房号、主持人状态、localState 均正确恢复为 connected
Made-with: Cursor
|
2026-04-22 14:08:07 +08:00 |
|
bujinyuan
|
807e111cb9
|
chore(scripts): start/stop/status 接入 media-server(port 3300)
- start.sh:新增 start_media(),端口 3300,缺 .env 时自动从 .env.example 复制;
缺 node_modules 时给出提示不跑命令;加入 all/独立子命令
- stop.sh:新增 stop_media(),通过 PID 文件 + 端口兜底停止媒体服务器
- status.sh:状态面板增加 "媒体服务器 SFU (:3300)" 行
- 完整用法:./scripts/start.sh [all|backend|frontend|admin|media|docker|--no-docker]
Made-with: Cursor
|
2026-04-22 11:27:21 +08:00 |
|
bujinyuan
|
f97fec24a8
|
feat(phase2e-2): 前端 mediasoup-client 集成 + Pinia meeting Store(Task 9)
- frontend/src/api/meeting.js:12 个 REST 接口封装,统一 unwrap envelope.data
- frontend/src/services/websocket.js:新增 sendWithAck(Promise 化 + 超时 + 序列号)
- frontend/src/utils/mediasoup-client.js:MediaEngine 包装 Device/Transport/Producer/Consumer
- frontend/src/store/meeting.js:Pinia 会议状态机,桥接 14 个 WS 事件 + cleanupStaleMeetings
- frontend/src/constants/meeting.js:状态枚举 + 事件名集中管理
- frontend/src/pages/meeting/debug.vue:临时调试页(H5 原生 video/audio DOM 绕过 uni 组件限制)
- backend:meeting.consume.resume WS 事件 + create/join 响应透传 router_id + rtp_capabilities
- 文档:frontend/meeting.md、websocket.md、CURRENT_STATUS、plan 全部同步 Task 9 落地
Made-with: Cursor
|
2026-04-22 11:22:49 +08:00 |
|
bujinyuan
|
e7f6a32dfe
|
docs(phase2e-2): 归档 Task 8 实施 plan 文件至 docs/plans
将 Cursor Plan 模式下的临时产物 task_8_会议生命周期状态机_3a0bc37b.plan.md
归档至 docs/plans/ 目录,统一命名风格为日期前缀 + 阶段/任务描述:
2026-04-21-phase2e-2-task8-lifecycle.plan.md
用途:
- 与 docs/plans/2026-04-21-phase2e-2-{design,implementation.plan}.md 共同构成
Phase 2e-2 Task 8 的完整设计 → 实施 → 归档三件套
- 保留 15 个 todo 的详细拆分 + 参考附录,作为后续复杂 Task 的实施样本
- 头部补充 archived_at / archived_note,指向实际交付结果与 commit
不改动文件主体内容,仅追加归档元数据。
Made-with: Cursor
|
2026-04-21 18:24:49 +08:00 |
|
bujinyuan
|
3b83c79036
|
feat(phase2e-2): 落地会议生命周期状态机 + Router 幂等双层防御(Task 8)
核心交付:
- 新建 MeetingLifecycleService(6 钩子 + sync.Map 本地 timer + Redis key 双保险 + RescheduleFromRedis)
- 新建 MeetingCleanupTask(启动重建 timer + 每 N 秒扫 host_grace/empty_ttl 兜底 + 4h stale active 回收)
- MediaOrchestrator 新增 ResolveRouterID;HTTPMediaOrchestrator.CreateRouter 入口 sync.Map 幂等防御
- 业务层 JoinRoom 移除 CreateRouter 调用改走 CancelEmptyTTL + ResolveRouterID;LeaveRoom 空房分支改调 OnAllMembersLeft 不再立即销毁
- MeetingSignalService 新增 OnWSDisconnect 实现 ws.MeetingDisconnectHook;OnRoomJoin 追加 host 重连钩子
- ws.handler 定义 MeetingDisconnectHook 接口 + SetMeetingDisconnectHook,解耦 ws→meeting 反向依赖
- config 新增 MeetingConfig{HostGrace=120, EmptyRoomTTL=300, CleanupInterval=30, StaleRoomHours=4}
关键设计决策:
- Redis key TTL = 业务时长 + max(CleanupIntervalSeconds*2, 30s) buffer:避免本地 timer 与
Redis 自动过期同步到期导致 DEL 返回 0 被误判为"已被其他路径处理"而跳过业务逻辑
- Router 幂等双层防御(决策 q2_router_dedup=a2_both):业务层不重复调 + HTTP 层 sync.Map 命中直接返回
- 普通成员 WS 断开仅清 media 资源不动 participant 表(决策 q1_nonhost_disconnect=a1_keep_current)
E2E 验证:docs/verify/meeting_t8_verify.mjs PASS=20 FAIL=0,覆盖 5 场景:
- S1 host 宽限期过期自动转让(meeting.host.changed + DB host_id 更新)
- S2 宽限期内重连保留身份
- S3 empty_ttl 期内新成员加入复活房间
- S4 empty_ttl 过期 → 房间 Ended + 新 join 被拒
- S5 CreateRoom +1 Router / JoinRoom 不再创建新 Router(通过 media-server /internal/info stats.routers 断言)
media-server:/internal/info 响应追加 stats.routers + routers[] 供 E2E 断言 Router 幂等
文档同步:
- docs/progress/CURRENT_STATUS.md 头部 + 新增 Task 8 交付条目
- docs/plans/2026-04-21-phase2e-2-implementation.plan.md Task 8 标记完成 + 实际产出/决策/验证
- docs/api/frontend/meeting.md 补充 host.changed.auto_reason / room.ended.reason=system_error / 空房 TTL 复活语义 + Task 8 验证记录
- docs/architecture/system-architecture.md meeting 模块职责补充"会议生命周期状态机"
- .cursor/rules/project-context.mdc 追加 Task 8 条目并更新 Phase 2e-2 进度(Task 0-8 ✅)
Made-with: Cursor
|
2026-04-21 18:21:04 +08:00 |
|
bujinyuan
|
55d6352c6a
|
feat(phase2e-2): 落地 HTTPMediaOrchestrator 打通 Go↔Node 媒体链路(Task 7)
- 新增 MediaServerConfig + config.{dev,docker}.yaml 的 media_server 段
(base_url / internal_token / timeout_ms / close_timeout_ms / close_retry)
- 新建 HTTPMediaOrchestrator(8 方法 + sync.Map 缓存 roomCode→routerID
+ 关闭类指数退避重试 200/500ms + ErrMediaResourceNotFound/ErrMediaServerError
两类错误)实现 MediaOrchestrator 接口
- wire 绑定由 NoopMediaOrchestrator 切换到 HTTPMediaOrchestrator;
MeetingService / MeetingSignalService 调用侧零改动
- E2E 脚本 docs/verify/meeting_t7_verify.mjs 证明 16/16 PASS:
健康检查/错token 401/REST 创房加入/WS room.join/真实 mediasoup
transport.create(ICE/DTLS 指纹均由 Node 返回非占位)/404 幂等关 producer
/host 结束会议触发 CloseRouter
- 同步更新 13 份文档:CURRENT_STATUS / implementation.plan / design
变更记录 / project-context / api 导览 / api websocket / api frontend meeting
/ system-architecture / media-server README / 顶层 README 等
- go build ./... 全绿
Made-with: Cursor
|
2026-04-21 17:47:11 +08:00 |
|
bujinyuan
|
dfd9b6011c
|
docs(phase2e-2): 同步 Task 5/6 落地到总 API 索引 + 总 WS 协议 + 设计文档变更记录
- docs/api/README.md:frontend/meeting.md 状态从"📋 设计阶段"升为"✅ Task 5/6",
补全 12 REST + 13 WS 事件描述;目录结构表 meeting.md/notify.md 同步标记完成
- docs/api/websocket.md:会议信令章节从设计阶段草稿重写为事件总览表 + SSOT 引用
(移除与 Task 6 实际实现不一致的旧事件名 meeting.member.mute/video/produce.stop
/room.info/consume.resume;明确 C→S 白名单与 ACK/资源追踪约定)
- docs/plans/2026-04-21-phase2e-2-design.md:§十六 变更记录追加 Task 5 + Task 6
两条偏离说明,完整记录事件合并/重命名/白名单/Redis 资源追踪等实施级决策
Made-with: Cursor
|
2026-04-21 17:14:14 +08:00 |
|
bujinyuan
|
096563d3ea
|
feat(phase2e-2): WebSocket 信令协议 13 事件全量落地(Task 6)
将 meeting.* 事件族从 Task 5 的 PublishToUser 循环升级为完整 WS 信令协议:
抽离统一广播层、扩容 MediaOrchestrator 接口、实现 8 个 C→S 事件业务逻辑
+ Redis 资源追踪 + host 权限校验,端到端 18/18 PASS。
核心产出:
- 新建 MeetingBroadcaster(统一广播层,REST/WS 共用)
- 新建 MeetingSignalService 8 C→S 事件 + cleanupUserResources
- 新建 MeetingWSHandler 薄层 controller
- MediaOrchestrator 扩容 9 方法 + NoopMediaOrchestrator 占位(Task 7 替换)
- C→S 白名单机制防恶意伪造广播事件
- Redis Set 资源追踪防 mediasoup 端资源泄漏
文档同步:
- docs/api/frontend/meeting.md 追加 §WebSocket 信令协议(Task 6)200 行
- docs/progress/CURRENT_STATUS.md + project-context.mdc + 实施计划 Task 6 ✅
Made-with: Cursor
|
2026-04-21 17:10:32 +08:00 |
|
bujinyuan
|
e235e001b3
|
feat(phase2e-2): Go meeting 模块 12 个 REST 接口业务逻辑全量落地(Task 5)
主要产出:
- DTO 层:app/dto/meeting_dto.go 完整定义 13 个 DTO(请求/响应/基础共用)
- Service 层:MeetingService 12 业务方法 + 11 个 sentinel 错误 + 4 辅助
- CreateRoom/JoinRoom/LeaveRoom/EndRoom 核心生命周期
- KickMember/TransferHost/GetRoom/ListMyMeetings 会议管理
- InviteUsers(+ NotifyPusher)/ RedeemInviteToken 邀请链路
- SendChat/ListChats 会议内聊天
- Controller 层:12 handler + handleError 领域错误 → HTTP 映射
- 工具层:pkg/utils/meeting_code.go(XXX-XXX-XXX + invite token)
- Stub 接口:MediaOrchestrator(Task 7 替换)+ NoopMediaOrchestrator
- 路径修正:/rooms → /rooms/mine、/invites → /invite-tokens 对齐设计
- DAO 契约修复:GetByID/GetByCode/GetByRoomAndUser/FindActiveByUser
将 gorm.ErrRecordNotFound 转为 (nil, nil),service 统一 nil 判定
- 安全强化:密码 bcrypt + 5 次错误锁 10 分钟;邀请 token 仅通过
NotifyPusher.Extra 定向下发,响应不回传
- host 自动转让:host 离会时将最早加入者提升为 host
- 单点参会:用户同一时间仅能在一个活跃会议
- API 文档:docs/api/frontend/meeting.md 重写为 12 接口完整规范
- Wire 依赖注入:MediaOrchestrator + NoopMediaOrchestrator provider
验证:
- go build / go vet / wire 零告警
- 端到端 3 用户场景:12 happy path + 5 错误路径 PASS=19 / FAIL=0
覆盖密码错/房间不存在/单点冲突/越权/邀请失效
文档同步:
- docs/progress/CURRENT_STATUS.md 增补 Task 5 章节
- .cursor/rules/project-context.mdc 更新阶段状态
- docs/plans/2026-04-21-phase2e-2-implementation.plan.md 标记 T5 ✅
下一步:Task 6(WS 信令协议 + BroadcastToMeeting 替换 PublishToUser 循环)
Made-with: Cursor
|
2026-04-21 16:39:59 +08:00 |
|
bujinyuan
|
d22f61fb9b
|
feat(phase2e-2): Go meeting 模块 DDL + Model + DAO + service/controller/router 骨架(Task 3+4)
Task 3 - 数据库与持久化层:
- init.sql 追加 3 张表(meeting_rooms / meeting_participants / meeting_chats)+ 9 索引 + 全量 COMMENT
- 新增 phase2e2_migration.sql 增量升级脚本(IF NOT EXISTS 幂等)
- app/constants/meeting.go 统一会议常量(类型/状态/角色/结束原因/离会原因/默认配置/WS 事件 8 组)
- app/meeting/model 3 个 model + GORM tag(TIMESTAMP(0) 对齐现有表风格)
- app/meeting/dao 3 个 DAO 共 24 个方法:
* meeting_room_dao:Create/Get/Exists/MarkStarted/MarkEnded(乐观锁)/UpdateHost/UpdateSettings/ListByHost/ListExpiredForCleanup
* meeting_participant_dao:JoinRoom(事务内复用旧记录)/LeaveRoom(EXTRACT(EPOCH) DB 时间)/LeaveAllActive/TransferHost(事务)/FindActiveByUser(JOIN 单点校验)/列表计数更新
* meeting_chat_dao:Create/ListByRoom(游标)/DeleteByRoomIDs/CountByRoom
- psql 真库跑通 CRUD + UNIQUE + CASCADE + 主持人转让事务 + duration=10s 精确匹配 8 场景
Task 4 - Go 侧 service/controller/router/wire 骨架:
- app/meeting/service/interfaces.go:NotifyPusher / UserInfoResolver / OnlineChecker 三接口(解耦 notify/contact/ws)
- app/meeting/service/meeting_service.go:MeetingService + 8 sentinel error + 17 个空方法(返回 ErrNotImplemented)
- app/meeting/controller/meeting_controller.go:12 个 Gin handler + responseNotImplemented(501)
- app/meeting/router.go:12 条路由挂 /api/v1/meeting/* 并套 jwtAuth(扁平化 router.go,与 group/notify 一致)
- app/meeting/provider.go:MeetingSet = wire.NewSet(DAO×3, Service, Controller)
- 全局 wire.go 挂入 MeetingSet + 3 条 wire.Bind(Notify/UserInfo/Online);App struct/NewApp 加字段;router/router.go 挂载
- 存量修复:admin/provider.go 补齐 MessageManage{DAO,Service,Controller} 解决旧版 wire 重生成报 no provider found
- 验证:go build ./... / go vet ./... / wire ./app/provider 零错误;GIN_MODE=debug 启动打印全部 12 条路由;无 token curl 3 条代表性路由均返回 401(JWT 中间件生效)
关键风格修正(偏离草案):
- 常量归入 app/constants/meeting.go 单文件(与 group.go / notify.go 同构),非草案 app/meeting/constants/
- TIMESTAMP(0) 取代草案 TIMESTAMPTZ 对齐所有现有表
- 冗余 idx_meeting_rooms_code 移除(UNIQUE 已自动建索引)
- router/provider 目录扁平化(app/meeting/router.go / app/meeting/provider.go)
- 延续项目 Go 侧"零 _test.go"风格,用代码审查 + psql 真库验证 + Playwright E2E 三层守护
文档同步:
- docs/progress/CURRENT_STATUS.md 新增 Task 3 / Task 4 交付段落
- .cursor/rules/project-context.mdc Phase 2e-2 进度推进至 Task 0-4 ✅
- docs/plans/2026-04-21-phase2e-2-implementation.plan.md Task 3 / Task 4 标记完成 + 实际产出 vs 计划差异
Made-with: Cursor
|
2026-04-21 16:04:57 +08:00 |
|
bujinyuan
|
6e2e792db0
|
feat(phase2e-2): media-server Task 0/1/2 落地 + 代码审查修复
本次提交一次性落盘 Phase 2e-2 的 Task 0(PoC Spike)、Task 1(骨架 + Fastify 5 升级)
和 Task 2(9 个内部 REST API + code-reviewer 审查修复),覆盖 media-server 子项目
从零到可用的全部工作。
【Task 0 - PoC Spike】
- media-server/poc/:Node + mediasoup + fastify-websocket + 前端 mediasoup-client
- Playwright 双 tab 自动化验证 2 人会议:4 transports / 4 producers / 4 consumers / RSS 61MB
- media-server/docs/poc-notes.md 归档 7 项关键坑 + 启动步骤 + Task 1/2/9 复用映射
- 锁定技术栈:mediasoup + fastify + mediasoup-client,不改用 livekit-server
【Task 1 - 骨架 + Fastify 5 升级】
- 依赖版本:mediasoup@3.19.0 + fastify@5.8.5 + fastify-plugin@5.1.0
+ @fastify/sensible@6.0.4 + @fastify/websocket@11.2.0
+ pino@9.3.2 + zod@3.23.8
- src 五件套:app.ts / config.ts / utils/logger.ts / mediasoup/worker.ts
/ middlewares/internal-auth.ts
- /healthz + /readyz + /internal/info 三端点实测通过
- X-Internal-Token 鉴权用 timingSafeEqual 防侧信道
- mediasoup Worker died 指数退避自愈通过 kill -9 验证
- 多阶段 Dockerfile:非 root + curl HEALTHCHECK + 暴露 40000-40199 UDP/TCP
- Fastify 4 → 5 升级:loggerInstance: logger 消灭两个 pino 实例
【Task 2 - 9 个内部 REST API + 审查修复】
接口全部挂 /internal/v1/* 前缀,覆盖 Router/Transport/Producer/Consumer 完整生命周期:
- POST /routers, DELETE /routers/:id
- POST /transports, POST /transports/:id/connect
- POST /producers, DELETE /producers/:id
- POST /consumers, POST /consumers/:id/resume, DELETE /consumers/:id
工程特性:
- zod 手动 parse + 全局 errorHandler(不引入 fastify-type-provider-zod 避免
zod v4 依赖冲突)
- AppError 统一错误码(NOT_FOUND/CONFLICT/CAN_NOT_CONSUME/ROUTER_LIMIT_EXCEEDED
/MEDIASOUP_ERROR/VALIDATION_ERROR/UNAUTHORIZED/INTERNAL_ERROR)
- 所有资源用 Map + observer.once('close') 自清理,Consumer 监听 producerclose 级联关闭
- Consumer 强制 paused:true 创建,/resume 独立接口
- Transport direction 强约束:recv 拒 produce、send 拒 consume
code-reviewer 子代理审查"有条件通过",同步修复:
- M1 _clearXxxMap 新增 src/utils/test-guard.ts#assertTestOnly 守卫(生产误调用抛错)
- M2 新增 src/schemas/rtp.ts 对 rtpParameters / rtpCapabilities 做 codecs 浅层校验
(mimeType/clockRate/payloadType 必填、codecs ≥1),消除 as unknown as 双跳断言
- m1 connectTransport 改乐观锁:先置位再 await,失败回退
- m2 改读 consumer.producerPaused(更符合 mediasoup 语义)
- m3 producerclose 改为 once(风格一致)
- m5 internal-auth 改为反向白名单 PRIVATE_PATH_PREFIXES = ['/internal/']
验证:
- typecheck / lint 0 错误
- vitest:65 passed / 8 spec 文件
- 覆盖率 stmts 82.87% / branches 75.83% / funcs 91.3% / lines 82.87%
- 9 接口 happy path + 6 类错误路径 curl 手测全部按预期返回
【文档同步】
- CURRENT_STATUS.md +232 行:新增 Task 0/1/2 完整记录 + 代码审查修复章节 + 延后清单
- project-context.mdc:Task 0/1/2 状态同步
- phase2e-2-design.md +20 行:Fastify 5 升级相关决策记录
- phase2e-2-implementation.plan.md +117 行:Task 0/1/2 实际产出 + 修复记录
余下 Minor/Nits(m4/m6~m10/n1~n10)登记至 Task 16 收尾清单一次性清扫。
Made-with: Cursor
|
2026-04-21 15:31:01 +08:00 |
|
bujinyuan
|
df538e8df6
|
feat:更新---2e-2阶段相关设计文档和实施步骤文档产出
|
2026-04-21 13:53:29 +08:00 |
|
bujinyuan
|
f1853f125d
|
feat: Phase 2e-1 统一通知中心 + 我的 TabBar 聚合未读红点
后端(notify 模块)
- 新增 notify 模块:DAO/Service/Pusher 接口/Controller/Router/CleanupTask
- 数据库 DDL:notify_notifications 表 + 3 索引(user+created/user+is_read/user+category)
- 11 种 type 枚举(好友/群聊 9 种 + meeting_* 2 种预留)+ 4 种 category
- 跨模块集成:contact 3 处 Pusher(friend_request/accepted/rejected)
- 跨模块集成:group 6 处 Pusher(invite/join_request/approved/rejected/kicked/role_changed)
- WS handler 断线补偿:连接建立即推送 notify.unread.total
- 5 REST API(4 用户 + 1 管理员广播)+ 2 WS 事件(notify.new / notify.unread.total)
- 30 天已读通知定时清理(未读永久保留)
- Provider/Wire 依赖注入(NotifyPusher、NotifyConnectHook、UserInfoResolver 接口)
前端
- 新增 notify 模块:API/Pinia Store(5 分类分页缓存 + 未读数 + WS 事件)/NotifyItem/通知中心主页
- profile 入口:铃铛 badge + 菜单项 badge + 数字显示
- App.vue/login 初始化 notifyStore WS 监听;logout 调用 notifyStore.reset() 清缓存
- 清理 contact.js/group.js 中散落 toast 与冗余 notify.friend.request/group.join.request 处理
- CustomTabBar 新增 hasDot() 聚合指示器:我的 Tab 显示纯红点(无数字),
当前聚合 notifyStore.unreadTotal,未来可扩展「资料待完善/安全提醒/新版本」等
文档
- 新增 Phase 2e 整体路线图 docs/plans/2026-04-20-phase2e-design.md
- 新增 Phase 2e-1 专用设计 docs/plans/2026-04-20-phase2e-1-design.md(§6.4 TabBar 聚合红点)
- 新增 Phase 2e-1 实施计划 docs/plans/2026-04-20-phase2e-1-implementation.plan.md
- 新增 E2E 验证报告 test-report-phase2e-1-notification.md(含 Playwright MCP 2 个现场 Bug 修复记录)
- 更新 docs/progress/CURRENT_STATUS.md、docs/api/README.md、docs/api/frontend/notify.md
- 更新 .cursor/rules/project-context.mdc、docs/plans/2026-02-27-echochat-system-design.md
其他
- .gitignore 排除 .playwright-mcp/ MCP 临时快照
架构决策
- 单端 WS 连接:沿用现有 ws.Hub,多端已读同步推迟到 Phase 2f/二期
- 跨模块依赖:contact/group → notify 严格单向(接口注入模式)
- 降级策略:Pusher 先入库后推送;WS 失败不回滚入库;入库失败仅 Warn 不影响业务
Playwright MCP 回归(4 类场景全通)
- 实时推送(admin 广播 → 1s 内前端自动插入 + 角标 +1)
- Deep-link 跳转(好友申请通知 → contact/request 页)
- 批量清零(全部已读按钮)
- TabBar 聚合红点(有未读亮/全部已读灭)与 notifyStore.unreadTotal 三层同步
Made-with: Cursor
|
2026-04-21 10:21:01 +08:00 |
|
bujinyuan
|
ccfceefdaf
|
feat:聊天页面相关功能优化,提供用户体验感
|
2026-04-20 15:40:16 +08:00 |
|
bujinyuan
|
0c1540bdee
|
feat:聊天信息支持 文件、图片、语音发送,增加一键启停脚本
|
2026-04-20 11:53:27 +08:00 |
|
bujinyuan
|
1a5aac9998
|
feat:修改地址配置
|
2026-03-13 16:34:28 +08:00 |
|
bujinyuan
|
8ee870636c
|
feat:群聊问题修复+相关项目文档更新
|
2026-03-05 09:24:49 +08:00 |
|
bujinyuan
|
4f55ef6de0
|
feat:代码推送测试
|
2026-03-04 15:07:31 +08:00 |
|
bujinyuan
|
19979da59e
|
feat(phase2c): 群聊与已读回执功能完整实现
Phase 2c 全部 14 个 Task 完成,包含:
后端(Go):
- MinIO 文件存储服务集成(Docker + Go SDK + 通用上传 API)
- Group 模块完整实现(DAO + Service + Controller + Router + Wire)
- 18 个群管理 REST API + 11 个 WS 群事件推送
- 群创建/解散/邀请/踢人/退出/转让群主/设管理员/禁言/全体禁言/群公告/群昵称/免打扰/搜索
- IM Service 扩展(群消息发送/撤回 + @提醒 + 管理员无时限撤回)
- 已读回执后端(单聊会话级 last_read_msg_id + 群聊消息级 im_message_reads)
- 管理端群组管理(列表/详情/解散)
- 数据库迁移(3 张新表 + 2 张表字段扩展)
前端(uni-app):
- 群聊 Store + API 封装 + 11 个 WS 事件监听
- 已读回执 UI(单聊已读/未读标记 + 群聊 X人已读 + 已读详情页)
- 7 个群聊页面(对话/创建/设置/成员/邀请/审批/搜索)
- 会话列表改造(全部/单聊/群聊 Tab + @标记 + 免打扰标识)
管理端(Vue 3 + Element Plus):
- 群组列表页(搜索/分页/详情弹窗/解散群聊)
- 侧边栏群组管理入口
文档同步:进度/架构/设计/API/规范文档全部更新
Made-with: Cursor
|
2026-03-04 14:58:23 +08:00 |
|
bujinyuan
|
ad76d9a613
|
docs: 修正分支状态标记(待创建→已创建)
Made-with: Cursor
|
2026-03-04 10:53:48 +08:00 |
|
bujinyuan
|
1d9f7a7432
|
docs: 新增后端架构规范文档 + 拆分集成规范
- 新增 backend-module-architecture.md(10 章:模块分层/接口注入/Wire/日志/错误处理/批量查询/系统消息/WS 推送/Store 封装)
- 从 frontend-backend-integration.md 拆分(621→360 行),符合 ≤500 行规范
- project-context.mdc 新增规则 #10 引用后端架构规范
Made-with: Cursor
|
2026-03-04 10:51:38 +08:00 |
|
bujinyuan
|
6568565c37
|
docs: 项目规则新增文档同步流程规范
- 新增「每阶段开始前的文档准备」清单(8 项检查)
- 新增「每个 Task 完成后的文档同步」清单(6 项检查)
- 补充「每阶段结束时的额外检查」新增集成规范时间戳更新
Made-with: Cursor
|
2026-03-04 10:46:08 +08:00 |
|
bujinyuan
|
13d26ca43a
|
docs: Phase 2c 规划同步到全局文档
- system-architecture.md: 新增 group/file 模块,admin 模块扩展群聊管理
- echochat-system-design.md: Phase 2c 开发分期详情 + 19 个新 API + 4 个管理端 API
- api/README.md: 新增 group.md 导航(前台+管理端)
- frontend-backend-integration.md: 更新最后修改时间
Made-with: Cursor
|
2026-03-04 10:43:33 +08:00 |
|
bujinyuan
|
cc09b192a4
|
docs: Phase 2c 设计文档补充 — @计数存储/系统消息格式/离线推送/错误常量
- 新增 at_me_count 字段设计(im_conversation_members)
- 明确系统消息 content 格式为纯文本 + 12 种消息模板
- 群聊离线推送策略(沿用 Phase 2b OfflineMessagePusher)
- 文件上传策略(两步解耦,无需接口注入)
- 群聊错误常量规划(15 个业务错误)
Made-with: Cursor
|
2026-03-04 10:37:48 +08:00 |
|
bujinyuan
|
86f371cc5c
|
docs: Phase 2c 设计文档 + 实施计划 + 项目进度更新
- 新增 Phase 2c 设计文档(群聊与已读回执)
- 新增 Phase 2c 实施计划(14 个 Task)
- 更新 CURRENT_STATUS.md 反映 Phase 2c 规划
- 更新 project-context.mdc 新增 Phase 2c 进度和模块信息
Made-with: Cursor
|
2026-03-04 10:34:35 +08:00 |
|
bujinyuan
|
9399f86a19
|
feat:单聊页面bug修复+后台管理端好友页面bug修复+项目进度、记忆文件更新
|
2026-03-04 10:03:16 +08:00 |
|
bujinyuan
|
7b1d061955
|
feat:修复聊天界面消息位置展示错误问题
|
2026-03-03 13:46:03 +08:00 |
|
bujinyuan
|
35788f6d2f
|
style(frontend): ui-ux-pro-max 规范改造 — chat 页面 + CustomTabBar
改造内容:
- 安装 @dcloudio/uni-ui + sass,配置 easycom 自动引入 uni-icons
- 4 个 chat 页面:emoji/HTML 实体 → uni-icons 组件(跨平台兼容)
- CustomTabBar:emoji → uni-icons(chatbubble/contact/videocam/person)
- 选中态使用 filled 变体,未选中态使用轮廓图标
- 所有可交互元素添加 :active 按压反馈 + transition (150-200ms)
- 触控目标保证 ≥ 88rpx (44px)
- input-bar/tab-bar 适配 safe-area-inset-bottom
设计规范文件:
- MASTER.md 修正为项目蓝色色板 (#2563EB) + 移动聊天应用规范
- 新增 4 个页面覆盖规范:chat-index/conversation/settings/search.md
- Pre-Delivery Checklist 全部通过
- npm run build:h5 编译验证通过
Made-with: Cursor
|
2026-03-03 11:23:11 +08:00 |
|
bujinyuan
|
143cb8aa5f
|
docs: Task 9 - Phase 2b 文档更新
- CURRENT_STATUS.md: 更新 Phase 2b 完成状态(10 个 Task)
- 新增功能描述(IM/会话/WS事件/REST API/前端页面/数据库)
- 更新目录结构、技术决策、测试指南
- 下一阶段规划更新为 Phase 2c
- project-context.mdc: 更新当前进度和模块列表
Made-with: Cursor
|
2026-03-03 11:04:19 +08:00 |
|
bujinyuan
|
0ea8c0d331
|
feat(frontend): Task 8 - 聊天设置页 + 消息搜索页 + 联系人改造
pages/chat/settings.vue(聊天设置页):
- 对方信息展示(头像+昵称)
- 置顶/取消置顶切换
- 清空聊天记录(确认弹窗)
- 删除会话(确认弹窗 + 双层返回)
pages/chat/search.vue(消息搜索页):
- 全局消息搜索(关键词匹配)
- 搜索结果列表(发送者头像+昵称+内容+时间)
- 点击跳转到对应会话
联系人模块改造:
- contact/detail.vue "发消息"按钮改为跳转聊天页
Made-with: Cursor
|
2026-03-03 11:02:30 +08:00 |
|
bujinyuan
|
565afb3149
|
feat(frontend): Task 7 - 会话列表页 + 聊天对话页
pages/chat/index.vue(会话列表页):
- 排序展示会话列表(置顶优先 → 时间降序)
- 未读 badge + 时间格式化(今天/昨天/日期)
- 长按操作菜单(置顶/删除)
- 正在输入状态提示
- 空状态 UI
pages/chat/conversation.vue(聊天页):
- 消息气泡(自己蓝色/对方白色)
- 游标分页加载历史消息
- 发送消息 + 三态确认(sending/sent/failed)
- 长按撤回 + 重发失败消息
- 正在输入通知(发送 + 接收)
- 自定义导航栏 + 设置入口
pages.json:
- 新增 chat/conversation、chat/settings、chat/search 路由
Made-with: Cursor
|
2026-03-03 11:01:15 +08:00 |
|
bujinyuan
|
780dea3c31
|
feat(frontend): Task 6 - Chat Store + API + WS 事件 + TabBar badge
API (api/im.js):
- getConversations / getHistoryMessages / pinConversation
- deleteConversation / clearHistory / searchMessages / getTotalUnread
Chat Store (store/chat.js):
- 会话列表管理 + 排序(置顶优先)
- 消息缓存 + 三态确认(sending/sent/failed)
- 全局未读数管理(Redis 同步)
- WS 事件监听:im.message.new/recalled/send.ack/offline.sync/typing
- 工具方法:游标分页加载、typing 超时清除
CustomTabBar:
- 消息 Tab 显示全局未读 badge(>99 显示 99+)
Made-with: Cursor
|
2026-03-03 10:58:35 +08:00 |
|
bujinyuan
|
2c7be494f2
|
feat(im): Task 5 - IM REST Controller + Router + Wire 集成
IMController (REST API):
- GET /api/v1/im/conversations: 会话列表
- GET /api/v1/im/messages: 历史消息(游标分页)
- PUT /api/v1/im/conversations/:id/pin: 置顶/取消
- DELETE /api/v1/im/conversations/🆔 删除会话
- DELETE /api/v1/im/conversations/:id/messages: 清空记录
- GET /api/v1/im/messages/search: 全局搜索
- GET /api/v1/im/unread: 全局未读数
Wire 集成:
- IMSet Provider Set + wire.Bind 接口注入
- App struct 新增 IM 字段
- wire_gen.go 手动更新
- OfflinePusher 通过 SetOfflinePusher 注入 WS Handler
Made-with: Cursor
|
2026-03-03 10:51:56 +08:00 |
|
bujinyuan
|
1e0c061e9d
|
feat(im): Task 4 - WS 事件处理器 + 离线消息推送
EventHandler:
- im.message.send: 发送消息(解析→业务→ACK+推送)
- im.message.recall: 撤回消息
- im.conversation.read: 标记已读
- im.typing: 正在输入通知(转发对方)
- 所有事件通过 Hub.RegisterEvent 注册到路由表
OfflinePusher:
- PushOfflineMessages: 推送未读会话摘要 + 全局未读总数
- 通过 OfflineMessagePusher 接口注入 WS Handler
- WebSocket 连接建立后自动触发离线推送
Made-with: Cursor
|
2026-03-03 10:48:36 +08:00 |
|
bujinyuan
|
334d3d860b
|
feat(im): Task 3 - IM Service 核心业务 + DTO + 接口定义
IMService 核心方法:
- SendMessage: 发送消息(好友校验→查找/创建会话→幂等去重→写入→推送)
- RecallMessage: 2分钟撤回
- GetConversationList: 会话列表(含对方用户信息)
- GetHistoryMessages: 游标分页历史消息
- MarkRead / PinConversation / DeleteConversation / ClearHistory
- SearchMessages: 全局消息搜索
- GetTotalUnread: Redis 全局未读总数
DTO:
- SendMessageRequest / RecallMessageRequest
- ConversationDTO / HistoryMessageResponse
- SearchMessageRequest / MarkReadRequest / TypingRequest
接口:
- FriendChecker: 好友校验(contact 模块注入)
- UserInfoGetter: 用户信息批量查询(contact 模块注入)
Made-with: Cursor
|
2026-03-03 10:46:57 +08:00 |
|
bujinyuan
|
e091fbccb3
|
feat(im): Task 2 - IM DAO 层(会话 + 消息)
ConversationDAO:
- FindPrivateConversation: 单聊会话去重查询
- CreateWithMembers: 事务创建会话+成员
- GetUserConversations: 会话列表(含未读/置顶)
- UpdateLastMessage / IncrementUnread / ClearUnread
- SoftDeleteMember / RestoreMember / UpdateMemberPinned
- GetUnreadConversations: 离线消息推送用
MessageDAO:
- Create / GetByID / GetByConversation(游标分页)
- UpdateStatus(撤回) / DeleteByConversation(清空)
- SearchMessages(全局搜索,JOIN 权限校验)
- FindByClientMsgID(幂等去重)
Made-with: Cursor
|
2026-03-03 10:44:34 +08:00 |
|
bujinyuan
|
c23b9087b8
|
feat(ws): Task 1 - Hub 事件路由表机制
- Hub 新增 eventHandlers map + RegisterEvent / DispatchEvent 方法
- handler.go onMessage 优先查路由表,未命中走内置 fallback
- default 分支改为返回"未知事件"错误响应 + 警告日志
Made-with: Cursor
|
2026-03-03 10:43:01 +08:00 |
|
bujinyuan
|
6ab04fb902
|
feat(im): Task 0 - IM 模型定义 + 数据库表迁移 + 常量定义
- 新增 3 个 GORM 模型:Conversation / ConversationMember / Message
- 新增 IM 常量:会话类型、消息类型、消息状态、撤回时限
- main.go 添加 IM 表 AutoMigrate
- init.sql 追加 IM 模块 DDL(含索引和全文搜索 GIN 索引)
Made-with: Cursor
|
2026-03-03 10:41:42 +08:00 |
|
bujinyuan
|
1f390fce56
|
docs: 补充 Phase 2b 设计文档和实施计划
- 设计文档新增 Mermaid 消息收发时序图
- IMService 依赖注入补充 *redis.Client(未读数 HASH 操作)
- 实施计划同步更新 Redis 依赖和 unreadKeyPrefix
Made-with: Cursor
|
2026-03-03 10:28:46 +08:00 |
|
bujinyuan
|
de9f9c1ee1
|
docs: Phase 2b 详细实施计划(10 个 Task)
覆盖后端 IM 模块(model/dao/service/controller/handler)、
WS 事件路由表扩展、前端 4 页面 + Store + API、联系人联动改造。
Made-with: Cursor
|
2026-03-03 10:24:01 +08:00 |
|
bujinyuan
|
2eeb8484fa
|
docs: Phase 2b 即时通讯消息系统设计文档
单聊核心设计:全双工 WS 消息收发、会话管理、离线消息推送、
消息撤回、正在输入、全局搜索、4 个前端页面。
Made-with: Cursor
|
2026-03-03 10:19:55 +08:00 |
|
bujinyuan
|
c8ae70ef97
|
fix(phase2a): handleError 增强 + 代码格式化 + 测试报告归档
- auth/contact Controller handleError 增加 fallbackMsg 可变参数,未知错误可返回更有语义的降级提示
- import 排序、变量对齐等 gofmt 格式规范化
- 新增管理端测试报告和路由验证报告
Made-with: Cursor
|
2026-03-03 09:51:43 +08:00 |
|
bujinyuan
|
2c59500e27
|
fix(phase2a): 代码审查修复 - 8项关键/重要问题
安全修复:
- WebSocket Token 增加 Redis 有效性校验(已登出用户无法建立 WS)
功能修复:
- GetRecommendFriends 改为批量查询,正确返回用户名/昵称/头像
- 上下线通知:OnlineService 通过接口注入获取好友列表推送状态变更
- 管理端在线用户 API 补充用户名信息
代码质量:
- 所有 json.Marshal/Redis 错误增加检查与日志
- ContactController 13 个 endpoint 统一走 handleError 业务错误映射
- 管理端 Controller 补全包注释、函数注释和结构化日志
- 前端 5 个联系人页面 avatar 工具函数抽取到 utils/avatar.js
Made-with: Cursor
|
2026-03-02 18:48:28 +08:00 |
|
bujinyuan
|
8ec0261427
|
docs: 同步全部项目文档至 Phase 2a 完成状态
- 架构设计文档:更新模块职责表(ws/contact 标记已完成)、路由架构、分层图
- 总体系统设计:联系人 API 扩展至 17 个、管理端 API 补充在线监控和好友管理
- Phase 2a 设计文档:状态标记为已完成
- API README:导航表 + 目录结构 + 联系人模块错误码
- 开发规范:新增 WebSocket 事件联动规范(第 8 节)
- 项目规则:新增「文档自动同步规则」,每个 Task 完成后自动检查更新文档
Made-with: Cursor
|
2026-03-02 17:53:46 +08:00 |
|
bujinyuan
|
a508a4cfbc
|
feat(phase2a): 前台联系人页面 + 管理端在线监控/好友管理 + API 文档
前台联系人模块(ui-ux-pro-max 规范):
- contact/index.vue: 好友列表(搜索/在线状态/骨架屏)
- contact/request.vue: 好友申请列表(接受/拒绝/防重复提交)
- contact/detail.vue: 好友详情(备注/分组/拉黑/删除)
- contact/search.vue: 搜索添加好友 + 好友推荐
- contact/groups.vue: 好友分组管理(CRUD)
- contact/blacklist.vue: 黑名单管理
管理端前端:
- views/monitor/online.vue: 在线监控(统计卡片/用户表格/30s 自动刷新)
- views/contact/list.vue: 好友关系管理(分页表格/强制删除)
- api/monitor.js + api/contact.js: 管理端 API 封装
- 路由 + 侧边栏导航更新
API 文档(4 份):
- docs/api/frontend/contact.md: 17 个接口完整文档
- docs/api/frontend/websocket.md: 前端 WS 事件协议
- docs/api/admin/online.md: 在线监控 API
- docs/api/admin/contact.md: 好友管理 API
进度文档更新至 Phase 2a 全部完成
Made-with: Cursor
|
2026-03-02 17:32:52 +08:00 |
|
bujinyuan
|
618c3f4409
|
feat(frontend): WebSocket 客户端 + Contact Store + API
- services/websocket.js: 单例 WS 连接管理(心跳/重连/事件分发)
- store/websocket.js: Pinia Store 管理连接状态
- store/contact.js: 联系人 Store(好友/申请/分组/黑名单/在线状态)
- api/contact.js: 联系人 REST API 封装(17 个接口)
- api/user.js: 用户搜索 API
Made-with: Cursor
|
2026-03-02 17:15:21 +08:00 |
|
bujinyuan
|
a77669ee99
|
feat(admin): 管理端在线监控 + 好友关系管理后端
- admin/service/online_service.go: 在线用户列表/计数
- admin/controller/online_controller.go: GET /admin/online/users|count
- admin/service/contact_manage_service.go: 好友关系分页查询/管理员删除
- admin/controller/contact_manage_controller.go: GET|DELETE /admin/contacts
- 更新 admin router/provider/wire 集成新控制器
Made-with: Cursor
|
2026-03-02 17:13:06 +08:00 |
|
bujinyuan
|
1c87a466a1
|
feat(ws): 在线状态管理(Redis SET + TTL 心跳续期)
- app/ws/online_service.go: 上线/下线/心跳续期/批量查询在线状态
- 集成到 WebSocket handler: 连接时写入 Redis, 断线时清除
- client.go: 添加 DisconnectHandler 回调
- Redis 键: echo:user:online (SET) + echo:user:status:{uid} (TTL 60s)
Made-with: Cursor
|
2026-03-02 17:10:13 +08:00 |
|