Files
EchoChat/backend/go-service/app/constants/meeting.go
bujinyuan c35097a0d8 fix(meeting): Task 16 修复 code-reviewer 审计 P2 七项 + Nit 七项
覆盖 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 处 + 历次 commit cdaa39d / ea2bf96 / f5ae095 / 5ed14c2)
- 推迟登记表(P2-4 / P2-5 / 端口收敛 / appData 校验 / RFC3339 时间格式,共 5 项)

Made-with: Cursor
2026-04-23 17:45:03 +08:00

147 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 // 空房销毁 TTL5 分钟)
MeetingInviteTokenTTL = 600 // 邀请链接 Token TTL10 分钟)
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客户端发往服务端需 ACKS→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→NodeTransport 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→NodeConsumer 订阅后由前端 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,
}