328 lines
9.2 KiB
Go

package roomadmin
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"hyapp-admin-server/internal/integration/roomclient"
)
const (
roomPinStatusActive = "active"
roomPinStatusCancelled = "cancelled"
roomPinStatusExpired = "expired"
roomPinStatusAll = "all"
roomPinTypeRegion = "region"
roomPinTypeGlobal = "global"
roomPinTypeCountry = "country"
defaultRoomPinDays = int64(30)
maxRoomPinDays = int64(3650)
roomPinDayMillis = int64(24 * 60 * 60 * 1000)
)
type RoomPinRoom struct {
CoverURL string `json:"coverUrl"`
RegionName string `json:"regionName"`
RoomID string `json:"roomId"`
RoomShortID string `json:"roomShortId"`
Status string `json:"status"`
Title string `json:"title"`
VisibleRegionID int64 `json:"visibleRegionId"`
}
type RoomPin struct {
CancelledAtMS int64 `json:"cancelledAtMs"`
CancelledByAdminID uint `json:"cancelledByAdminId"`
CreatedAtMS int64 `json:"createdAtMs"`
CreatedByAdminID uint `json:"createdByAdminId"`
ExpiresAtMS int64 `json:"expiresAtMs"`
ID int64 `json:"id"`
PinType string `json:"pinType"`
PinnedAtMS int64 `json:"pinnedAtMs"`
RemainingMS int64 `json:"remainingMs"`
Room RoomPinRoom `json:"room"`
Status string `json:"status"`
UpdatedAtMS int64 `json:"updatedAtMs"`
User RoomOwner `json:"user"`
Weight int64 `json:"weight"`
}
func (s *Service) ListRoomPins(ctx context.Context, query roomPinListQuery) ([]RoomPin, int64, error) {
if s.roomClient == nil {
return nil, 0, fmt.Errorf("room service client is not configured")
}
query = normalizeRoomPinListQuery(query)
result, err := s.roomClient.ListRoomPins(ctx, roomclient.ListRoomPinsRequest{
Page: query.Page,
PageSize: query.PageSize,
Keyword: query.Keyword,
Status: query.Status,
RegionID: query.RegionID,
PinType: query.PinType,
})
if err != nil {
return nil, 0, err
}
items := roomPinsFromClient(result.Pins)
if err := s.fillRoomPinDetails(ctx, items); err != nil {
return nil, 0, err
}
return items, result.Total, nil
}
func (s *Service) CreateRoomPin(ctx context.Context, req createRoomPinRequest, actorID uint) (RoomPin, error) {
if s.roomClient == nil {
return RoomPin{}, fmt.Errorf("room service client is not configured")
}
input, err := normalizeCreateRoomPinRequest(req)
if err != nil {
return RoomPin{}, err
}
pin, err := s.roomClient.CreateRoomPin(ctx, roomclient.CreateRoomPinRequest{
RoomID: input.RoomID,
PinType: input.PinType,
Weight: input.Weight,
DurationDays: input.DurationDays,
PinnedAtMS: input.PinnedAtMS,
ExpiresAtMS: input.ExpiresAtMS,
AdminID: uint64(actorID),
})
if err != nil {
return RoomPin{}, mapRoomClientError(err)
}
item := roomPinFromClient(pin)
items := []RoomPin{item}
if err := s.fillRoomPinDetails(ctx, items); err != nil {
return RoomPin{}, err
}
return items[0], nil
}
func (s *Service) CancelRoomPin(ctx context.Context, pinID int64, actorID uint) (RoomPin, error) {
if s.roomClient == nil {
return RoomPin{}, fmt.Errorf("room service client is not configured")
}
if pinID <= 0 {
return RoomPin{}, fmt.Errorf("%w: 置顶 ID 不正确", ErrInvalidArgument)
}
pin, err := s.roomClient.CancelRoomPin(ctx, roomclient.CancelRoomPinRequest{PinID: pinID, AdminID: uint64(actorID)})
if err != nil {
return RoomPin{}, mapRoomClientError(err)
}
item := roomPinFromClient(pin)
items := []RoomPin{item}
if err := s.fillRoomPinDetails(ctx, items); err != nil {
return RoomPin{}, err
}
return items[0], nil
}
func normalizeRoomPinListQuery(query roomPinListQuery) roomPinListQuery {
if query.Page < 1 {
query.Page = 1
}
if query.PageSize < 1 {
query.PageSize = 20
}
if query.PageSize > 100 {
query.PageSize = 100
}
query.Keyword = strings.TrimSpace(query.Keyword)
query.Status = normalizeRoomPinStatus(query.Status)
query.PinType = normalizeRoomPinType(query.PinType)
if query.RegionID < 0 {
query.RegionID = 0
}
return query
}
func normalizeRoomPinType(pinType string) string {
switch strings.ToLower(strings.TrimSpace(pinType)) {
case roomPinTypeGlobal:
return roomPinTypeGlobal
case roomPinTypeCountry:
return roomPinTypeCountry
case roomPinTypeRegion:
return roomPinTypeRegion
default:
return ""
}
}
func normalizeRoomPinStatus(status string) string {
switch strings.ToLower(strings.TrimSpace(status)) {
case "", roomPinStatusActive:
return roomPinStatusActive
case roomPinStatusExpired:
return roomPinStatusExpired
case roomPinStatusCancelled:
return roomPinStatusCancelled
case roomPinStatusAll:
return roomPinStatusAll
default:
return roomPinStatusActive
}
}
func normalizeCreateRoomPinRequest(req createRoomPinRequest) (createRoomPinRequest, error) {
req.RoomID = strings.TrimSpace(req.RoomID)
if req.RoomID == "" || len(req.RoomID) > 64 {
return req, fmt.Errorf("%w: 请选择房间", ErrInvalidArgument)
}
if req.Weight < 0 {
return req, fmt.Errorf("%w: 权重不能小于 0", ErrInvalidArgument)
}
req.PinType = normalizeRoomPinType(req.PinType)
if req.PinType == "" {
req.PinType = roomPinTypeRegion
}
if req.PinnedAtMS <= 0 {
req.PinnedAtMS = time.Now().UTC().UnixMilli()
}
if req.ExpiresAtMS <= 0 && req.DurationDays == 0 {
req.DurationDays = defaultRoomPinDays
}
if req.ExpiresAtMS <= 0 {
req.ExpiresAtMS = req.PinnedAtMS + req.DurationDays*roomPinDayMillis
}
if req.ExpiresAtMS <= req.PinnedAtMS {
return req, fmt.Errorf("%w: 置顶时间区间不正确", ErrInvalidArgument)
}
durationDays := (req.ExpiresAtMS - req.PinnedAtMS + roomPinDayMillis - 1) / roomPinDayMillis
if durationDays < 1 || durationDays > maxRoomPinDays {
return req, fmt.Errorf("%w: 置顶时间必须在 1 到 3650 天之间", ErrInvalidArgument)
}
req.DurationDays = durationDays
return req, nil
}
func roomPinsFromClient(input []roomclient.RoomPin) []RoomPin {
out := make([]RoomPin, 0, len(input))
for _, pin := range input {
out = append(out, roomPinFromClient(pin))
}
return out
}
func roomPinFromClient(input roomclient.RoomPin) RoomPin {
status := input.Room.Status
if status != "active" {
status = "closed"
}
ownerUserID := strconv.FormatInt(input.Room.OwnerUserID, 10)
remainingMS := input.ExpiresAtMS - time.Now().UTC().UnixMilli()
if remainingMS < 0 {
remainingMS = 0
}
return RoomPin{
ID: input.ID,
PinType: firstNonEmpty(input.PinType, roomPinTypeRegion),
Weight: input.Weight,
Status: input.Status,
PinnedAtMS: input.PinnedAtMS,
ExpiresAtMS: input.ExpiresAtMS,
CancelledAtMS: input.CancelledAtMS,
CreatedByAdminID: uint(input.CreatedByAdminID),
CancelledByAdminID: uint(input.CancelledByAdminID),
CreatedAtMS: input.CreatedAtMS,
UpdatedAtMS: input.UpdatedAtMS,
RemainingMS: remainingMS,
User: RoomOwner{UserID: ownerUserID},
Room: RoomPinRoom{
RoomID: input.Room.RoomID,
RoomShortID: input.Room.RoomShortID,
VisibleRegionID: input.Room.VisibleRegionID,
Title: input.Room.Title,
CoverURL: input.Room.CoverURL,
Status: status,
},
}
}
type roomPinScanner interface {
Scan(dest ...any) error
}
func scanRoomPin(row roomPinScanner) (RoomPin, error) {
var item RoomPin
var ownerUserID int64
err := row.Scan(
&item.ID,
&item.Room.VisibleRegionID,
&item.Room.RoomID,
&item.Weight,
&item.Status,
&item.PinnedAtMS,
&item.ExpiresAtMS,
&item.CancelledAtMS,
&item.CreatedByAdminID,
&item.CancelledByAdminID,
&item.CreatedAtMS,
&item.UpdatedAtMS,
&item.Room.RoomShortID,
&item.Room.Title,
&item.Room.CoverURL,
&ownerUserID,
&item.Room.Status,
)
item.User = RoomOwner{UserID: strconv.FormatInt(ownerUserID, 10)}
item.RemainingMS = item.ExpiresAtMS - time.Now().UTC().UnixMilli()
if item.RemainingMS < 0 {
item.RemainingMS = 0
}
if item.Room.Status != "active" {
item.Room.Status = "closed"
}
return item, err
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return strings.TrimSpace(value)
}
}
return ""
}
func (s *Service) fillRoomPinDetails(ctx context.Context, items []RoomPin) error {
if s.userDB == nil || len(items) == 0 {
return nil
}
ownerIDs := make([]int64, 0, len(items))
regionIDs := make([]int64, 0, len(items))
for _, item := range items {
if ownerID, err := strconv.ParseInt(item.User.UserID, 10, 64); err == nil && ownerID > 0 {
ownerIDs = append(ownerIDs, ownerID)
}
if item.Room.VisibleRegionID > 0 {
regionIDs = append(regionIDs, item.Room.VisibleRegionID)
}
}
owners, err := s.queryRoomOwners(ctx, uniqueInt64s(ownerIDs))
if err != nil {
return err
}
regions, err := s.queryRegionNames(ctx, uniqueInt64s(regionIDs))
if err != nil {
return err
}
for index := range items {
if ownerID, err := strconv.ParseInt(items[index].User.UserID, 10, 64); err == nil {
if owner, ok := owners[ownerID]; ok {
items[index].User = owner
}
}
if name, ok := regions[items[index].Room.VisibleRegionID]; ok {
items[index].Room.RegionName = name
}
}
return nil
}