2026-07-03 10:43:34 +08:00

253 lines
7.5 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 mysql 实现 room-service 的 MySQL 持久化边界。
package mysql
import (
"context"
"database/sql"
"errors"
"strings"
"hyapp/pkg/appcode"
roomservice "hyapp/services/room-service/internal/room/service"
)
func (r *Repository) ProjectRoomPresence(ctx context.Context, snapshot roomservice.RoomPresenceSnapshot) error {
appCode := normalizedRecordAppCode(ctx, snapshot.AppCode)
if strings.TrimSpace(snapshot.RoomID) == "" {
return nil
}
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
// 先把该房间上一版 active presence 全部置为 left再把本次快照里的在线用户 upsert 回 active。
// 这样 Join/Leave/Kick/Close 的投影逻辑统一,避免按命令类型维护多份分支。
if _, err := tx.ExecContext(ctx,
`UPDATE room_user_presence
SET status = ?, publish_state = '', mic_session_id = '', room_version = ?, updated_at_ms = ?
WHERE app_code = ? AND room_id = ? AND status = ?`,
roomPresenceStatusLeftSQL,
snapshot.RoomVersion,
snapshot.UpdatedAtMS,
appCode,
snapshot.RoomID,
roomPresenceStatusActiveSQL,
); err != nil {
_ = tx.Rollback()
return err
}
for _, user := range snapshot.Users {
if user.UserID <= 0 {
continue
}
if _, err := tx.ExecContext(ctx,
`INSERT INTO room_user_presence (
app_code, user_id, room_id, role, publish_state, mic_session_id, room_version, status, joined_at_ms, last_seen_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
room_id = VALUES(room_id),
role = VALUES(role),
publish_state = VALUES(publish_state),
mic_session_id = VALUES(mic_session_id),
room_version = VALUES(room_version),
status = VALUES(status),
joined_at_ms = VALUES(joined_at_ms),
last_seen_at_ms = VALUES(last_seen_at_ms),
updated_at_ms = VALUES(updated_at_ms)`,
appCode,
user.UserID,
snapshot.RoomID,
user.Role,
user.PublishState,
user.MicSessionID,
snapshot.RoomVersion,
roomPresenceStatusActiveSQL,
user.JoinedAtMS,
user.LastSeenAtMS,
snapshot.UpdatedAtMS,
); err != nil {
_ = tx.Rollback()
return err
}
}
return tx.Commit()
}
// UpsertRoomUserPresence 只写入单个 active 用户,避免 JoinRoom 为整房间在线用户产生 O(N) 写放大。
func (r *Repository) UpsertRoomUserPresence(ctx context.Context, presence roomservice.RoomPresence) error {
appCode := normalizedRecordAppCode(ctx, presence.AppCode)
if presence.UserID <= 0 || strings.TrimSpace(presence.RoomID) == "" {
return nil
}
_, err := r.db.ExecContext(ctx,
`INSERT INTO room_user_presence (
app_code, user_id, room_id, role, publish_state, mic_session_id, room_version, status, joined_at_ms, last_seen_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
room_id = VALUES(room_id),
role = VALUES(role),
publish_state = VALUES(publish_state),
mic_session_id = VALUES(mic_session_id),
room_version = VALUES(room_version),
status = VALUES(status),
joined_at_ms = VALUES(joined_at_ms),
last_seen_at_ms = VALUES(last_seen_at_ms),
updated_at_ms = VALUES(updated_at_ms)`,
appCode,
presence.UserID,
presence.RoomID,
presence.Role,
presence.PublishState,
presence.MicSessionID,
presence.RoomVersion,
roomPresenceStatusActiveSQL,
presence.JoinedAtMS,
presence.LastSeenAtMS,
presence.UpdatedAtMS,
)
return err
}
// MarkRoomUserPresenceLeft 只关闭指定用户在指定房间的 active presence避免旧离房投影误伤新房间。
func (r *Repository) MarkRoomUserPresenceLeft(ctx context.Context, presence roomservice.RoomPresence) error {
appCode := normalizedRecordAppCode(ctx, presence.AppCode)
if presence.UserID <= 0 || strings.TrimSpace(presence.RoomID) == "" {
return nil
}
_, err := r.db.ExecContext(ctx,
`UPDATE room_user_presence
SET status = ?, publish_state = '', mic_session_id = '', room_version = ?, updated_at_ms = ?
WHERE app_code = ? AND user_id = ? AND room_id = ? AND status = ?`,
roomPresenceStatusLeftSQL,
presence.RoomVersion,
presence.UpdatedAtMS,
appCode,
presence.UserID,
presence.RoomID,
roomPresenceStatusActiveSQL,
)
return err
}
// GetCurrentRoomPresence 查询用户当前 active 房间,不扫描发现页列表。
func (r *Repository) GetCurrentRoomPresence(ctx context.Context, userID int64) (roomservice.RoomPresence, bool, error) {
row := r.db.QueryRowContext(ctx,
`SELECT app_code, user_id, room_id, role, publish_state, mic_session_id, room_version, status, joined_at_ms, last_seen_at_ms, updated_at_ms
FROM room_user_presence
WHERE app_code = ? AND user_id = ? AND status = ?
LIMIT 1`,
appcode.FromContext(ctx),
userID,
roomPresenceStatusActiveSQL,
)
var presence roomservice.RoomPresence
if err := row.Scan(
&presence.AppCode,
&presence.UserID,
&presence.RoomID,
&presence.Role,
&presence.PublishState,
&presence.MicSessionID,
&presence.RoomVersion,
&presence.Status,
&presence.JoinedAtMS,
&presence.LastSeenAtMS,
&presence.UpdatedAtMS,
); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return roomservice.RoomPresence{}, false, nil
}
return roomservice.RoomPresence{}, false, err
}
return presence, true, nil
}
// ListRoomOnlineUsers 分页查询某个房间当前 active presence。
func (r *Repository) ListRoomOnlineUsers(ctx context.Context, query roomservice.RoomOnlineUserQuery) (roomservice.RoomOnlineUserPage, error) {
page := query.Page
if page <= 0 {
page = 1
}
pageSize := query.PageSize
if pageSize <= 0 {
pageSize = 50
}
offset := (page - 1) * pageSize
appCode := normalizedRecordAppCode(ctx, query.AppCode)
var total int64
if err := r.db.QueryRowContext(ctx,
`SELECT COUNT(*)
FROM room_user_presence
WHERE app_code = ? AND room_id = ? AND status = ?`,
appCode,
query.RoomID,
roomPresenceStatusActiveSQL,
).Scan(&total); err != nil {
return roomservice.RoomOnlineUserPage{}, err
}
orderBy := "p.last_seen_at_ms DESC, p.user_id ASC"
if strings.EqualFold(strings.TrimSpace(query.Sort), "gift_value") {
orderBy = "COALESCE(g.gift_value, 0) DESC, p.last_seen_at_ms DESC, p.user_id ASC"
}
rows, err := r.db.QueryContext(ctx,
`SELECT p.app_code, p.user_id, p.room_id, p.role, p.publish_state, p.mic_session_id, p.room_version, p.status,
p.joined_at_ms, p.last_seen_at_ms, p.updated_at_ms, COALESCE(g.gift_value, 0)
FROM room_user_presence p
LEFT JOIN room_user_gift_stats g
ON g.app_code = p.app_code AND g.room_id = p.room_id AND g.user_id = p.user_id
WHERE p.app_code = ? AND p.room_id = ? AND p.status = ?
ORDER BY `+orderBy+`
LIMIT ? OFFSET ?`,
appCode,
query.RoomID,
roomPresenceStatusActiveSQL,
pageSize,
offset,
)
if err != nil {
return roomservice.RoomOnlineUserPage{}, err
}
defer rows.Close()
users := make([]roomservice.RoomPresence, 0, pageSize)
for rows.Next() {
var presence roomservice.RoomPresence
if err := rows.Scan(
&presence.AppCode,
&presence.UserID,
&presence.RoomID,
&presence.Role,
&presence.PublishState,
&presence.MicSessionID,
&presence.RoomVersion,
&presence.Status,
&presence.JoinedAtMS,
&presence.LastSeenAtMS,
&presence.UpdatedAtMS,
&presence.GiftValue,
); err != nil {
return roomservice.RoomOnlineUserPage{}, err
}
users = append(users, presence)
}
if err := rows.Err(); err != nil {
return roomservice.RoomOnlineUserPage{}, err
}
return roomservice.RoomOnlineUserPage{
Users: users,
Total: total,
Page: page,
PageSize: pageSize,
}, nil
}