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:
138
backend/go-service/app/meeting/controller/meeting_controller.go
Normal file
138
backend/go-service/app/meeting/controller/meeting_controller.go
Normal file
@@ -0,0 +1,138 @@
|
||||
// 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")
|
||||
}
|
||||
Reference in New Issue
Block a user