2026-05-22 14:22:27 +08:00

292 lines
7.6 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 regionimgroup
import (
"context"
"encoding/json"
"errors"
"fmt"
"math"
"net/http"
"strconv"
"strings"
"chatapp3-golang/internal/model"
"chatapp3-golang/internal/service/tencentim"
"gorm.io/gorm"
)
// SendRegionBroadcast 向当前区域 IM 群发送自定义广播消息。
func (s *Service) SendRegionBroadcast(ctx context.Context, req RegionBroadcastRequest) (*RegionBroadcastResponse, error) {
if s == nil || s.im == nil {
return nil, mapTencentIMError(tencentim.ErrNotConfigured)
}
sysOrigin := normalizeSysOrigin(req.SysOrigin)
messageType := strings.TrimSpace(req.Type)
if messageType == "" {
return nil, NewAppError(http.StatusBadRequest, "missing_broadcast_type", "type is required")
}
data := cloneBroadcastData(req.Data)
senderUserID := req.SenderUserID.Int64()
if senderUserID <= 0 {
senderUserID = firstInt64Value(data["senderUserId"], data["sendUserId"], data["userId"])
}
regionCode := normalizeRegionCode(req.RegionCode)
if regionCode == "" {
if senderUserID <= 0 {
return nil, NewAppError(http.StatusBadRequest, "missing_sender_user_id", "senderUserId is required when regionCode is empty")
}
resolvedRegionCode, err := s.resolveUserRegion(ctx, sysOrigin, senderUserID)
if err != nil {
return nil, err
}
regionCode = resolvedRegionCode
}
group, err := s.loadActiveRegionIMGroup(ctx, sysOrigin, regionCode)
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, NewAppError(http.StatusNotFound, "region_im_group_not_configured",
fmt.Sprintf("region im group is not configured sysOrigin=%s regionCode=%s", sysOrigin, regionCode))
}
if err != nil {
return nil, err
}
data["sysOrigin"] = sysOrigin
data["regionCode"] = regionCode
if senderUserID > 0 {
if err := s.enrichBroadcastSender(ctx, data, senderUserID); err != nil {
return nil, err
}
}
if acceptUserID := firstAcceptUserID(data); acceptUserID > 0 {
if err := s.enrichBroadcastAcceptUser(ctx, data, acceptUserID); err != nil {
return nil, err
}
}
refreshHighWinMessage(data, messageType)
body := map[string]any{
"type": messageType,
"data": data,
}
if err := s.im.SendCustomGroupMessage(ctx, group.GroupID, body); err != nil {
return nil, mapTencentIMError(err)
}
return &RegionBroadcastResponse{
SysOrigin: sysOrigin,
RegionCode: regionCode,
GroupID: group.GroupID,
Type: messageType,
}, nil
}
func cloneBroadcastData(data map[string]any) map[string]any {
if len(data) == 0 {
return map[string]any{}
}
clone := make(map[string]any, len(data))
for key, value := range data {
clone[key] = value
}
return clone
}
func (s *Service) enrichBroadcastSender(ctx context.Context, data map[string]any, userID int64) error {
profile, err := s.loadBroadcastUserProfile(ctx, userID)
if err != nil {
return err
}
account := strings.TrimSpace(profile.Account)
if account == "" {
account = strconv.FormatInt(userID, 10)
}
setIfMissing(data, "sendUserId", strconv.FormatInt(userID, 10))
setIfMissing(data, "senderUserId", strconv.FormatInt(userID, 10))
setIfMissing(data, "account", account)
setIfMissing(data, "actualAccount", account)
setIfMissing(data, "userNickname", profile.UserNickname)
setIfMissing(data, "nickname", profile.UserNickname)
setIfMissing(data, "userAvatar", profile.UserAvatar)
setIfMissing(data, "countryCode", profile.CountryCode)
setIfMissing(data, "countryName", profile.CountryName)
return nil
}
func (s *Service) enrichBroadcastAcceptUser(ctx context.Context, data map[string]any, userID int64) error {
profile, err := s.loadBroadcastUserProfile(ctx, userID)
if err != nil {
return err
}
account := strings.TrimSpace(profile.Account)
if account == "" {
account = strconv.FormatInt(userID, 10)
}
setIfMissing(data, "acceptUserId", strconv.FormatInt(userID, 10))
setIfMissing(data, "toUserId", strconv.FormatInt(userID, 10))
setIfMissing(data, "acceptAccount", account)
setIfMissing(data, "acceptNickname", profile.UserNickname)
setIfMissing(data, "toUserName", profile.UserNickname)
setIfMissing(data, "acceptUserAvatar", profile.UserAvatar)
setIfMissing(data, "toUserAvatarUrl", profile.UserAvatar)
return nil
}
func (s *Service) loadBroadcastUserProfile(ctx context.Context, userID int64) (model.UserBaseInfo, error) {
var user model.UserBaseInfo
err := s.db.WithContext(ctx).Where("id = ?", userID).First(&user).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
account := strconv.FormatInt(userID, 10)
return model.UserBaseInfo{
ID: userID,
Account: account,
UserNickname: account,
}, nil
}
if err != nil {
return model.UserBaseInfo{}, err
}
if strings.TrimSpace(user.Account) == "" {
user.Account = strconv.FormatInt(userID, 10)
}
if strings.TrimSpace(user.UserNickname) == "" {
user.UserNickname = user.Account
}
return user, nil
}
func setIfMissing(data map[string]any, key string, value any) {
if data == nil || value == nil {
return
}
if existing, exists := data[key]; exists && strings.TrimSpace(fmt.Sprint(existing)) != "" {
return
}
data[key] = value
}
func firstAcceptUserID(data map[string]any) int64 {
if id := firstInt64Value(data["acceptUserId"], data["toUserId"]); id > 0 {
return id
}
return firstInt64FromList(data["acceptUserIds"])
}
func firstInt64Value(values ...any) int64 {
for _, value := range values {
if parsed := int64FromAny(value); parsed > 0 {
return parsed
}
}
return 0
}
func refreshHighWinMessage(data map[string]any, messageType string) {
target := highWinPlayTarget(messageType)
if target == "" {
return
}
user := firstTextValue(data["userNickname"], data["nickname"], data["account"], data["actualAccount"], data["userId"])
multiple := firstTextValue(data["multiple"], data["winMultiple"])
amount := firstTextValue(data["winAmount"], data["awardAmount"])
if user == "" || multiple == "" || amount == "" {
return
}
data["msg"] = fmt.Sprintf("%s play %s get x %s win %s coins", user, target, multiple, amount)
}
func highWinPlayTarget(messageType string) string {
switch strings.TrimSpace(messageType) {
case "GAME_LUCKY_GIFT":
return "lucky_gift"
case "GAME_BAISHUN_WIN":
return "game"
default:
return ""
}
}
func firstTextValue(values ...any) string {
for _, value := range values {
text := strings.TrimSpace(fmt.Sprint(value))
if text != "" && text != "<nil>" {
return text
}
}
return ""
}
func firstInt64FromList(value any) int64 {
switch typed := value.(type) {
case []any:
for _, item := range typed {
if parsed := int64FromAny(item); parsed > 0 {
return parsed
}
}
case []int64:
for _, item := range typed {
if item > 0 {
return item
}
}
case []string:
for _, item := range typed {
if parsed := int64FromAny(item); parsed > 0 {
return parsed
}
}
default:
return int64FromAny(typed)
}
return 0
}
func int64FromAny(value any) int64 {
switch typed := value.(type) {
case nil:
return 0
case int:
return int64(typed)
case int8:
return int64(typed)
case int16:
return int64(typed)
case int32:
return int64(typed)
case int64:
return typed
case uint:
if uint64(typed) > math.MaxInt64 {
return 0
}
return int64(typed)
case uint8:
return int64(typed)
case uint16:
return int64(typed)
case uint32:
return int64(typed)
case uint64:
if typed > math.MaxInt64 {
return 0
}
return int64(typed)
case float32:
return int64(typed)
case float64:
return int64(typed)
case json.Number:
parsed, _ := typed.Int64()
return parsed
case string:
parsed, _ := strconv.ParseInt(strings.TrimSpace(typed), 10, 64)
return parsed
default:
parsed, _ := strconv.ParseInt(strings.TrimSpace(fmt.Sprint(typed)), 10, 64)
return parsed
}
}