196 lines
6.2 KiB
Go
196 lines
6.2 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
|
|
roomv1 "hyapp.local/api/proto/room/v1"
|
|
"hyapp/pkg/tencentim"
|
|
"hyapp/pkg/xerr"
|
|
"hyapp/services/room-service/internal/room/command"
|
|
"hyapp/services/room-service/internal/room/outbox"
|
|
"hyapp/services/room-service/internal/room/rank"
|
|
"hyapp/services/room-service/internal/room/state"
|
|
)
|
|
|
|
// UpdateRoomProfile 修改房间展示资料和麦位数量,所有状态变化仍由 Room Cell 串行提交。
|
|
func (s *Service) UpdateRoomProfile(ctx context.Context, req *roomv1.UpdateRoomProfileRequest) (*roomv1.UpdateRoomProfileResponse, error) {
|
|
ctx = contextFromMeta(ctx, req.GetMeta())
|
|
patch, err := s.normalizeUpdateRoomProfile(ctx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cmd := command.UpdateRoomProfile{
|
|
Base: baseFromMeta(req.GetMeta()),
|
|
RoomName: patch.Name,
|
|
RoomAvatar: patch.Avatar,
|
|
RoomDescription: patch.Description,
|
|
SeatCount: patch.SeatCount,
|
|
}
|
|
|
|
result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) {
|
|
if err := requireActiveRoom(current); err != nil {
|
|
return mutationResult{}, nil, err
|
|
}
|
|
if err := requireOwnerPresent(current, cmd.ActorUserID()); err != nil {
|
|
return mutationResult{}, nil, err
|
|
}
|
|
|
|
profileChanged := applyProfilePatch(current, cmd)
|
|
seatChanged, err := applySeatCountPatch(current, cmd.SeatCount)
|
|
if err != nil {
|
|
return mutationResult{}, nil, err
|
|
}
|
|
if !profileChanged && !seatChanged {
|
|
// 未提交任何字段变化时仍通过 command log 保护 command_id 幂等,但不广播系统消息。
|
|
return mutationResult{snapshot: current.ToProto()}, nil, nil
|
|
}
|
|
|
|
current.Version++
|
|
seatCount := int32(len(current.MicSeats))
|
|
profileEvent, err := outbox.Build(current.RoomID, "RoomProfileUpdated", current.Version, now, &roomeventsv1.RoomProfileUpdated{
|
|
ActorUserId: cmd.ActorUserID(),
|
|
RoomName: current.RoomExt[roomExtTitleKey],
|
|
RoomAvatar: current.RoomExt[roomExtCoverURLKey],
|
|
RoomDescription: current.RoomExt[roomExtDescriptionKey],
|
|
SeatCount: seatCount,
|
|
})
|
|
if err != nil {
|
|
return mutationResult{}, nil, err
|
|
}
|
|
|
|
result := mutationResult{
|
|
snapshot: current.ToProto(),
|
|
syncEvent: &tencentim.RoomEvent{
|
|
EventID: profileEvent.EventID,
|
|
RoomID: current.RoomID,
|
|
EventType: "room_profile_updated",
|
|
ActorUserID: cmd.ActorUserID(),
|
|
RoomVersion: current.Version,
|
|
Attributes: map[string]string{
|
|
"room_name": current.RoomExt[roomExtTitleKey],
|
|
"room_avatar": current.RoomExt[roomExtCoverURLKey],
|
|
"room_description": current.RoomExt[roomExtDescriptionKey],
|
|
"seat_count": int32String(seatCount),
|
|
},
|
|
},
|
|
}
|
|
if seatChanged {
|
|
result.roomSeatCount = &seatCount
|
|
}
|
|
return result, []outbox.Record{profileEvent}, nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &roomv1.UpdateRoomProfileResponse{
|
|
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
|
|
Room: result.snapshot,
|
|
}, nil
|
|
}
|
|
|
|
type updateRoomProfilePatch struct {
|
|
Name *string
|
|
Avatar *string
|
|
Description *string
|
|
SeatCount *int32
|
|
}
|
|
|
|
func (s *Service) normalizeUpdateRoomProfile(ctx context.Context, req *roomv1.UpdateRoomProfileRequest) (updateRoomProfilePatch, error) {
|
|
var patch updateRoomProfilePatch
|
|
if req == nil {
|
|
return patch, xerr.New(xerr.InvalidArgument, "request is required")
|
|
}
|
|
if req.RoomName != nil {
|
|
value := strings.TrimSpace(req.GetRoomName())
|
|
if value == "" {
|
|
return patch, xerr.New(xerr.InvalidArgument, "room_name is required")
|
|
}
|
|
if utf8.RuneCountInString(value) > maxRoomNameRunes {
|
|
return patch, xerr.New(xerr.InvalidArgument, "room_name is too long")
|
|
}
|
|
patch.Name = &value
|
|
}
|
|
if req.RoomAvatar != nil {
|
|
value := strings.TrimSpace(req.GetRoomAvatar())
|
|
if value == "" {
|
|
// 房主清空头像时恢复系统默认资源,避免列表卡片落空。
|
|
value = defaultRoomAvatar
|
|
}
|
|
if utf8.RuneCountInString(value) > maxRoomAvatarRunes {
|
|
return patch, xerr.New(xerr.InvalidArgument, "room_avatar is too long")
|
|
}
|
|
patch.Avatar = &value
|
|
}
|
|
if req.RoomDescription != nil {
|
|
value := strings.TrimSpace(req.GetRoomDescription())
|
|
if utf8.RuneCountInString(value) > maxRoomDescriptionRunes {
|
|
return patch, xerr.New(xerr.InvalidArgument, "room_description is too long")
|
|
}
|
|
patch.Description = &value
|
|
}
|
|
if req.SeatCount != nil {
|
|
value, err := s.validateRoomSeatCount(ctx, req.GetSeatCount())
|
|
if err != nil {
|
|
return patch, err
|
|
}
|
|
patch.SeatCount = &value
|
|
}
|
|
return patch, nil
|
|
}
|
|
|
|
func applyProfilePatch(current *state.RoomState, cmd command.UpdateRoomProfile) bool {
|
|
if current.RoomExt == nil {
|
|
current.RoomExt = make(map[string]string)
|
|
}
|
|
changed := false
|
|
if cmd.RoomName != nil && current.RoomExt[roomExtTitleKey] != *cmd.RoomName {
|
|
current.RoomExt[roomExtTitleKey] = *cmd.RoomName
|
|
changed = true
|
|
}
|
|
if cmd.RoomAvatar != nil && current.RoomExt[roomExtCoverURLKey] != *cmd.RoomAvatar {
|
|
current.RoomExt[roomExtCoverURLKey] = *cmd.RoomAvatar
|
|
changed = true
|
|
}
|
|
if cmd.RoomDescription != nil && current.RoomExt[roomExtDescriptionKey] != *cmd.RoomDescription {
|
|
current.RoomExt[roomExtDescriptionKey] = *cmd.RoomDescription
|
|
changed = true
|
|
}
|
|
return changed
|
|
}
|
|
|
|
func applySeatCountPatch(current *state.RoomState, seatCount *int32) (bool, error) {
|
|
if seatCount == nil {
|
|
return false, nil
|
|
}
|
|
target := *seatCount
|
|
currentCount := int32(len(current.MicSeats))
|
|
if target == currentCount {
|
|
return false, nil
|
|
}
|
|
if target > currentCount {
|
|
for seatNo := currentCount + 1; seatNo <= target; seatNo++ {
|
|
current.MicSeats = append(current.MicSeats, state.MicSeat{SeatNo: seatNo})
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
nextSeats := make([]state.MicSeat, 0, target)
|
|
for _, seat := range current.MicSeats {
|
|
if seat.SeatNo <= target {
|
|
nextSeats = append(nextSeats, seat)
|
|
continue
|
|
}
|
|
if seat.UserID != 0 || seat.Locked || seat.PublishState != "" || seat.MicSessionID != "" {
|
|
// 缩容不能隐式踢人、清理发流会话或解锁麦位;房主需要先把高编号麦位处理干净。
|
|
return false, xerr.New(xerr.Conflict, "removed seats must be empty and unlocked")
|
|
}
|
|
}
|
|
current.MicSeats = nextSeats
|
|
return true, nil
|
|
}
|