Add test IM broadcast group prefix

This commit is contained in:
zhx 2026-06-02 15:49:00 +08:00
parent 6d35f421c0
commit f5f2b828c6
17 changed files with 225 additions and 17 deletions

View File

@ -40,11 +40,20 @@ type ParsedGroup struct {
// GlobalBroadcastGroupID 生成 App 全局播报群 ID。
// app_code 先 Normalize 再进入 GroupID确保 HTTP、gRPC、outbox 和腾讯回调看到的是同一租户标识。
func GlobalBroadcastGroupID(value string) (string, error) {
return GlobalBroadcastGroupIDWithPrefix("", value)
}
// GlobalBroadcastGroupIDWithPrefix 生成带环境前缀的 App 全局播报群 ID。
func GlobalBroadcastGroupIDWithPrefix(prefix string, value string) (string, error) {
prefix = strings.TrimSpace(prefix)
if err := validateGroupIDPrefix(prefix); err != nil {
return "", err
}
app := appcode.Normalize(value)
if err := validateAppCodeForGroupID(app); err != nil {
return "", err
}
groupID := "hy_" + app + globalSuffix
groupID := prefix + "hy_" + app + globalSuffix
if len(groupID) > roomid.MaxStringIDLength {
return "", fmt.Errorf("global broadcast group_id is too long")
}
@ -54,6 +63,15 @@ func GlobalBroadcastGroupID(value string) (string, error) {
// RegionBroadcastGroupID 生成区域播报群 ID。
// region_id <= 0 被拒绝GLOBAL/未知区域不能变成一个可 join 的区域群。
func RegionBroadcastGroupID(value string, regionID int64) (string, error) {
return RegionBroadcastGroupIDWithPrefix("", value, regionID)
}
// RegionBroadcastGroupIDWithPrefix 生成带环境前缀的区域播报群 ID。
func RegionBroadcastGroupIDWithPrefix(prefix string, value string, regionID int64) (string, error) {
prefix = strings.TrimSpace(prefix)
if err := validateGroupIDPrefix(prefix); err != nil {
return "", err
}
app := appcode.Normalize(value)
if err := validateAppCodeForGroupID(app); err != nil {
return "", err
@ -61,7 +79,7 @@ func RegionBroadcastGroupID(value string, regionID int64) (string, error) {
if regionID <= 0 {
return "", fmt.Errorf("region_id must be positive")
}
groupID := "hy_" + app + regionToken + strconv.FormatInt(regionID, 10)
groupID := prefix + "hy_" + app + regionToken + strconv.FormatInt(regionID, 10)
if len(groupID) > roomid.MaxStringIDLength {
return "", fmt.Errorf("region broadcast group_id is too long")
}
@ -76,6 +94,42 @@ func Parse(groupID string) ParsedGroup {
return ParsedGroup{Kind: KindUnknown}
}
if parsed := parseBroadcastGroupID(groupID); parsed.Kind != KindUnknown {
return parsed
}
if strings.HasPrefix(groupID, "hy_") && strings.Contains(groupID, "_bc_") {
return ParsedGroup{Kind: KindUnknown}
}
if roomid.ValidStringID(groupID) {
return ParsedGroup{Kind: KindRoom, RoomID: groupID}
}
return ParsedGroup{Kind: KindUnknown}
}
// ParseWithPrefix 使用配置的环境前缀解析腾讯云回调里的 GroupID。
func ParseWithPrefix(groupID string, prefix string) ParsedGroup {
groupID = strings.TrimSpace(groupID)
prefix = strings.TrimSpace(prefix)
if groupID == "" || validateGroupIDPrefix(prefix) != nil {
return ParsedGroup{Kind: KindUnknown}
}
if prefix == "" {
return Parse(groupID)
}
if after, ok := strings.CutPrefix(groupID, prefix); ok {
return parseBroadcastGroupID(after)
}
if strings.HasPrefix(groupID, "hy_") && strings.Contains(groupID, "_bc_") {
return ParsedGroup{Kind: KindUnknown}
}
if roomid.ValidStringID(groupID) {
return ParsedGroup{Kind: KindRoom, RoomID: groupID}
}
return ParsedGroup{Kind: KindUnknown}
}
func parseBroadcastGroupID(groupID string) ParsedGroup {
if strings.HasPrefix(groupID, "hy_") && strings.HasSuffix(groupID, globalSuffix) {
app := strings.TrimSuffix(strings.TrimPrefix(groupID, "hy_"), globalSuffix)
if validateAppCodeForGroupID(app) == nil {
@ -98,12 +152,32 @@ func Parse(groupID string) ParsedGroup {
}
}
if roomid.ValidStringID(groupID) {
return ParsedGroup{Kind: KindRoom, RoomID: groupID}
}
return ParsedGroup{Kind: KindUnknown}
}
func validateGroupIDPrefix(prefix string) error {
if prefix == "" {
return nil
}
for i := 0; i < len(prefix); i++ {
char := prefix[i]
if char >= 'a' && char <= 'z' {
continue
}
if char >= 'A' && char <= 'Z' {
continue
}
if char >= '0' && char <= '9' {
continue
}
if char == '_' || char == '-' {
continue
}
return fmt.Errorf("group_id_prefix contains unsupported character")
}
return nil
}
func validateAppCodeForGroupID(app string) error {
if app == "" {
return fmt.Errorf("app_code is required")

View File

@ -42,3 +42,35 @@ func TestParseRoomAndRejectInvalidBroadcast(t *testing.T) {
t.Fatal("app_code with spaces must be rejected")
}
}
func TestBroadcastGroupIDWithPrefix(t *testing.T) {
global, err := GlobalBroadcastGroupIDWithPrefix("test_", "lalu")
if err != nil {
t.Fatalf("GlobalBroadcastGroupIDWithPrefix failed: %v", err)
}
if global != "test_hy_lalu_bc_g" {
t.Fatalf("prefixed global group mismatch: %s", global)
}
region, err := RegionBroadcastGroupIDWithPrefix("test_", "lalu", 1001)
if err != nil {
t.Fatalf("RegionBroadcastGroupIDWithPrefix failed: %v", err)
}
if region != "test_hy_lalu_bc_r_1001" {
t.Fatalf("prefixed region group mismatch: %s", region)
}
parsedGlobal := ParseWithPrefix(global, "test_")
if parsedGlobal.Kind != KindGlobalBroadcast || parsedGlobal.AppCode != "lalu" {
t.Fatalf("prefixed global parse mismatch: %+v", parsedGlobal)
}
parsedRegion := ParseWithPrefix(region, "test_")
if parsedRegion.Kind != KindRegionBroadcast || parsedRegion.AppCode != "lalu" || parsedRegion.RegionID != 1001 {
t.Fatalf("prefixed region parse mismatch: %+v", parsedRegion)
}
if parsed := ParseWithPrefix("hy_lalu_bc_r_1001", "test_"); parsed.Kind != KindUnknown {
t.Fatalf("unprefixed broadcast group must be rejected when prefix is configured: %+v", parsed)
}
if parsed := ParseWithPrefix("room-1001", "test_"); parsed.Kind != KindRoom || parsed.RoomID != "room-1001" {
t.Fatalf("room parse with prefix mismatch: %+v", parsed)
}
}

View File

@ -34,6 +34,7 @@ tencent_im:
admin_user_sig_ttl: "24h"
endpoint: "adminapisgp.im.qcloud.com"
group_type: "ChatRoom"
group_id_prefix: "test_"
request_timeout: "5s"
broadcast:
enabled: true

View File

@ -136,6 +136,7 @@ func New(cfg config.Config) (*App, error) {
WorkerMaxRetry: cfg.Broadcast.WorkerMaxRetry,
WorkerPollInterval: cfg.Broadcast.WorkerPollInterval,
EnsureGroupsOnStartup: cfg.Broadcast.EnsureGroupsOnStartup,
GroupIDPrefix: cfg.TencentIM.GroupIDPrefix,
}, repository, broadcastPublisher, client.NewGRPCRegionSource(userConn))
broadcastSvc.SetSenderProfileSource(client.NewGRPCUserProfileSource(userConn))
luckyGiftSvc := luckygiftservice.New(repository,

View File

@ -87,6 +87,7 @@ type TencentIMConfig struct {
AdminUserSigTTL time.Duration `yaml:"admin_user_sig_ttl"`
Endpoint string `yaml:"endpoint"`
GroupType string `yaml:"group_type"`
GroupIDPrefix string `yaml:"group_id_prefix"`
RequestTimeout time.Duration `yaml:"request_timeout"`
}
@ -272,6 +273,7 @@ func Load(path string) (Config, error) {
if cfg.TencentIM.GroupType == "" {
cfg.TencentIM.GroupType = tencentim.DefaultGroupType
}
cfg.TencentIM.GroupIDPrefix = strings.TrimSpace(cfg.TencentIM.GroupIDPrefix)
if cfg.TencentIM.AdminUserSigTTL <= 0 {
cfg.TencentIM.AdminUserSigTTL = 24 * time.Hour
}

View File

@ -72,6 +72,7 @@ type Config struct {
WorkerMaxRetry int
WorkerPollInterval time.Duration
EnsureGroupsOnStartup bool
GroupIDPrefix string
}
// PublishInput 是服务端入队播报请求。
@ -125,7 +126,7 @@ func (s *Service) EnsureBroadcastGroups(ctx context.Context) (int, error) {
}
app := appcode.FromContext(ctx)
count := 0
globalID, err := imgroup.GlobalBroadcastGroupID(app)
globalID, err := imgroup.GlobalBroadcastGroupIDWithPrefix(s.cfg.GroupIDPrefix, app)
if err != nil {
return 0, xerr.New(xerr.InvalidArgument, err.Error())
}
@ -147,7 +148,7 @@ func (s *Service) EnsureBroadcastGroups(ctx context.Context) (int, error) {
// GLOBAL/未知区域不对应 IM 区域群避免客户端加入一个语义不清的“0 区域群”。
continue
}
groupID, err := imgroup.RegionBroadcastGroupID(app, regionID)
groupID, err := imgroup.RegionBroadcastGroupIDWithPrefix(s.cfg.GroupIDPrefix, app, regionID)
if err != nil {
return count, xerr.New(xerr.InvalidArgument, err.Error())
}
@ -165,7 +166,7 @@ func (s *Service) PublishRegionBroadcast(ctx context.Context, input PublishInput
if input.RegionID <= 0 {
return broadcastdomain.PublishResult{}, xerr.New(xerr.InvalidArgument, "region_id is required")
}
groupID, err := imgroup.RegionBroadcastGroupID(appcode.FromContext(ctx), input.RegionID)
groupID, err := imgroup.RegionBroadcastGroupIDWithPrefix(s.cfg.GroupIDPrefix, appcode.FromContext(ctx), input.RegionID)
if err != nil {
return broadcastdomain.PublishResult{}, xerr.New(xerr.InvalidArgument, err.Error())
}
@ -183,7 +184,7 @@ func (s *Service) PublishRoomBroadcast(ctx context.Context, roomID string, input
// PublishGlobalBroadcast 把全局播报写入持久化 outbox。
// 全局播报和区域播报共用同一张表scope/group_id 决定最终投递目标。
func (s *Service) PublishGlobalBroadcast(ctx context.Context, input PublishInput) (broadcastdomain.PublishResult, error) {
groupID, err := imgroup.GlobalBroadcastGroupID(appcode.FromContext(ctx))
groupID, err := imgroup.GlobalBroadcastGroupIDWithPrefix(s.cfg.GroupIDPrefix, appcode.FromContext(ctx))
if err != nil {
return broadcastdomain.PublishResult{}, xerr.New(xerr.InvalidArgument, err.Error())
}
@ -203,7 +204,7 @@ func (s *Service) RemoveRegionBroadcastMember(ctx context.Context, userID int64,
if s == nil || s.publisher == nil {
return "", false, xerr.New(xerr.Unavailable, "broadcast publisher is not configured")
}
groupID, err := imgroup.RegionBroadcastGroupID(appcode.FromContext(ctx), regionID)
groupID, err := imgroup.RegionBroadcastGroupIDWithPrefix(s.cfg.GroupIDPrefix, appcode.FromContext(ctx), regionID)
if err != nil {
return "", false, xerr.New(xerr.InvalidArgument, err.Error())
}

View File

@ -254,6 +254,36 @@ func TestRemoveRegionBroadcastMemberDeletesOnlyUserMembership(t *testing.T) {
}
}
func TestRegionBroadcastUsesConfiguredGroupPrefix(t *testing.T) {
repository := newFakeRepository()
publisher := &fakePublisher{}
service := New(Config{NodeID: "node-a", GroupIDPrefix: "test_"}, repository, publisher, nil)
result, err := service.PublishRegionBroadcast(appcode.WithContext(context.Background(), "lalu"), PublishInput{
EventID: "evt-prefixed",
BroadcastType: broadcastdomain.TypeSuperGift,
RegionID: 1001,
PayloadJSON: `{"ok":true}`,
})
if err != nil {
t.Fatalf("PublishRegionBroadcast failed: %v", err)
}
if result.GroupID != "test_hy_lalu_bc_r_1001" {
t.Fatalf("prefixed publish group mismatch: %+v", result)
}
if record := repository.records["evt-prefixed"]; record.GroupID != "test_hy_lalu_bc_r_1001" {
t.Fatalf("prefixed outbox group mismatch: %+v", record)
}
groupID, removed, err := service.RemoveRegionBroadcastMember(appcode.WithContext(context.Background(), "lalu"), 42, 1001)
if err != nil {
t.Fatalf("RemoveRegionBroadcastMember failed: %v", err)
}
if !removed || groupID != "test_hy_lalu_bc_r_1001" || publisher.deletedGroupID != "test_hy_lalu_bc_r_1001" {
t.Fatalf("prefixed remove mismatch: group_id=%s removed=%t deleted=%s", groupID, removed, publisher.deletedGroupID)
}
}
func mustGiftEnvelope(t *testing.T, gift *roomeventsv1.RoomGiftSent) *roomeventsv1.EventEnvelope {
t.Helper()
body, err := proto.Marshal(gift)

View File

@ -99,6 +99,7 @@ tencent_im:
user_sig_ttl: "24h"
endpoint: "adminapisgp.im.qcloud.com"
request_timeout: "5s"
group_id_prefix: "test_"
callback_url: ""
callback_auth_token: ""

View File

@ -130,6 +130,7 @@ func New(cfg config.Config) (*App, error) {
UserSigTTL: cfg.TencentIM.UserSigTTL,
AdminIdentifier: cfg.TencentIM.AdminIdentifier,
CallbackAuthToken: cfg.TencentIM.CallbackAuthToken,
GroupIDPrefix: cfg.TencentIM.GroupIDPrefix,
}, userProfileClient)
if cfg.TencentIM.SDKAppID > 0 && strings.TrimSpace(cfg.TencentIM.SecretKey) != "" {
imClient, err := tencentim.NewRESTClient(cfg.TencentIM.RESTConfig())

View File

@ -166,6 +166,8 @@ type TencentIMConfig struct {
Endpoint string `yaml:"endpoint"`
// RequestTimeout 是 gateway 调腾讯 IM REST 的请求超时。
RequestTimeout time.Duration `yaml:"request_timeout"`
// GroupIDPrefix 给测试服等隔离环境使用,正式服为空以保持既有腾讯 IM 群不变。
GroupIDPrefix string `yaml:"group_id_prefix"`
// CallbackURL 是在腾讯云 IM 控制台配置的服务端回调地址。
CallbackURL string `yaml:"callback_url"`
// CallbackAuthToken 是腾讯云 IM 回调鉴权 token只用于校验回调来源。
@ -462,6 +464,7 @@ func (cfg *Config) Normalize() error {
if cfg.TencentIM.RequestTimeout <= 0 {
cfg.TencentIM.RequestTimeout = 5 * time.Second
}
cfg.TencentIM.GroupIDPrefix = strings.TrimSpace(cfg.TencentIM.GroupIDPrefix)
cfg.TencentIM.CallbackURL = strings.TrimSpace(cfg.TencentIM.CallbackURL)
cfg.TencentIM.CallbackAuthToken = strings.TrimSpace(cfg.TencentIM.CallbackAuthToken)

View File

@ -12,6 +12,7 @@ type TencentIMConfig struct {
SDKAppID int64
AdminIdentifier string
CallbackAuthToken string
GroupIDPrefix string
}
// TencentRTCConfig 是腾讯云 RTC 服务端回调校验需要的最小配置。

View File

@ -92,7 +92,7 @@ func (h *Handler) handleTencentIMCallback(writer http.ResponseWriter, request *h
}
func (h *Handler) handleTencentIMJoinCallback(writer http.ResponseWriter, request *http.Request, body tencentIMCallbackBody) {
parsed := imgroup.Parse(body.GroupID)
parsed := imgroup.ParseWithPrefix(body.GroupID, h.tencentIM.GroupIDPrefix)
userID, ok := parseTencentIMUserID(body.Requestor)
if !ok {
writeTencentCallback(writer, http.StatusOK, 1, "join guard input invalid")
@ -149,7 +149,7 @@ func (h *Handler) handleTencentIMBroadcastJoinCallback(writer http.ResponseWrite
}
func (h *Handler) handleTencentIMSendCallback(writer http.ResponseWriter, request *http.Request, body tencentIMCallbackBody) {
parsed := imgroup.Parse(body.GroupID)
parsed := imgroup.ParseWithPrefix(body.GroupID, h.tencentIM.GroupIDPrefix)
switch parsed.Kind {
case imgroup.KindGlobalBroadcast, imgroup.KindRegionBroadcast:
if h.tencentIMCallbackSenderIsServer(body) {

View File

@ -63,6 +63,7 @@ type TencentIMConfig struct {
UserSigTTL time.Duration
AdminIdentifier string
CallbackAuthToken string
GroupIDPrefix string
}
// TencentIMAccountImporter 是 gateway 在签发 UserSig 前补偿导入账号的最小边界。
@ -333,7 +334,7 @@ func (h *Handler) issueTencentIMUserSig(writer http.ResponseWriter, request *htt
}
}
joinGroups, ok := imJoinGroupsForUser(app, user.GetRegionId())
joinGroups, ok := imJoinGroupsForUser(app, user.GetRegionId(), h.tencentIM.GroupIDPrefix)
if !ok {
// 生成 join_groups 失败代表服务端 app_code/region_id 配置异常,不能返回缺失群列表让客户端自行猜。
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
@ -363,8 +364,12 @@ type tencentIMJoinGroup struct {
RegionID int64 `json:"region_id,omitempty"`
}
func imJoinGroupsForUser(app string, regionID int64) ([]tencentIMJoinGroup, bool) {
globalGroupID, err := imgroup.GlobalBroadcastGroupID(app)
func imJoinGroupsForUser(app string, regionID int64, groupIDPrefix ...string) ([]tencentIMJoinGroup, bool) {
prefix := ""
if len(groupIDPrefix) > 0 {
prefix = groupIDPrefix[0]
}
globalGroupID, err := imgroup.GlobalBroadcastGroupIDWithPrefix(prefix, app)
if err != nil {
return nil, false
}
@ -375,7 +380,7 @@ func imJoinGroupsForUser(app string, regionID int64) ([]tencentIMJoinGroup, bool
}}
if regionID > 0 {
// 区域群来自 user-service 当前 region_id客户端不能自选区域群区域变更后下一次 UserSig 返回新群。
regionGroupID, err := imgroup.RegionBroadcastGroupID(app, regionID)
regionGroupID, err := imgroup.RegionBroadcastGroupIDWithPrefix(prefix, app, regionID)
if err != nil {
return nil, false
}

View File

@ -5294,6 +5294,36 @@ func TestTencentIMUserSigUsesAuthenticatedUserIDAndFailsClosed(t *testing.T) {
t.Fatalf("region join group mismatch: %+v", groups[1])
}
prefixedRouter := NewHandlerWithConfig(&fakeRoomClient{}, nil, nil, nil, TencentIMConfig{
SDKAppID: 1400000000,
SecretKey: "secret",
UserSigTTL: time.Hour,
GroupIDPrefix: "test_",
}, profileClient).Routes(auth.NewVerifier("secret"))
prefixedRequest := httptest.NewRequest(http.MethodGet, "/api/v1/im/usersig", nil)
prefixedRequest.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
prefixedRecorder := httptest.NewRecorder()
prefixedRouter.ServeHTTP(prefixedRecorder, prefixedRequest)
if prefixedRecorder.Code != http.StatusOK {
t.Fatalf("prefixed status mismatch: got %d body=%s", prefixedRecorder.Code, prefixedRecorder.Body.String())
}
var prefixedResponse httpkit.ResponseEnvelope
if err := json.NewDecoder(prefixedRecorder.Body).Decode(&prefixedResponse); err != nil {
t.Fatalf("decode prefixed response failed: %v", err)
}
prefixedData, ok := prefixedResponse.Data.(map[string]any)
if !ok {
t.Fatalf("prefixed response data mismatch: %+v", prefixedResponse)
}
prefixedGroups, ok := prefixedData["join_groups"].([]any)
if !ok || len(prefixedGroups) != 2 {
t.Fatalf("prefixed join_groups mismatch: %+v", prefixedData["join_groups"])
}
prefixedRegionGroup, ok := prefixedGroups[1].(map[string]any)
if !ok || prefixedRegionGroup["group_id"] != "test_hy_lalu_bc_r_1001" {
t.Fatalf("prefixed region join group mismatch: %+v", prefixedGroups[1])
}
missingConfigRouter := NewHandlerWithConfig(&fakeRoomClient{}, nil, nil, nil, TencentIMConfig{
SDKAppID: 1400000000,
UserSigTTL: time.Hour,
@ -5645,6 +5675,27 @@ func TestTencentIMBroadcastCallbacksUseRegionGuardAndServerOnlySend(t *testing.T
assertTencentCallback(t, adminSendRecorder, http.StatusOK, 0)
}
func TestTencentIMBroadcastCallbacksUseConfiguredGroupPrefix(t *testing.T) {
profileClient := &fakeUserProfileClient{regionID: 1001}
router := NewHandlerWithConfig(&fakeRoomClient{}, &fakeRoomGuardClient{}, nil, nil, TencentIMConfig{
SDKAppID: 1400000000,
AdminIdentifier: "administrator",
GroupIDPrefix: "test_",
}, profileClient).Routes(auth.NewVerifier("secret"))
joinBody := []byte(`{"CallbackCommand":"Group.CallbackBeforeApplyJoinGroup","GroupId":"test_hy_lalu_bc_r_1001","Requestor_Account":"42"}`)
joinRequest := httptest.NewRequest(http.MethodPost, "/api/v1/tencent-im/callback?SdkAppid=1400000000&CallbackCommand=Group.CallbackBeforeApplyJoinGroup", bytes.NewReader(joinBody))
joinRecorder := httptest.NewRecorder()
router.ServeHTTP(joinRecorder, joinRequest)
assertTencentCallback(t, joinRecorder, http.StatusOK, 0)
oldGroupBody := []byte(`{"CallbackCommand":"Group.CallbackBeforeApplyJoinGroup","GroupId":"hy_lalu_bc_r_1001","Requestor_Account":"42"}`)
oldGroupRequest := httptest.NewRequest(http.MethodPost, "/api/v1/tencent-im/callback?SdkAppid=1400000000&CallbackCommand=Group.CallbackBeforeApplyJoinGroup", bytes.NewReader(oldGroupBody))
oldGroupRecorder := httptest.NewRecorder()
router.ServeHTTP(oldGroupRecorder, oldGroupRequest)
assertTencentCallback(t, oldGroupRecorder, http.StatusOK, 1)
}
func TestTencentIMCallbackAuthAndUnknownCommand(t *testing.T) {
router := NewHandlerWithConfig(&fakeRoomClient{}, &fakeRoomGuardClient{}, nil, nil, TencentIMConfig{
SDKAppID: 1400000000,

View File

@ -45,6 +45,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler {
UserIdentityClient: h.userIdentityClient,
UserProfileClient: h.userProfileClient,
UserHostClient: h.userHostClient,
IMGroupIDPrefix: h.tencentIM.GroupIDPrefix,
})
userAPI := userapi.New(userapi.Config{
UserIdentityClient: h.userIdentityClient,
@ -97,6 +98,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler {
SDKAppID: h.tencentIM.SDKAppID,
AdminIdentifier: h.tencentIM.AdminIdentifier,
CallbackAuthToken: h.tencentIM.CallbackAuthToken,
GroupIDPrefix: h.tencentIM.GroupIDPrefix,
},
TencentRTC: callbackapi.TencentRTCConfig{
SDKAppID: h.tencentRTC.SDKAppID,

View File

@ -16,6 +16,7 @@ type Handler struct {
userIdentityClient client.UserIdentityClient
userProfileClient client.UserProfileClient
userHostClient client.UserHostClient
imGroupIDPrefix string
}
type Config struct {
@ -25,6 +26,7 @@ type Config struct {
UserIdentityClient client.UserIdentityClient
UserProfileClient client.UserProfileClient
UserHostClient client.UserHostClient
IMGroupIDPrefix string
}
func New(config Config) *Handler {
@ -35,6 +37,7 @@ func New(config Config) *Handler {
userIdentityClient: config.UserIdentityClient,
userProfileClient: config.UserProfileClient,
userHostClient: config.UserHostClient,
imGroupIDPrefix: config.IMGroupIDPrefix,
}
}

View File

@ -192,7 +192,7 @@ func (h *Handler) getCurrentRegionIMGroup(writer http.ResponseWriter, request *h
httpkit.WriteError(writer, request, http.StatusConflict, "voice_room_region_missing", "conflict")
return
}
groupID, err := imgroup.RegionBroadcastGroupID(appcode.FromContext(request.Context()), user.GetRegionId())
groupID, err := imgroup.RegionBroadcastGroupIDWithPrefix(h.imGroupIDPrefix, appcode.FromContext(request.Context()), user.GetRegionId())
if err != nil {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return