fix(meeting): Task 16 修复 code-reviewer 审计 P1 八项

按 docs/reviews/2026-04-23-phase2e-2-code-review.md 清单落地 P1 批次:

- P1-1 主持人转让原子性:MeetingParticipantDAO.TransferHost 内部事务
  合并 meeting_rooms.host_id 更新,service/lifecycle 移除冗余 UpdateHost
- P1-2 ListChatMessages / ListMyMeetings 回归 DAO:新增
  MeetingChatDAO.ListByRoomBefore 反向游标,service 移除 s.db 直查
- P1-3 ListMyMeetings N+1 放大优化:新增
  MeetingParticipantDAO.ListJoinedRoomsByUser JOIN + DISTINCT 单次 SQL
- P1-4 EndRoom 行锁 + 事务:s.db.Transaction 包裹
  SELECT FOR UPDATE → 快照 → MarkEnded → LeaveAllActive;DAO 补
  WithTx(*gorm.DB) 和 GetByIDForUpdate
- P1-5 后台 goroutine trace_id 保留:新增 logs.DetachContext(ctx);
  替换 meeting_service / meeting_signal_service 共 10 处 context.Background();
  meeting_ws_handler 每条 WS 消息分配独立 trace_id
- P1-6 _broadcastSelfState 重试:最多 2 次指数退避(700ms→2100ms),
  检测 localAudioEnabled/localVideoEnabled 已被后续动作覆盖时放弃旧 patch
- P1-7 HandleHostGraceExpired / HandleEmptyRoomExpired 处理锁:
  独立 host_grace_handling:<code> / empty_ttl_handling:<code> SETNX + 60s TTL,
  消除 Redis 自然过期 + 本地 timer 到点 + DEL 返回 0 的盲区
- P1-8 pushExistingRoomState 同步化:OnRoomJoin 返回前完成补推,
  消除与 REST member.joined 并行造成的前端状态闪烁

并附带修复 .gitignore 中 logs/ 规则误伤 pkg/logs/ 代码目录的历史遗留问题,
将 logger.go / trace.go 正式纳入版本控制。

验证:backend go build + go vet 通过;frontend npm run build:h5 通过。

Made-with: Cursor
This commit is contained in:
bujinyuan
2026-04-23 16:53:30 +08:00
parent cdaa39d686
commit ea2bf96c2f
12 changed files with 567 additions and 108 deletions

View File

@@ -11,6 +11,7 @@ import (
"github.com/echochat/backend/pkg/logs"
"go.uber.org/zap"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// MeetingRoomDAO 会议房间数据访问对象
@@ -23,6 +24,31 @@ func NewMeetingRoomDAO(db *gorm.DB) *MeetingRoomDAO {
return &MeetingRoomDAO{db: db}
}
// WithTx 返回绑定到指定事务句柄的新 DAO 实例
// 用法service 层使用 s.db.Transaction(func(tx *gorm.DB) error { return s.roomDAO.WithTx(tx).MarkEnded(...) })
// 所有 DAO 方法都通过 d.db.WithContext(ctx) 派生查询,替换 d.db 为 tx 即可让现有方法参与上游事务。
// Task 16 P1-4EndRoom 行锁 + 事务化 新增
func (d *MeetingRoomDAO) WithTx(tx *gorm.DB) *MeetingRoomDAO {
return &MeetingRoomDAO{db: tx}
}
// GetByIDForUpdate 事务内 SELECT ... FOR UPDATE 行锁查询
// 仅在上游已开启事务(调用方通过 WithTx(tx) 注入)时使用;否则等同于普通查询不生效。
// Task 16 P1-4EndRoom 行锁 新增
func (d *MeetingRoomDAO) GetByIDForUpdate(ctx context.Context, id int64) (*model.MeetingRoom, error) {
var room model.MeetingRoom
err := d.db.WithContext(ctx).
Clauses(clause.Locking{Strength: "UPDATE"}).
First(&room, id).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}
return &room, nil
}
// Create 新建一个会议房间
// 由调用方预先生成 RoomCode 并确认唯一,本方法不做冲突重试
func (d *MeetingRoomDAO) Create(ctx context.Context, room *model.MeetingRoom) error {