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

139 lines
4.1 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 controller 提供 meeting 模块的 HTTP 接口
package controller
import (
"net/http"
"github.com/echochat/backend/app/meeting/service"
"github.com/echochat/backend/pkg/middleware"
"github.com/echochat/backend/pkg/utils"
"github.com/gin-gonic/gin"
)
// MeetingController 会议 REST 控制器
// Task 4 骨架阶段:所有 handler 均返回 501 Not Implemented便于路由自测与前端 mock
// Task 5/6/7 将在此填充请求解析、业务调用与错误映射
type MeetingController struct {
meetingService *service.MeetingService
}
// NewMeetingController 创建 MeetingController 实例
func NewMeetingController(meetingService *service.MeetingService) *MeetingController {
return &MeetingController{meetingService: meetingService}
}
// responseNotImplemented Task 4 占位响应Task 5/6/7 逐接口替换为真实实现
func responseNotImplemented(c *gin.Context, endpoint string) {
c.JSON(http.StatusNotImplemented, gin.H{
"code": http.StatusNotImplemented,
"message": "接口尚未实现",
"endpoint": endpoint,
})
}
// requireUserID 统一的当前用户取值,失败直接写 401 并返回 false
func requireUserID(c *gin.Context) (int64, bool) {
userID, ok := middleware.GetCurrentUserID(c)
if !ok {
utils.ResponseUnauthorized(c, "无法获取当前用户信息")
return 0, false
}
return userID, true
}
// CreateRoom POST /api/v1/meeting/rooms
func (ctl *MeetingController) CreateRoom(c *gin.Context) {
if _, ok := requireUserID(c); !ok {
return
}
responseNotImplemented(c, "POST /api/v1/meeting/rooms")
}
// GetRoom GET /api/v1/meeting/rooms/:code
func (ctl *MeetingController) GetRoom(c *gin.Context) {
if _, ok := requireUserID(c); !ok {
return
}
responseNotImplemented(c, "GET /api/v1/meeting/rooms/:code")
}
// JoinRoom POST /api/v1/meeting/rooms/:code/join
func (ctl *MeetingController) JoinRoom(c *gin.Context) {
if _, ok := requireUserID(c); !ok {
return
}
responseNotImplemented(c, "POST /api/v1/meeting/rooms/:code/join")
}
// LeaveRoom POST /api/v1/meeting/rooms/:code/leave
func (ctl *MeetingController) LeaveRoom(c *gin.Context) {
if _, ok := requireUserID(c); !ok {
return
}
responseNotImplemented(c, "POST /api/v1/meeting/rooms/:code/leave")
}
// EndRoom POST /api/v1/meeting/rooms/:code/end
func (ctl *MeetingController) EndRoom(c *gin.Context) {
if _, ok := requireUserID(c); !ok {
return
}
responseNotImplemented(c, "POST /api/v1/meeting/rooms/:code/end")
}
// ListMyMeetings GET /api/v1/meeting/rooms?role=host|participant
func (ctl *MeetingController) ListMyMeetings(c *gin.Context) {
if _, ok := requireUserID(c); !ok {
return
}
responseNotImplemented(c, "GET /api/v1/meeting/rooms")
}
// TransferHost POST /api/v1/meeting/rooms/:code/transfer-host
func (ctl *MeetingController) TransferHost(c *gin.Context) {
if _, ok := requireUserID(c); !ok {
return
}
responseNotImplemented(c, "POST /api/v1/meeting/rooms/:code/transfer-host")
}
// KickMember POST /api/v1/meeting/rooms/:code/kick
func (ctl *MeetingController) KickMember(c *gin.Context) {
if _, ok := requireUserID(c); !ok {
return
}
responseNotImplemented(c, "POST /api/v1/meeting/rooms/:code/kick")
}
// InviteUsers POST /api/v1/meeting/rooms/:code/invite
func (ctl *MeetingController) InviteUsers(c *gin.Context) {
if _, ok := requireUserID(c); !ok {
return
}
responseNotImplemented(c, "POST /api/v1/meeting/rooms/:code/invite")
}
// RedeemInvite POST /api/v1/meeting/invites/:token/redeem
func (ctl *MeetingController) RedeemInvite(c *gin.Context) {
if _, ok := requireUserID(c); !ok {
return
}
responseNotImplemented(c, "POST /api/v1/meeting/invites/:token/redeem")
}
// SendChat POST /api/v1/meeting/rooms/:code/chats
func (ctl *MeetingController) SendChat(c *gin.Context) {
if _, ok := requireUserID(c); !ok {
return
}
responseNotImplemented(c, "POST /api/v1/meeting/rooms/:code/chats")
}
// ListChats GET /api/v1/meeting/rooms/:code/chats?after_id=&limit=
func (ctl *MeetingController) ListChats(c *gin.Context) {
if _, ok := requireUserID(c); !ok {
return
}
responseNotImplemented(c, "GET /api/v1/meeting/rooms/:code/chats")
}