256 lines
6.6 KiB
Go
256 lines
6.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
|
|
}
|
|
}
|
|
|
|
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 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
|
|
}
|
|
}
|