Files
EchoChat/deploy/docker/postgres/phase2e2_migration.sql
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

89 lines
5.5 KiB
SQL
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.

-- ============================================================
-- Phase 2e-2 数据库迁移:会议 MVPmeeting_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 应用层强制 8schema 默认 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 CASCADEroom 删除时自动清理参与者
-- ============================================================
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.idCASCADE 删除';
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.idCASCADE 删除';
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);