252 lines
8.6 KiB
Go
252 lines
8.6 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/roomid"
|
|
"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"
|
|
)
|
|
|
|
// SaveRoomBackground 把房主上传后的背景图 URL 保存到房间维度素材列表。
|
|
// 该动作只维护低频素材表,不改变 Room Cell 当前背景;真正生效必须调用 SetRoomBackground。
|
|
func (s *Service) SaveRoomBackground(ctx context.Context, req *roomv1.SaveRoomBackgroundRequest) (*roomv1.SaveRoomBackgroundResponse, error) {
|
|
ctx = contextFromMeta(ctx, req.GetMeta())
|
|
roomID, actorUserID, imageURL, err := normalizeSaveRoomBackground(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.requireRoomOwnerMeta(ctx, roomID, actorUserID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nowMS := s.clock.Now().UnixMilli()
|
|
background, err := s.repository.SaveRoomBackground(ctx, RoomBackgroundImage{
|
|
AppCode: req.GetMeta().GetAppCode(),
|
|
RoomID: roomID,
|
|
ImageURL: imageURL,
|
|
CreatedByUserID: actorUserID,
|
|
CreatedAtMS: nowMS,
|
|
UpdatedAtMS: nowMS,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &roomv1.SaveRoomBackgroundResponse{
|
|
Background: roomBackgroundToProto(background),
|
|
ServerTimeMs: s.clock.Now().UnixMilli(),
|
|
}, nil
|
|
}
|
|
|
|
// ListRoomBackgrounds 返回房主保存过的背景图列表和当前生效背景。
|
|
// 列表属于房间编辑面板数据,因此只允许房主读取。
|
|
func (s *Service) ListRoomBackgrounds(ctx context.Context, req *roomv1.ListRoomBackgroundsRequest) (*roomv1.ListRoomBackgroundsResponse, error) {
|
|
ctx = contextFromMeta(ctx, req.GetMeta())
|
|
roomID, viewerUserID, err := normalizeListRoomBackgrounds(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.requireRoomOwnerMeta(ctx, roomID, viewerUserID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
backgrounds, err := s.repository.ListRoomBackgrounds(ctx, roomID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
snapshot, err := s.currentSnapshot(ctx, roomID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &roomv1.ListRoomBackgroundsResponse{
|
|
Backgrounds: roomBackgroundsToProto(backgrounds),
|
|
SelectedBackgroundUrl: snapshot.GetRoomExt()[roomExtBackgroundKey],
|
|
ServerTimeMs: s.clock.Now().UnixMilli(),
|
|
}, nil
|
|
}
|
|
|
|
// SetRoomBackground 把已保存的背景图设置为当前房间背景。
|
|
// 当前背景属于房间展示状态,必须经 Room Cell 命令链路、command log 和快照恢复。
|
|
func (s *Service) SetRoomBackground(ctx context.Context, req *roomv1.SetRoomBackgroundRequest) (*roomv1.SetRoomBackgroundResponse, error) {
|
|
ctx = contextFromMeta(ctx, req.GetMeta())
|
|
roomID, actorUserID, backgroundID, err := normalizeSetRoomBackground(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.requireRoomOwnerMeta(ctx, roomID, actorUserID); err != nil {
|
|
return nil, err
|
|
}
|
|
background, exists, err := s.repository.GetRoomBackground(ctx, roomID, backgroundID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !exists {
|
|
return nil, xerr.New(xerr.NotFound, "room background not found")
|
|
}
|
|
|
|
cmd := command.SetRoomBackground{
|
|
Base: baseFromMeta(req.GetMeta()),
|
|
BackgroundID: background.BackgroundID,
|
|
BackgroundURL: background.ImageURL,
|
|
}
|
|
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 current.OwnerUserID != cmd.ActorUserID() {
|
|
return mutationResult{}, nil, xerr.New(xerr.PermissionDenied, "permission denied")
|
|
}
|
|
if current.RoomExt == nil {
|
|
current.RoomExt = make(map[string]string)
|
|
}
|
|
if current.RoomExt[roomExtBackgroundKey] == cmd.BackgroundURL {
|
|
return mutationResult{snapshot: current.ToProto()}, nil, nil
|
|
}
|
|
|
|
current.RoomExt[roomExtBackgroundKey] = cmd.BackgroundURL
|
|
current.Version++
|
|
backgroundEvent, err := outbox.Build(current.RoomID, "RoomBackgroundChanged", current.Version, now, &roomeventsv1.RoomBackgroundChanged{
|
|
ActorUserId: cmd.ActorUserID(),
|
|
BackgroundId: cmd.BackgroundID,
|
|
RoomBackgroundUrl: current.RoomExt[roomExtBackgroundKey],
|
|
})
|
|
if err != nil {
|
|
return mutationResult{}, nil, err
|
|
}
|
|
return mutationResult{
|
|
snapshot: current.ToProto(),
|
|
syncEvent: &tencentim.RoomEvent{
|
|
EventID: backgroundEvent.EventID,
|
|
RoomID: current.RoomID,
|
|
EventType: "room_background_changed",
|
|
ActorUserID: cmd.ActorUserID(),
|
|
RoomVersion: current.Version,
|
|
Attributes: map[string]string{
|
|
"background_id": int64String(cmd.BackgroundID),
|
|
"room_background_url": current.RoomExt[roomExtBackgroundKey],
|
|
},
|
|
},
|
|
}, []outbox.Record{backgroundEvent}, nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &roomv1.SetRoomBackgroundResponse{
|
|
Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
|
|
Room: result.snapshot,
|
|
Background: roomBackgroundToProto(background),
|
|
}, nil
|
|
}
|
|
|
|
func normalizeSaveRoomBackground(req *roomv1.SaveRoomBackgroundRequest) (string, int64, string, error) {
|
|
if req == nil || req.GetMeta() == nil {
|
|
return "", 0, "", xerr.New(xerr.InvalidArgument, "request is required")
|
|
}
|
|
roomID := strings.TrimSpace(req.GetRoomId())
|
|
if roomID == "" {
|
|
roomID = strings.TrimSpace(req.GetMeta().GetRoomId())
|
|
}
|
|
if !roomid.ValidStringID(roomID) {
|
|
return "", 0, "", xerr.New(xerr.InvalidArgument, "room_id is invalid")
|
|
}
|
|
actorUserID := req.GetMeta().GetActorUserId()
|
|
if actorUserID <= 0 {
|
|
return "", 0, "", xerr.New(xerr.InvalidArgument, "actor_user_id is required")
|
|
}
|
|
imageURL := strings.TrimSpace(req.GetImageUrl())
|
|
if imageURL == "" {
|
|
return "", 0, "", xerr.New(xerr.InvalidArgument, "image_url is required")
|
|
}
|
|
if utf8.RuneCountInString(imageURL) > maxRoomBackgroundRunes {
|
|
return "", 0, "", xerr.New(xerr.InvalidArgument, "image_url is too long")
|
|
}
|
|
return roomID, actorUserID, imageURL, nil
|
|
}
|
|
|
|
func normalizeListRoomBackgrounds(req *roomv1.ListRoomBackgroundsRequest) (string, int64, error) {
|
|
if req == nil || req.GetMeta() == nil {
|
|
return "", 0, xerr.New(xerr.InvalidArgument, "request is required")
|
|
}
|
|
roomID := strings.TrimSpace(req.GetRoomId())
|
|
if !roomid.ValidStringID(roomID) {
|
|
return "", 0, xerr.New(xerr.InvalidArgument, "room_id is invalid")
|
|
}
|
|
viewerUserID := req.GetViewerUserId()
|
|
if viewerUserID <= 0 {
|
|
return "", 0, xerr.New(xerr.InvalidArgument, "viewer_user_id is required")
|
|
}
|
|
return roomID, viewerUserID, nil
|
|
}
|
|
|
|
func normalizeSetRoomBackground(req *roomv1.SetRoomBackgroundRequest) (string, int64, int64, error) {
|
|
if req == nil || req.GetMeta() == nil {
|
|
return "", 0, 0, xerr.New(xerr.InvalidArgument, "request is required")
|
|
}
|
|
roomID := strings.TrimSpace(req.GetMeta().GetRoomId())
|
|
if !roomid.ValidStringID(roomID) {
|
|
return "", 0, 0, xerr.New(xerr.InvalidArgument, "room_id is invalid")
|
|
}
|
|
actorUserID := req.GetMeta().GetActorUserId()
|
|
if actorUserID <= 0 {
|
|
return "", 0, 0, xerr.New(xerr.InvalidArgument, "actor_user_id is required")
|
|
}
|
|
if strings.TrimSpace(req.GetMeta().GetCommandId()) == "" {
|
|
return "", 0, 0, xerr.New(xerr.InvalidArgument, "command_id is required")
|
|
}
|
|
if req.GetBackgroundId() <= 0 {
|
|
return "", 0, 0, xerr.New(xerr.InvalidArgument, "background_id is required")
|
|
}
|
|
return roomID, actorUserID, req.GetBackgroundId(), nil
|
|
}
|
|
|
|
func (s *Service) requireRoomOwnerMeta(ctx context.Context, roomID string, actorUserID int64) error {
|
|
meta, exists, err := s.repository.GetRoomMeta(ctx, roomID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !exists {
|
|
return xerr.New(xerr.NotFound, "room not found")
|
|
}
|
|
if meta.OwnerUserID != actorUserID {
|
|
return xerr.New(xerr.PermissionDenied, "permission denied")
|
|
}
|
|
if meta.Status != state.RoomStatusActive {
|
|
return xerr.New(xerr.Conflict, "room is not active")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func roomBackgroundToProto(background RoomBackgroundImage) *roomv1.RoomBackgroundImage {
|
|
if background.BackgroundID == 0 {
|
|
return nil
|
|
}
|
|
return &roomv1.RoomBackgroundImage{
|
|
BackgroundId: background.BackgroundID,
|
|
RoomId: background.RoomID,
|
|
ImageUrl: background.ImageURL,
|
|
CreatedByUserId: background.CreatedByUserID,
|
|
CreatedAtMs: background.CreatedAtMS,
|
|
UpdatedAtMs: background.UpdatedAtMS,
|
|
}
|
|
}
|
|
|
|
func roomBackgroundsToProto(backgrounds []RoomBackgroundImage) []*roomv1.RoomBackgroundImage {
|
|
out := make([]*roomv1.RoomBackgroundImage, 0, len(backgrounds))
|
|
for _, background := range backgrounds {
|
|
out = append(out, roomBackgroundToProto(background))
|
|
}
|
|
return out
|
|
}
|