覆盖 Phase 2e-2 代码审查报告(docs/reviews/2026-04-23-phase2e-2-code-review.md) P2/Nit 收尾批次,均在本仓库完成闭环;剩余 5 项登记推迟至 Phase 2f/3。 ===== P2 七项 ===== - P2-1 cleanupUserResources 补 transport 清理 · media-server 新增 DELETE /internal/v1/transports/:id + transport.service.closeTransport · Go MediaOrchestrator 接口新增 CloseTransport;HTTP 实现按 doCloseRequest 走 4xx 幂等 + 指数退避 · meeting_signal_service.cleanupUserResources 新增 "transport:<id>" 分支 - P2-2 preview.vue 快速切设备竞态 · previewSeq 序号 + nextTick 后 stale 判断,丢弃过期结果 · onVideoChange/onAudioChange 走 scheduleRestartPreview 200ms 防抖 · onBeforeUnmount 清理 changeDebounceTimer - P2-3 room.vue onLoad redirectTo 后补 return · 引入 redirectingToJoin 守卫,跳转页不再执行 onMounted 初始化 - P2-6 generateUniqueRoomCode 重试上限监控 · 重试后成功:Warn 日志(码空间健康度告警) · 重试耗尽:Error 日志 + ErrRoomCodeConflict · 修正 logs.Error 调用签名(去掉多余的 nil) - P2-7 SendChatMessage 服务端长度 + 频率限制 · 新增 ErrChatContentEmpty / ErrChatContentTooLong / ErrChatRateLimited · utf8.RuneCountInString 校验 500 字符上限 · Redis INCR + EXPIRE 滑动窗口(30 条/60s,首次写入 EXPIRE 兜底) · controller.handleError 映射为 HTTP 400 - P2-8 MEETING_ENDED_REASON_LABEL 覆盖复核 · 新增前端专属常量 MEETING_ENDED_REASON_KICKED + 文案 · store/meeting.js _onMemberKicked 使用常量 · 同步修复后端 OnWSDisconnect 硬编码 "ws_disconnect" → constants.MeetingLeftReasonDisconnect - P2 已修 P2-1/2/3/6/7/8;P2-4(WS token 迁出 URL query)与 P2-5(Chat 服务拆分)登记推迟 ===== Nit 七项 ===== - Nit: kind:id 解析改用 strings.SplitN · cleanupUserResources / pushExistingRoomState 两处同步 - Nit: resourceTTL 中央化 · 新增 constants.MeetingResourceTrackTTLSeconds(3600) · meeting_signal_service.go resourceTTL 由 const 改 var 并引用常量 - Nit: ws/handler.go CheckOrigin 白名单 · NewHandler 新增 serverCfg 依赖;checkOrigin 支持同源放行 / dev 模式放行 / release 模式白名单严格匹配 · config.ServerConfig 新增 WSAllowedOrigins(逗号分隔)+ AllowedOrigins() / IsRelease() 辅助方法 · provider.go 新增 provideServerConfig,wire_gen.go 同步 - Nit: http_media_orchestrator.go 超时 + CreateRouter 重试 · 默认 TimeoutMS 5000→10000ms 兼容 Worker 冷启动 · 新增 CreateRouterRetry(默认 1 次,300ms 退避),仅对非 404 错误重试 · config.dev.yaml / config.docker.yaml 同步写入显式配置 - Nit: deploy-public.sh REDIS_PASSWORD × redis.conf 联动校验 · 检测 REDIS_PASSWORD 与 redis.conf 的 requirepass 配对一致性 · redis.conf 增加公网部署 requirepass 使用说明 - Nit: media-server internal-auth isPrivatePath 按 path 匹配 · 剔除 query/hash 后再与白名单 startsWith,避免 "?" 语义混淆 - Nit: mediasoup-client.js in-flight 锁走读确认 · finally 分支已覆盖 resolve/reject 两路,追加注释强化语义 - Nit 走读复核:_onMemberLeft 整槽关闭 vs _onProducerNew(closed=true) 精确匹配 producerId · 粒度正确,无需改动(登记结论) ===== 构建验证 ===== - go vet ./... / go build ./... 通过 - frontend npm run build:h5 通过(仅 uni-app legacy warning,无 error) - media-server npx tsc --noEmit 通过 ===== 审查追踪小节 ===== docs/reviews/2026-04-23-phase2e-2-code-review.md 追加 "Task 16 修复追踪(2026-04-24 更新)": - 已修复一览(本批次 14 处 + 历次 commitcdaa39d/ea2bf96/f5ae095/ 5ed14c2) - 推迟登记表(P2-4 / P2-5 / 端口收敛 / appData 校验 / RFC3339 时间格式,共 5 项) Made-with: Cursor
147 lines
6.8 KiB
Go
147 lines
6.8 KiB
Go
package constants
|
||
|
||
// 会议相关常量(Phase 2e-2)
|
||
// 包含:会议类型、会议状态、参与者角色、结束原因、离会原因、默认配置
|
||
// 所有枚举值严禁直接用中文字符串,中文仅用于 *Map 展示
|
||
|
||
// 会议类型(meeting_rooms.type)
|
||
const (
|
||
MeetingTypeInstant = 1 // 即时会议(MVP 仅此)
|
||
MeetingTypeScheduled = 2 // 预约会议(Phase 2e-3)
|
||
)
|
||
|
||
// MeetingTypeMap 会议类型中文映射
|
||
var MeetingTypeMap = map[int]string{
|
||
MeetingTypeInstant: "即时会议",
|
||
MeetingTypeScheduled: "预约会议",
|
||
}
|
||
|
||
// 会议状态(meeting_rooms.status)
|
||
const (
|
||
MeetingStatusPending = 0 // 未开始(仅预约会议使用)
|
||
MeetingStatusActive = 1 // 进行中
|
||
MeetingStatusEnded = 2 // 已结束
|
||
)
|
||
|
||
// MeetingStatusMap 会议状态中文映射
|
||
var MeetingStatusMap = map[int]string{
|
||
MeetingStatusPending: "未开始",
|
||
MeetingStatusActive: "进行中",
|
||
MeetingStatusEnded: "已结束",
|
||
}
|
||
|
||
// 会议参与者角色(meeting_participants.role)
|
||
// MVP 仅使用 Participant / Host 两档,CoHost 保留至第二期
|
||
const (
|
||
MeetingRoleParticipant = 0 // 普通参会人
|
||
MeetingRoleHost = 1 // 主持人
|
||
MeetingRoleCoHost = 2 // 联合主持人(保留,MVP 不用)
|
||
)
|
||
|
||
// MeetingRoleMap 参与者角色中文映射
|
||
var MeetingRoleMap = map[int]string{
|
||
MeetingRoleParticipant: "参会人",
|
||
MeetingRoleHost: "主持人",
|
||
MeetingRoleCoHost: "联合主持人",
|
||
}
|
||
|
||
// 会议结束原因(meeting_rooms.ended_reason)
|
||
const (
|
||
MeetingEndedReasonHostEnded = "host_ended" // 主持人主动结束
|
||
MeetingEndedReasonEmptyTTL = "empty_ttl" // 空房超时销毁
|
||
MeetingEndedReasonAdminForce = "admin_force" // 管理员强制结束
|
||
MeetingEndedReasonSystemError = "system_error" // 系统异常(如 Node worker died)
|
||
)
|
||
|
||
// MeetingEndedReasonMap 结束原因中文映射
|
||
var MeetingEndedReasonMap = map[string]string{
|
||
MeetingEndedReasonHostEnded: "主持人结束",
|
||
MeetingEndedReasonEmptyTTL: "空房超时",
|
||
MeetingEndedReasonAdminForce: "管理员强制结束",
|
||
MeetingEndedReasonSystemError: "系统异常",
|
||
}
|
||
|
||
// 离会原因(meeting_participants.left_reason)
|
||
const (
|
||
MeetingLeftReasonSelf = "self" // 主动离会
|
||
MeetingLeftReasonKicked = "kicked" // 被主持人移除
|
||
MeetingLeftReasonHostEnd = "host_end" // 主持人结束会议
|
||
MeetingLeftReasonEmptyTTL = "empty_ttl" // 空房 TTL 触发
|
||
MeetingLeftReasonDisconnect = "disconnect" // 网络断开(宽限期结束仍未重连)
|
||
)
|
||
|
||
// MeetingLeftReasonMap 离会原因中文映射
|
||
var MeetingLeftReasonMap = map[string]string{
|
||
MeetingLeftReasonSelf: "主动离开",
|
||
MeetingLeftReasonKicked: "被移除",
|
||
MeetingLeftReasonHostEnd: "会议结束",
|
||
MeetingLeftReasonEmptyTTL: "空房超时",
|
||
MeetingLeftReasonDisconnect: "网络断开",
|
||
}
|
||
|
||
// 会议默认配置(应用层强制约束)
|
||
const (
|
||
MeetingMVPMaxMembers = 8 // MVP 阶段单会议硬上限
|
||
MeetingRoomCodeLength = 9 // 会议号去连字符后的长度(XXX-XXX-XXX)
|
||
MeetingRoomCodeRetryMax = 3 // 生成会议号冲突重试上限
|
||
MeetingPasswordMaxAttempts = 5 // 密码连续错误锁定阈值
|
||
MeetingPasswordLockSeconds = 600 // 密码锁定时长(10 分钟)
|
||
MeetingHostGraceSeconds = 120 // 主持人掉线宽限期(2 分钟)
|
||
MeetingEmptyRoomTTLSeconds = 300 // 空房销毁 TTL(5 分钟)
|
||
MeetingInviteTokenTTL = 600 // 邀请链接 Token TTL(10 分钟)
|
||
MeetingChatRetentionHours = 24 // 会议聊天保留时长(会议结束后)
|
||
|
||
// Task 16 P2-7:服务端聊天限流(防止客户端校验被绕过)
|
||
MeetingChatMaxContentLen = 500 // 单条消息最大字符数(Unicode rune 计数)
|
||
MeetingChatRateLimitPerMin = 30 // 单用户单会议每分钟最大条数
|
||
MeetingChatRateLimitWindowS = 60 // 限流滑动窗口秒数(配合 Redis INCR + EXPIRE)
|
||
|
||
// Task 16 Nit:单个用户资源追踪集合 TTL(秒)
|
||
// 原硬编码在 meeting_signal_service.go::resourceTTL = time.Hour;
|
||
// 中央化到 constants 便于统一修改 + 单元测试覆盖
|
||
MeetingResourceTrackTTLSeconds = 3600 // 1 小时
|
||
)
|
||
|
||
// 会议 WS 事件常量(设计文档 §6.3)
|
||
// 命名格式:meeting.{domain}.{action}
|
||
// 方向说明:C→S(客户端发往服务端,需 ACK);S→C(服务端广播/定向推送,不需 ACK)
|
||
const (
|
||
// ====== 房间组(3 个)======
|
||
MeetingWSEventRoomJoin = "meeting.room.join" // C→S:加入会议后的"宣告在线",绑定 WS 连接 ↔ roomCode
|
||
MeetingWSEventRoomLeave = "meeting.room.leave" // C→S:主动离会(等价 REST leave,但保留信令触发入口)
|
||
MeetingWSEventRoomEnded = "meeting.room.ended" // S→C:会议已结束(广播全员)
|
||
|
||
// ====== 成员组(5 个)======
|
||
MeetingWSEventMemberJoined = "meeting.member.joined" // S→C:新成员加入
|
||
MeetingWSEventMemberLeft = "meeting.member.left" // S→C:成员离开
|
||
MeetingWSEventMemberStateChange = "meeting.member.state.changed" // 双向:麦克风 / 摄像头状态变化(host 可带 target_user_id 强制静音他人)
|
||
MeetingWSEventMemberKicked = "meeting.member.kicked" // S→C:被移出会议(定向发给被踢者)
|
||
MeetingWSEventMemberProducerNew = "meeting.member.producer.new" // S→C:有新 producer 可订阅(produce.start 成功后广播)
|
||
MeetingWSEventHostChanged = "meeting.host.changed" // S→C:主持人变更
|
||
|
||
// ====== 媒体组(5 个,mediasoup signaling 桥接)======
|
||
MeetingWSEventTransportCreate = "meeting.transport.create" // C→S→Node:创建 Transport
|
||
MeetingWSEventTransportConnect = "meeting.transport.connect" // C→S→Node:Transport DTLS 握手
|
||
MeetingWSEventProduceStart = "meeting.produce.start" // C→S→Node:创建 Producer(广播 producer.new)
|
||
MeetingWSEventConsumeStart = "meeting.consume.start" // C→S→Node:订阅远端 Producer,创建本地 Consumer
|
||
MeetingWSEventConsumeResume = "meeting.consume.resume" // C→S→Node:Consumer 订阅后由前端 track 挂载就绪再 resume(防止首帧丢失;Task 9 引入)
|
||
MeetingWSEventProducerClose = "meeting.producer.close" // C→S→Node:关闭自己的 Producer(推流停止)
|
||
|
||
// ====== 补充事件(非 §6.3 核心 11 事件,但业务必须)======
|
||
MeetingWSEventChatMessage = "meeting.chat.message" // S→C:会议内文字聊天广播(REST SendChat 触发)
|
||
)
|
||
|
||
// MeetingWSClientEvents 客户端可主动发起的 WS 事件(C→S)白名单
|
||
// 用于 WS 消息路由时快速校验事件名合法性
|
||
var MeetingWSClientEvents = []string{
|
||
MeetingWSEventRoomJoin,
|
||
MeetingWSEventRoomLeave,
|
||
MeetingWSEventMemberStateChange,
|
||
MeetingWSEventTransportCreate,
|
||
MeetingWSEventTransportConnect,
|
||
MeetingWSEventProduceStart,
|
||
MeetingWSEventConsumeStart,
|
||
MeetingWSEventConsumeResume,
|
||
MeetingWSEventProducerClose,
|
||
}
|