Files
EchoChat/backend/go-service/app/constants/meeting.go
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

115 lines
4.7 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 // 会议聊天保留时长(会议结束后)
)
// 会议 WS 事件常量(设计文档 §6.311 个事件)
// 命名格式meeting.{domain}.{action}
const (
// 房间级事件
MeetingWSEventRoomEnded = "meeting.room.ended" // 会议已结束
MeetingWSEventRoomLocked = "meeting.room.locked" // 会议被锁定等候室Phase 2e-3
MeetingWSEventRoomHostChange = "meeting.room.host.changed" // 主持人变更
// 成员级事件
MeetingWSEventMemberJoined = "meeting.member.joined" // 新成员加入
MeetingWSEventMemberLeft = "meeting.member.left" // 成员离开
MeetingWSEventMemberStateChange = "meeting.member.state.changed" // 麦克风 / 摄像头状态变化
MeetingWSEventMemberKicked = "meeting.member.kicked" // 被移出
// 媒体级事件Go 发起,驱动客户端 mediasoup-client 订阅/取消)
MeetingWSEventProducerNew = "meeting.producer.new" // 有新 producer 可订阅
MeetingWSEventProducerClosed = "meeting.producer.closed" // producer 已关闭
MeetingWSEventConsumerResumed = "meeting.consumer.resumed" // consumer 已恢复(由服务端触发)
MeetingWSEventChatMessage = "meeting.chat.message" // 会议内文字聊天
)