292 lines
7.6 KiB
Go
292 lines
7.6 KiB
Go
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
|
||
}
|
||
}
|