Add test IM broadcast group prefix
This commit is contained in:
parent
6d35f421c0
commit
f5f2b828c6
@ -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")
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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: ""
|
||||
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ type TencentIMConfig struct {
|
||||
SDKAppID int64
|
||||
AdminIdentifier string
|
||||
CallbackAuthToken string
|
||||
GroupIDPrefix string
|
||||
}
|
||||
|
||||
// TencentRTCConfig 是腾讯云 RTC 服务端回调校验需要的最小配置。
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user