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
This commit is contained in:
88
deploy/docker/postgres/phase2e2_migration.sql
Normal file
88
deploy/docker/postgres/phase2e2_migration.sql
Normal file
@@ -0,0 +1,88 @@
|
||||
-- ============================================================
|
||||
-- Phase 2e-2 数据库迁移:会议 MVP(meeting_rooms / meeting_participants / meeting_chats)
|
||||
-- 执行环境:在已有 Phase 2e-1(通知中心)数据库基础上增量升级
|
||||
-- 全部语句使用 IF NOT EXISTS / 幂等形式,可重复执行
|
||||
-- 时间字段沿用项目风格 TIMESTAMP(0)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS meeting_rooms (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
room_code VARCHAR(20) UNIQUE NOT NULL,
|
||||
title VARCHAR(200) NOT NULL,
|
||||
host_id BIGINT NOT NULL REFERENCES auth_users(id),
|
||||
type SMALLINT NOT NULL DEFAULT 1,
|
||||
password_hash VARCHAR(255) DEFAULT NULL,
|
||||
max_members INT NOT NULL DEFAULT 50,
|
||||
status SMALLINT NOT NULL DEFAULT 0,
|
||||
scheduled_at TIMESTAMP(0) DEFAULT NULL,
|
||||
started_at TIMESTAMP(0) DEFAULT NULL,
|
||||
ended_at TIMESTAMP(0) DEFAULT NULL,
|
||||
ended_reason VARCHAR(20) DEFAULT NULL,
|
||||
settings JSONB NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP(0) NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE meeting_rooms IS '会议房间表';
|
||||
COMMENT ON COLUMN meeting_rooms.room_code IS '用户可见的会议号 XXX-XXX-XXX,全局唯一';
|
||||
COMMENT ON COLUMN meeting_rooms.title IS '会议标题';
|
||||
COMMENT ON COLUMN meeting_rooms.host_id IS '主持人用户 ID,引用 auth_users.id';
|
||||
COMMENT ON COLUMN meeting_rooms.type IS '会议类型:1=即时(MVP 仅此),2=预约(Phase 2e-3)';
|
||||
COMMENT ON COLUMN meeting_rooms.password_hash IS '入会密码 bcrypt 哈希,NULL 表示无密码';
|
||||
COMMENT ON COLUMN meeting_rooms.max_members IS '房间最大成员数;MVP 应用层强制 8,schema 默认 50 供后续扩展';
|
||||
COMMENT ON COLUMN meeting_rooms.status IS '0=未开始(仅预约),1=进行中,2=已结束';
|
||||
COMMENT ON COLUMN meeting_rooms.scheduled_at IS '预约开始时间,即时会议为 NULL';
|
||||
COMMENT ON COLUMN meeting_rooms.started_at IS '实际开始时间,host 首次加入时回填';
|
||||
COMMENT ON COLUMN meeting_rooms.ended_at IS '实际结束时间';
|
||||
COMMENT ON COLUMN meeting_rooms.ended_reason IS '结束原因:host_ended / empty_ttl / admin_force / system_error';
|
||||
COMMENT ON COLUMN meeting_rooms.settings IS 'JSON 配置:{mute_on_join, allow_chat, record_enabled 等}';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_meeting_rooms_host_status ON meeting_rooms (host_id, status, created_at DESC);
|
||||
|
||||
-- ============================================================
|
||||
-- meeting_participants: 参与者记录
|
||||
-- ON DELETE CASCADE:room 删除时自动清理参与者
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS meeting_participants (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
room_id BIGINT NOT NULL REFERENCES meeting_rooms(id) ON DELETE CASCADE,
|
||||
user_id BIGINT NOT NULL REFERENCES auth_users(id),
|
||||
role SMALLINT NOT NULL DEFAULT 0,
|
||||
joined_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||||
left_at TIMESTAMP(0) DEFAULT NULL,
|
||||
left_reason VARCHAR(20) DEFAULT NULL,
|
||||
duration INT NOT NULL DEFAULT 0,
|
||||
UNIQUE (room_id, user_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE meeting_participants IS '会议参与者记录(一次入会一行,重入更新)';
|
||||
COMMENT ON COLUMN meeting_participants.room_id IS '所属会议房间 ID,引用 meeting_rooms.id,CASCADE 删除';
|
||||
COMMENT ON COLUMN meeting_participants.user_id IS '参与者用户 ID,引用 auth_users.id';
|
||||
COMMENT ON COLUMN meeting_participants.role IS '0=普通,1=主持人(MVP 仅此两档),2=联合主持人(第二期)';
|
||||
COMMENT ON COLUMN meeting_participants.joined_at IS '加入时间,记录创建即代表加入';
|
||||
COMMENT ON COLUMN meeting_participants.left_at IS '离开时间,NULL 表示仍在会议中';
|
||||
COMMENT ON COLUMN meeting_participants.left_reason IS '离会原因:self / kicked / host_end / empty_ttl / disconnect';
|
||||
COMMENT ON COLUMN meeting_participants.duration IS '本次参会时长(秒),left_at 写入时同步计算';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_meeting_participants_room ON meeting_participants (room_id, joined_at ASC);
|
||||
CREATE INDEX IF NOT EXISTS idx_meeting_participants_user ON meeting_participants (user_id, joined_at DESC);
|
||||
|
||||
-- ============================================================
|
||||
-- meeting_chats: 会议内文字聊天
|
||||
-- 清理策略见 CURRENT_STATUS Phase 2e-2 说明
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS meeting_chats (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
room_id BIGINT NOT NULL REFERENCES meeting_rooms(id) ON DELETE CASCADE,
|
||||
user_id BIGINT NOT NULL REFERENCES auth_users(id),
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE meeting_chats IS '会议内文字聊天消息(会议结束 24 小时后批量清理)';
|
||||
COMMENT ON COLUMN meeting_chats.room_id IS '所属会议房间 ID,引用 meeting_rooms.id,CASCADE 删除';
|
||||
COMMENT ON COLUMN meeting_chats.user_id IS '发送者用户 ID,引用 auth_users.id';
|
||||
COMMENT ON COLUMN meeting_chats.content IS '消息内容(纯文本,MVP 不支持富媒体)';
|
||||
COMMENT ON COLUMN meeting_chats.created_at IS '消息发送时间';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_meeting_chats_room_created ON meeting_chats (room_id, created_at ASC);
|
||||
Reference in New Issue
Block a user