经理中心
This commit is contained in:
parent
e086a518bc
commit
995268fd70
@ -1,6 +1,7 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@ -160,7 +161,7 @@ func registerGameProviderRoutes(engine *gin.Engine, javaClient authGateway, regi
|
||||
writeError(c, common.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
|
||||
return
|
||||
}
|
||||
resp, err := provider.LaunchGame(c.Request.Context(), mustAuthUser(c), req, resolveClientIP(c))
|
||||
resp, err := launchProviderGame(c, registry, provider, req)
|
||||
if err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
@ -214,6 +215,25 @@ func resolveGameProvider(c *gin.Context, registry *gameprovider.Registry) (gamep
|
||||
return provider, true
|
||||
}
|
||||
|
||||
func launchProviderGame(c *gin.Context, registry *gameprovider.Registry, provider gameprovider.Provider, req gameprovider.LaunchRequest) (*gameprovider.LaunchResponse, error) {
|
||||
user := mustAuthUser(c)
|
||||
clientIP := resolveClientIP(c)
|
||||
resp, err := provider.LaunchGame(c.Request.Context(), user, req, clientIP)
|
||||
if err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
if !isGameNotFoundError(err) {
|
||||
return nil, err
|
||||
}
|
||||
// 旧客户端只能把热游列表项伪装成 LEADER 进入已有 WebView;这里再按 gameId 回到真实 provider,避免 LEADER 路由找不到 hg_*。
|
||||
return registry.LaunchGame(c.Request.Context(), user, req, clientIP)
|
||||
}
|
||||
|
||||
func isGameNotFoundError(err error) bool {
|
||||
var appErr *common.AppError
|
||||
return errors.As(err, &appErr) && appErr.Code == "game_not_found"
|
||||
}
|
||||
|
||||
type publicRoomGameItem struct {
|
||||
ID int64 `json:"id"`
|
||||
GameID string `json:"gameId"`
|
||||
@ -370,8 +390,14 @@ func publicLaunch(resp *gameprovider.LaunchResponse) *publicLaunchResponse {
|
||||
}
|
||||
|
||||
func appCompatProviderName(provider string) string {
|
||||
// 客户端历史上只认识 LEADER,不认识内部厂商名 LINGXIAN,所以出参继续保持旧字段值。
|
||||
if strings.EqualFold(strings.TrimSpace(provider), "LINGXIAN") {
|
||||
return "LEADER"
|
||||
}
|
||||
// 旧客户端没有 HOTGAME 页面;热游先按 LEADER 展示并进入已有 WebView,launch 路由再按 hg_* 分发回真实热游 provider。
|
||||
switch strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(provider), "_", "")) {
|
||||
case "HOTGAME", "REYOU", "LALU":
|
||||
return "LEADER"
|
||||
}
|
||||
return provider
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"chatapp3-golang/internal/common"
|
||||
@ -82,6 +83,41 @@ func TestPublicRoomGameListResponseMapsLingxianToLeaderForOldClients(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicRoomGameListResponseMapsHotgameAliasesToLeaderForOldClients(t *testing.T) {
|
||||
for _, alias := range []string{"HOTGAME", "HOT_GAME", "REYOU", "LALU"} {
|
||||
resp := publicRoomGameListResponse(&gameprovider.RoomGameListResponse{
|
||||
Items: []gameprovider.RoomGameListItem{
|
||||
{
|
||||
ID: 9530,
|
||||
GameID: "hg_1",
|
||||
GameType: alias,
|
||||
Provider: alias,
|
||||
ProviderGameID: "1",
|
||||
Name: "Hotgame",
|
||||
LaunchParams: map[string]any{"gameType": alias, "screenMode": "seven"},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if len(resp.Items) != 1 {
|
||||
t.Fatalf("%s items = %d, want 1", alias, len(resp.Items))
|
||||
}
|
||||
item := resp.Items[0]
|
||||
if item.Provider != "LEADER" {
|
||||
t.Fatalf("%s provider = %q, want LEADER", alias, item.Provider)
|
||||
}
|
||||
if item.GameType != "LEADER" {
|
||||
t.Fatalf("%s gameType = %q, want LEADER", alias, item.GameType)
|
||||
}
|
||||
if item.VendorType != "LEADER" {
|
||||
t.Fatalf("%s vendorType = %q, want LEADER", alias, item.VendorType)
|
||||
}
|
||||
if got := item.LaunchParams["gameType"]; got != "LEADER" {
|
||||
t.Fatalf("%s launchParams.gameType = %v, want LEADER", alias, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicLaunchKeepsLegacyBridgeAndProviderFields(t *testing.T) {
|
||||
launchConfig := map[string]any{"code": "launch-code"}
|
||||
resp := publicLaunch(&gameprovider.LaunchResponse{
|
||||
@ -234,6 +270,45 @@ func TestGameProviderRoutesReturnProvidersWhenVisibilityPolicyAllows(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderLaunchFallsBackToRegistryWhenLegacyProviderCannotFindGame(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
engine := gin.New()
|
||||
registerGameProviderRoutes(
|
||||
engine,
|
||||
routeStubAuthGateway{},
|
||||
gameprovider.NewRegistry(
|
||||
&routeLaunchStubProvider{key: "LINGXIAN"},
|
||||
&routeLaunchStubProvider{key: "HOTGAME", matchGameID: "hg_1"},
|
||||
),
|
||||
nil,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/app/game/providers/LEADER/launch", strings.NewReader(`{"roomId":"room-1","gameId":"hg_1"}`))
|
||||
req.Header.Set("Authorization", "Bearer token")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
engine.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
|
||||
}
|
||||
var payload struct {
|
||||
Body publicLaunchResponse `json:"body"`
|
||||
}
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("response json error = %v: %s", err, rec.Body.String())
|
||||
}
|
||||
if payload.Body.GameID != "hg_1" {
|
||||
t.Fatalf("gameId = %q, want hg_1", payload.Body.GameID)
|
||||
}
|
||||
if payload.Body.Provider != "LEADER" {
|
||||
t.Fatalf("provider = %q, want LEADER for old-client compat", payload.Body.Provider)
|
||||
}
|
||||
if payload.Body.GameSessionID != "hg-session" {
|
||||
t.Fatalf("gameSessionId = %q, want hg-session", payload.Body.GameSessionID)
|
||||
}
|
||||
}
|
||||
|
||||
type routeStubAuthGateway struct{}
|
||||
|
||||
func (routeStubAuthGateway) AuthenticateToken(context.Context, string) (integration.UserCredential, error) {
|
||||
@ -304,3 +379,51 @@ func (p *routeStubProvider) LaunchGame(context.Context, gameprovider.AuthUser, g
|
||||
func (p *routeStubProvider) CloseGame(context.Context, gameprovider.AuthUser, gameprovider.CloseRequest) (*gameprovider.RoomStateResponse, error) {
|
||||
return &gameprovider.RoomStateResponse{}, nil
|
||||
}
|
||||
|
||||
type routeLaunchStubProvider struct {
|
||||
key string
|
||||
matchGameID string
|
||||
}
|
||||
|
||||
func (p *routeLaunchStubProvider) Key() string { return p.key }
|
||||
|
||||
func (p *routeLaunchStubProvider) DisplayName() string { return p.key }
|
||||
|
||||
func (p *routeLaunchStubProvider) SupportsLaunch(_ context.Context, _ gameprovider.AuthUser, req gameprovider.LaunchRequest) (bool, error) {
|
||||
return p.matchGameID != "" && req.GameID == p.matchGameID, nil
|
||||
}
|
||||
|
||||
func (p *routeLaunchStubProvider) ListShortcutGames(context.Context, gameprovider.AuthUser, string) ([]gameprovider.RoomGameListItem, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *routeLaunchStubProvider) ListRoomGames(context.Context, gameprovider.AuthUser, string, string) (*gameprovider.RoomGameListResponse, error) {
|
||||
return &gameprovider.RoomGameListResponse{}, nil
|
||||
}
|
||||
|
||||
func (p *routeLaunchStubProvider) GetRoomState(context.Context, gameprovider.AuthUser, string) (*gameprovider.RoomStateResponse, error) {
|
||||
return &gameprovider.RoomStateResponse{}, nil
|
||||
}
|
||||
|
||||
func (p *routeLaunchStubProvider) LaunchGame(_ context.Context, _ gameprovider.AuthUser, req gameprovider.LaunchRequest, _ string) (*gameprovider.LaunchResponse, error) {
|
||||
if p.matchGameID == "" || req.GameID != p.matchGameID {
|
||||
return nil, common.NewAppError(http.StatusNotFound, "game_not_found", "game not found")
|
||||
}
|
||||
return &gameprovider.LaunchResponse{
|
||||
GameSessionID: "hg-session",
|
||||
Provider: p.key,
|
||||
GameID: req.GameID,
|
||||
ProviderGameID: "1",
|
||||
Entry: gameprovider.LaunchEntry{LaunchMode: "H5_REMOTE"},
|
||||
RoomState: gameprovider.RoomStateResponse{
|
||||
RoomID: req.RoomID,
|
||||
State: "PLAYING",
|
||||
Provider: p.key,
|
||||
GameSessionID: "hg-session",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *routeLaunchStubProvider) CloseGame(context.Context, gameprovider.AuthUser, gameprovider.CloseRequest) (*gameprovider.RoomStateResponse, error) {
|
||||
return &gameprovider.RoomStateResponse{}, nil
|
||||
}
|
||||
|
||||
@ -251,6 +251,8 @@ func normalizeProviderAlias(key string) string {
|
||||
switch normalizeKey(key) {
|
||||
case "LEADER":
|
||||
return "LINGXIAN"
|
||||
case "HOTGAME", "HOT_GAME", "REYOU", "LALU":
|
||||
return "HOTGAME"
|
||||
default:
|
||||
return normalizeKey(key)
|
||||
}
|
||||
|
||||
@ -80,6 +80,20 @@ func TestRegistryResolveMapsLeaderAliasToLingxian(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryResolveMapsHotgameAliases(t *testing.T) {
|
||||
registry := NewRegistry(&stubProvider{key: "HOTGAME", name: "热游"})
|
||||
|
||||
for _, alias := range []string{"HOTGAME", "hot_game", "REYOU", "lalu"} {
|
||||
provider, err := registry.Resolve(alias)
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve(%q) error = %v", alias, err)
|
||||
}
|
||||
if provider.Key() != "HOTGAME" {
|
||||
t.Fatalf("Resolve(%q) key = %q, want HOTGAME", alias, provider.Key())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryListKeepsRegistrationOrder(t *testing.T) {
|
||||
registry := NewRegistry(
|
||||
&stubProvider{key: "BAISHUN", name: "百顺"},
|
||||
|
||||
@ -124,7 +124,7 @@ func (s *Service) ListBDLeaders(ctx context.Context, user AuthUser) (*BDLeaderLi
|
||||
}
|
||||
|
||||
func (s *Service) AddBDLeader(ctx context.Context, user AuthUser, req AddBDLeaderRequest) (*AddBDLeaderResponse, error) {
|
||||
manager, err := s.requireManagerInfo(ctx, user)
|
||||
manager, err := s.requireManagerFeature(ctx, user, managerFeatureAddBDLeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -14,13 +14,17 @@ import (
|
||||
)
|
||||
|
||||
type teamManagerInfoRow struct {
|
||||
ID int64 `gorm:"column:id;primaryKey"`
|
||||
UserID int64 `gorm:"column:user_id"`
|
||||
Origin string `gorm:"column:sys_origin"`
|
||||
Contact string `gorm:"column:contact"`
|
||||
RegionID string `gorm:"column:region_id"`
|
||||
CreateUser int64 `gorm:"column:create_user"`
|
||||
UpdateUser int64 `gorm:"column:update_user"`
|
||||
ID int64 `gorm:"column:id;primaryKey"`
|
||||
UserID int64 `gorm:"column:user_id"`
|
||||
Origin string `gorm:"column:sys_origin"`
|
||||
Contact string `gorm:"column:contact"`
|
||||
RegionID string `gorm:"column:region_id"`
|
||||
CanAddBDLeader *bool `gorm:"column:can_add_bd_leader"`
|
||||
CanGiftProps *bool `gorm:"column:can_gift_props"`
|
||||
CanBanUser *bool `gorm:"column:can_ban_user"`
|
||||
CanUnbanUser *bool `gorm:"column:can_unban_user"`
|
||||
CreateUser int64 `gorm:"column:create_user"`
|
||||
UpdateUser int64 `gorm:"column:update_user"`
|
||||
}
|
||||
|
||||
func (teamManagerInfoRow) TableName() string {
|
||||
@ -73,7 +77,8 @@ func (propsNobleVIPAbilityRow) TableName() string {
|
||||
}
|
||||
|
||||
func (s *Service) GetProfile(ctx context.Context, user AuthUser) (*ProfileResponse, error) {
|
||||
if err := s.ensureManager(ctx, user); err != nil {
|
||||
manager, err := s.requireManagerInfo(ctx, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -81,7 +86,7 @@ func (s *Service) GetProfile(ctx context.Context, user AuthUser) (*ProfileRespon
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ProfileResponse{Manager: view}, nil
|
||||
return &ProfileResponse{Manager: view, Features: managerFeaturesFromRow(manager)}, nil
|
||||
}
|
||||
|
||||
func (s *Service) SearchUser(ctx context.Context, user AuthUser, account string) (*UserSearchResponse, error) {
|
||||
@ -148,7 +153,7 @@ func (s *Service) ListProps(ctx context.Context, user AuthUser, propsType string
|
||||
}
|
||||
|
||||
func (s *Service) SendProps(ctx context.Context, user AuthUser, req SendPropsRequest) (*SendPropsResponse, error) {
|
||||
if err := s.ensureManager(ctx, user); err != nil {
|
||||
if _, err := s.requireManagerFeature(ctx, user, managerFeatureGiftProps); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.AcceptUserID.Int64() <= 0 {
|
||||
@ -243,7 +248,7 @@ func (s *Service) SendProps(ctx context.Context, user AuthUser, req SendPropsReq
|
||||
}
|
||||
|
||||
func (s *Service) BanUser(ctx context.Context, user AuthUser, req BanUserRequest) (*BanUserResponse, error) {
|
||||
if err := s.ensureManager(ctx, user); err != nil {
|
||||
if _, err := s.requireManagerFeature(ctx, user, managerFeatureBanUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.UserID.Int64() <= 0 {
|
||||
@ -290,7 +295,7 @@ func (s *Service) BanUser(ctx context.Context, user AuthUser, req BanUserRequest
|
||||
}
|
||||
|
||||
func (s *Service) UnbanUser(ctx context.Context, user AuthUser, req UnbanUserRequest) (*UnbanUserResponse, error) {
|
||||
if err := s.ensureManager(ctx, user); err != nil {
|
||||
if _, err := s.requireManagerFeature(ctx, user, managerFeatureUnbanUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.UserID.Int64() <= 0 {
|
||||
@ -332,6 +337,48 @@ func (s *Service) ensureManager(ctx context.Context, user AuthUser) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// requireManagerFeature 先确认调用人仍然是团队经理,再检查 admin 给这个经理打开的单项 H5 操作权限。
|
||||
// 这样前端隐藏按钮和后端写操作使用同一张 team_manager_base_info 表做判断,H5 被绕过时也会被服务层拦截。
|
||||
func (s *Service) requireManagerFeature(ctx context.Context, user AuthUser, feature managerFeature) (teamManagerInfoRow, error) {
|
||||
manager, err := s.requireManagerInfo(ctx, user)
|
||||
if err != nil {
|
||||
return teamManagerInfoRow{}, err
|
||||
}
|
||||
features := managerFeaturesFromRow(manager)
|
||||
if !features.enabled(feature) {
|
||||
return teamManagerInfoRow{}, forbidden("manager_feature_disabled", "manager feature is disabled")
|
||||
}
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
func managerFeaturesFromRow(row teamManagerInfoRow) ManagerFeatures {
|
||||
return ManagerFeatures{
|
||||
AddBDLeader: defaultEnabled(row.CanAddBDLeader),
|
||||
GiftProps: defaultEnabled(row.CanGiftProps),
|
||||
BanUser: defaultEnabled(row.CanBanUser),
|
||||
UnbanUser: defaultEnabled(row.CanUnbanUser),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultEnabled(value *bool) bool {
|
||||
return value == nil || *value
|
||||
}
|
||||
|
||||
func (features ManagerFeatures) enabled(feature managerFeature) bool {
|
||||
switch feature {
|
||||
case managerFeatureAddBDLeader:
|
||||
return features.AddBDLeader
|
||||
case managerFeatureGiftProps:
|
||||
return features.GiftProps
|
||||
case managerFeatureBanUser:
|
||||
return features.BanUser
|
||||
case managerFeatureUnbanUser:
|
||||
return features.UnbanUser
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) findUserView(ctx context.Context, account string) (UserView, error) {
|
||||
if userID, err := strconv.ParseInt(account, 10, 64); err == nil && userID > 0 {
|
||||
if row, err := s.loadUserRowByID(ctx, userID); err == nil {
|
||||
|
||||
@ -167,6 +167,26 @@ func TestGetProfileToleratesRechargeQueryFailure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProfileReturnsManagerFeatures(t *testing.T) {
|
||||
service, _ := newManagerCenterTestService(t)
|
||||
db := service.db.(*gorm.DB)
|
||||
seedManagerWithFeatures(t, db, 1, ManagerFeatures{
|
||||
AddBDLeader: true,
|
||||
GiftProps: false,
|
||||
BanUser: true,
|
||||
UnbanUser: false,
|
||||
})
|
||||
seedUsers(t, db, userBaseInfoRow{ID: 1, Account: "1"})
|
||||
|
||||
resp, err := service.GetProfile(context.Background(), AuthUser{UserID: 1})
|
||||
if err != nil {
|
||||
t.Fatalf("GetProfile() error = %v", err)
|
||||
}
|
||||
if !resp.Features.AddBDLeader || resp.Features.GiftProps || !resp.Features.BanUser || resp.Features.UnbanUser {
|
||||
t.Fatalf("features = %+v, want row switches returned", resp.Features)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddBDLeaderCreatesLeaderAndListEntry(t *testing.T) {
|
||||
service, _ := newManagerCenterTestService(t)
|
||||
db := service.db.(*gorm.DB)
|
||||
@ -234,6 +254,24 @@ func TestAddBDLeaderRejectsTargetFromOtherOrigin(t *testing.T) {
|
||||
assertAppErrorCode(t, err, "bd_leader_origin_mismatch")
|
||||
}
|
||||
|
||||
func TestAddBDLeaderRejectsDisabledFeature(t *testing.T) {
|
||||
service, _ := newManagerCenterTestService(t)
|
||||
db := service.db.(*gorm.DB)
|
||||
seedManagerWithFeatures(t, db, 1, ManagerFeatures{GiftProps: true, BanUser: true, UnbanUser: true})
|
||||
seedUsers(t, db, userBaseInfoRow{ID: 2, Account: "2002", OriginSys: "LIKEI"})
|
||||
|
||||
_, err := service.AddBDLeader(context.Background(), AuthUser{UserID: 1, SysOrigin: "LIKEI"}, AddBDLeaderRequest{UserID: ID(2)})
|
||||
assertAppErrorCode(t, err, "manager_feature_disabled")
|
||||
|
||||
var count int64
|
||||
if err := db.Model(&bdLeaderRow{}).Where("user_id = ?", int64(2)).Count(&count).Error; err != nil {
|
||||
t.Fatalf("count bd leaders: %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("bd leader count = %d, want 0", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendPropsRejectsNonAdminFree(t *testing.T) {
|
||||
service, fake := newManagerCenterTestService(t)
|
||||
db := service.db.(*gorm.DB)
|
||||
@ -294,6 +332,23 @@ func TestSendNobleVIPGivesAccessoriesAndSwitchesMax(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendPropsRejectsDisabledFeature(t *testing.T) {
|
||||
service, fake := newManagerCenterTestService(t)
|
||||
db := service.db.(*gorm.DB)
|
||||
seedManagerWithFeatures(t, db, 1, ManagerFeatures{AddBDLeader: true, BanUser: true, UnbanUser: true})
|
||||
seedUsers(t, db, userBaseInfoRow{ID: 2, Account: "2"})
|
||||
seedProps(t, db, propsSourceRecordRow{ID: 101, Type: propsTypeRide, Name: "Giftable ride", AdminFree: true})
|
||||
|
||||
_, err := service.SendProps(context.Background(), AuthUser{UserID: 1}, SendPropsRequest{
|
||||
AcceptUserID: ID(2),
|
||||
PropsID: ID(101),
|
||||
})
|
||||
assertAppErrorCode(t, err, "manager_feature_disabled")
|
||||
if len(fake.giveRequests) != 0 {
|
||||
t.Fatalf("give requests = %+v, want none", fake.giveRequests)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendBadgeActivatesBadgeBackpack(t *testing.T) {
|
||||
service, fake := newManagerCenterTestService(t)
|
||||
db := service.db.(*gorm.DB)
|
||||
@ -513,6 +568,19 @@ func assertManagerVIPCleanupBadge(t *testing.T, db *gorm.DB, badgeID int64, expi
|
||||
}
|
||||
}
|
||||
|
||||
func TestBanUserRejectsDisabledFeature(t *testing.T) {
|
||||
service, fake := newManagerCenterTestService(t)
|
||||
db := service.db.(*gorm.DB)
|
||||
seedManagerWithFeatures(t, db, 1, ManagerFeatures{AddBDLeader: true, GiftProps: true, UnbanUser: true})
|
||||
seedUsers(t, db, userBaseInfoRow{ID: 2, Account: "2"})
|
||||
|
||||
_, err := service.BanUser(context.Background(), AuthUser{UserID: 1}, BanUserRequest{UserID: ID(2)})
|
||||
assertAppErrorCode(t, err, "manager_feature_disabled")
|
||||
if len(fake.punishRequests) != 0 {
|
||||
t.Fatalf("punish requests = %+v, want none", fake.punishRequests)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBanUserRejectsRechargedUser(t *testing.T) {
|
||||
service, fake := newManagerCenterTestService(t)
|
||||
db := service.db.(*gorm.DB)
|
||||
@ -592,6 +660,19 @@ func TestBanUserWithoutRechargeCallsPunishment(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnbanUserRejectsDisabledFeature(t *testing.T) {
|
||||
service, fake := newManagerCenterTestService(t)
|
||||
db := service.db.(*gorm.DB)
|
||||
seedManagerWithFeatures(t, db, 1, ManagerFeatures{AddBDLeader: true, GiftProps: true, BanUser: true})
|
||||
seedUsers(t, db, userBaseInfoRow{ID: 2, Account: "2", AccountStatus: accountStatusArchive})
|
||||
|
||||
_, err := service.UnbanUser(context.Background(), AuthUser{UserID: 1}, UnbanUserRequest{UserID: ID(2)})
|
||||
assertAppErrorCode(t, err, "manager_feature_disabled")
|
||||
if len(fake.unblockRequests) != 0 {
|
||||
t.Fatalf("unblock requests = %+v, want none", fake.unblockRequests)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnbanUserRejectsNormalUser(t *testing.T) {
|
||||
service, fake := newManagerCenterTestService(t)
|
||||
db := service.db.(*gorm.DB)
|
||||
@ -682,11 +763,34 @@ func newManagerCenterTestService(t *testing.T) (*Service, *fakeManagerGateway) {
|
||||
|
||||
func seedManager(t *testing.T, db *gorm.DB, userID int64) {
|
||||
t.Helper()
|
||||
if err := db.Create(&teamManagerInfoRow{ID: userID, UserID: userID, Origin: "LIKEI", RegionID: "SA"}).Error; err != nil {
|
||||
seedManagerWithFeatures(t, db, userID, ManagerFeatures{
|
||||
AddBDLeader: true,
|
||||
GiftProps: true,
|
||||
BanUser: true,
|
||||
UnbanUser: true,
|
||||
})
|
||||
}
|
||||
|
||||
func seedManagerWithFeatures(t *testing.T, db *gorm.DB, userID int64, features ManagerFeatures) {
|
||||
t.Helper()
|
||||
if err := db.Create(&teamManagerInfoRow{
|
||||
ID: userID,
|
||||
UserID: userID,
|
||||
Origin: "LIKEI",
|
||||
RegionID: "SA",
|
||||
CanAddBDLeader: boolPtr(features.AddBDLeader),
|
||||
CanGiftProps: boolPtr(features.GiftProps),
|
||||
CanBanUser: boolPtr(features.BanUser),
|
||||
CanUnbanUser: boolPtr(features.UnbanUser),
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("seed manager: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func boolPtr(value bool) *bool {
|
||||
return &value
|
||||
}
|
||||
|
||||
func seedUsers(t *testing.T, db *gorm.DB, rows ...userBaseInfoRow) {
|
||||
t.Helper()
|
||||
for _, row := range rows {
|
||||
|
||||
@ -38,6 +38,15 @@ const (
|
||||
accountStatusArchiveDevice = "ARCHIVE_DEVICE"
|
||||
)
|
||||
|
||||
type managerFeature string
|
||||
|
||||
const (
|
||||
managerFeatureAddBDLeader managerFeature = "add_bd_leader"
|
||||
managerFeatureGiftProps managerFeature = "gift_props"
|
||||
managerFeatureBanUser managerFeature = "ban_user"
|
||||
managerFeatureUnbanUser managerFeature = "unban_user"
|
||||
)
|
||||
|
||||
type AppError = common.AppError
|
||||
type AuthUser = common.AuthUser
|
||||
|
||||
@ -106,7 +115,15 @@ func (id *ID) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
|
||||
type ProfileResponse struct {
|
||||
Manager UserView `json:"manager"`
|
||||
Manager UserView `json:"manager"`
|
||||
Features ManagerFeatures `json:"features"`
|
||||
}
|
||||
|
||||
type ManagerFeatures struct {
|
||||
AddBDLeader bool `json:"addBdLeader"`
|
||||
GiftProps bool `json:"giftProps"`
|
||||
BanUser bool `json:"banUser"`
|
||||
UnbanUser bool `json:"unbanUser"`
|
||||
}
|
||||
|
||||
type UserSearchResponse struct {
|
||||
|
||||
61
migrations/054_team_manager_feature_switch.sql
Normal file
61
migrations/054_team_manager_feature_switch.sql
Normal file
@ -0,0 +1,61 @@
|
||||
SET @table_schema = DATABASE();
|
||||
|
||||
SET @ddl = IF(
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @table_schema
|
||||
AND TABLE_NAME = 'team_manager_base_info'
|
||||
AND COLUMN_NAME = 'can_add_bd_leader'
|
||||
),
|
||||
'SELECT 1',
|
||||
"ALTER TABLE `team_manager_base_info` ADD COLUMN `can_add_bd_leader` tinyint(1) NOT NULL DEFAULT 1 COMMENT '经理中心是否允许添加BD Leader' AFTER `region_id`"
|
||||
);
|
||||
PREPARE stmt FROM @ddl;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @ddl = IF(
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @table_schema
|
||||
AND TABLE_NAME = 'team_manager_base_info'
|
||||
AND COLUMN_NAME = 'can_gift_props'
|
||||
),
|
||||
'SELECT 1',
|
||||
"ALTER TABLE `team_manager_base_info` ADD COLUMN `can_gift_props` tinyint(1) NOT NULL DEFAULT 1 COMMENT '经理中心是否允许赠送道具' AFTER `can_add_bd_leader`"
|
||||
);
|
||||
PREPARE stmt FROM @ddl;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @ddl = IF(
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @table_schema
|
||||
AND TABLE_NAME = 'team_manager_base_info'
|
||||
AND COLUMN_NAME = 'can_ban_user'
|
||||
),
|
||||
'SELECT 1',
|
||||
"ALTER TABLE `team_manager_base_info` ADD COLUMN `can_ban_user` tinyint(1) NOT NULL DEFAULT 1 COMMENT '经理中心是否允许封禁用户' AFTER `can_gift_props`"
|
||||
);
|
||||
PREPARE stmt FROM @ddl;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @ddl = IF(
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @table_schema
|
||||
AND TABLE_NAME = 'team_manager_base_info'
|
||||
AND COLUMN_NAME = 'can_unban_user'
|
||||
),
|
||||
'SELECT 1',
|
||||
"ALTER TABLE `team_manager_base_info` ADD COLUMN `can_unban_user` tinyint(1) NOT NULL DEFAULT 1 COMMENT '经理中心是否允许解封用户' AFTER `can_ban_user`"
|
||||
);
|
||||
PREPARE stmt FROM @ddl;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
Loading…
x
Reference in New Issue
Block a user