经理中心
This commit is contained in:
parent
e086a518bc
commit
995268fd70
@ -1,6 +1,7 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -160,7 +161,7 @@ func registerGameProviderRoutes(engine *gin.Engine, javaClient authGateway, regi
|
|||||||
writeError(c, common.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
|
writeError(c, common.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp, err := provider.LaunchGame(c.Request.Context(), mustAuthUser(c), req, resolveClientIP(c))
|
resp, err := launchProviderGame(c, registry, provider, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(c, err)
|
writeError(c, err)
|
||||||
return
|
return
|
||||||
@ -214,6 +215,25 @@ func resolveGameProvider(c *gin.Context, registry *gameprovider.Registry) (gamep
|
|||||||
return provider, true
|
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 {
|
type publicRoomGameItem struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
GameID string `json:"gameId"`
|
GameID string `json:"gameId"`
|
||||||
@ -370,8 +390,14 @@ func publicLaunch(resp *gameprovider.LaunchResponse) *publicLaunchResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func appCompatProviderName(provider string) string {
|
func appCompatProviderName(provider string) string {
|
||||||
|
// 客户端历史上只认识 LEADER,不认识内部厂商名 LINGXIAN,所以出参继续保持旧字段值。
|
||||||
if strings.EqualFold(strings.TrimSpace(provider), "LINGXIAN") {
|
if strings.EqualFold(strings.TrimSpace(provider), "LINGXIAN") {
|
||||||
return "LEADER"
|
return "LEADER"
|
||||||
}
|
}
|
||||||
|
// 旧客户端没有 HOTGAME 页面;热游先按 LEADER 展示并进入已有 WebView,launch 路由再按 hg_* 分发回真实热游 provider。
|
||||||
|
switch strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(provider), "_", "")) {
|
||||||
|
case "HOTGAME", "REYOU", "LALU":
|
||||||
|
return "LEADER"
|
||||||
|
}
|
||||||
return provider
|
return provider
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"chatapp3-golang/internal/common"
|
"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) {
|
func TestPublicLaunchKeepsLegacyBridgeAndProviderFields(t *testing.T) {
|
||||||
launchConfig := map[string]any{"code": "launch-code"}
|
launchConfig := map[string]any{"code": "launch-code"}
|
||||||
resp := publicLaunch(&gameprovider.LaunchResponse{
|
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{}
|
type routeStubAuthGateway struct{}
|
||||||
|
|
||||||
func (routeStubAuthGateway) AuthenticateToken(context.Context, string) (integration.UserCredential, error) {
|
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) {
|
func (p *routeStubProvider) CloseGame(context.Context, gameprovider.AuthUser, gameprovider.CloseRequest) (*gameprovider.RoomStateResponse, error) {
|
||||||
return &gameprovider.RoomStateResponse{}, nil
|
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) {
|
switch normalizeKey(key) {
|
||||||
case "LEADER":
|
case "LEADER":
|
||||||
return "LINGXIAN"
|
return "LINGXIAN"
|
||||||
|
case "HOTGAME", "HOT_GAME", "REYOU", "LALU":
|
||||||
|
return "HOTGAME"
|
||||||
default:
|
default:
|
||||||
return normalizeKey(key)
|
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) {
|
func TestRegistryListKeepsRegistrationOrder(t *testing.T) {
|
||||||
registry := NewRegistry(
|
registry := NewRegistry(
|
||||||
&stubProvider{key: "BAISHUN", name: "百顺"},
|
&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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,13 +14,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type teamManagerInfoRow struct {
|
type teamManagerInfoRow struct {
|
||||||
ID int64 `gorm:"column:id;primaryKey"`
|
ID int64 `gorm:"column:id;primaryKey"`
|
||||||
UserID int64 `gorm:"column:user_id"`
|
UserID int64 `gorm:"column:user_id"`
|
||||||
Origin string `gorm:"column:sys_origin"`
|
Origin string `gorm:"column:sys_origin"`
|
||||||
Contact string `gorm:"column:contact"`
|
Contact string `gorm:"column:contact"`
|
||||||
RegionID string `gorm:"column:region_id"`
|
RegionID string `gorm:"column:region_id"`
|
||||||
CreateUser int64 `gorm:"column:create_user"`
|
CanAddBDLeader *bool `gorm:"column:can_add_bd_leader"`
|
||||||
UpdateUser int64 `gorm:"column:update_user"`
|
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 {
|
func (teamManagerInfoRow) TableName() string {
|
||||||
@ -73,7 +77,8 @@ func (propsNobleVIPAbilityRow) TableName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetProfile(ctx context.Context, user AuthUser) (*ProfileResponse, error) {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +86,7 @@ func (s *Service) GetProfile(ctx context.Context, user AuthUser) (*ProfileRespon
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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) {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
if req.AcceptUserID.Int64() <= 0 {
|
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) {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
if req.UserID.Int64() <= 0 {
|
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) {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
if req.UserID.Int64() <= 0 {
|
if req.UserID.Int64() <= 0 {
|
||||||
@ -332,6 +337,48 @@ func (s *Service) ensureManager(ctx context.Context, user AuthUser) error {
|
|||||||
return err
|
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) {
|
func (s *Service) findUserView(ctx context.Context, account string) (UserView, error) {
|
||||||
if userID, err := strconv.ParseInt(account, 10, 64); err == nil && userID > 0 {
|
if userID, err := strconv.ParseInt(account, 10, 64); err == nil && userID > 0 {
|
||||||
if row, err := s.loadUserRowByID(ctx, userID); err == nil {
|
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) {
|
func TestAddBDLeaderCreatesLeaderAndListEntry(t *testing.T) {
|
||||||
service, _ := newManagerCenterTestService(t)
|
service, _ := newManagerCenterTestService(t)
|
||||||
db := service.db.(*gorm.DB)
|
db := service.db.(*gorm.DB)
|
||||||
@ -234,6 +254,24 @@ func TestAddBDLeaderRejectsTargetFromOtherOrigin(t *testing.T) {
|
|||||||
assertAppErrorCode(t, err, "bd_leader_origin_mismatch")
|
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) {
|
func TestSendPropsRejectsNonAdminFree(t *testing.T) {
|
||||||
service, fake := newManagerCenterTestService(t)
|
service, fake := newManagerCenterTestService(t)
|
||||||
db := service.db.(*gorm.DB)
|
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) {
|
func TestSendBadgeActivatesBadgeBackpack(t *testing.T) {
|
||||||
service, fake := newManagerCenterTestService(t)
|
service, fake := newManagerCenterTestService(t)
|
||||||
db := service.db.(*gorm.DB)
|
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) {
|
func TestBanUserRejectsRechargedUser(t *testing.T) {
|
||||||
service, fake := newManagerCenterTestService(t)
|
service, fake := newManagerCenterTestService(t)
|
||||||
db := service.db.(*gorm.DB)
|
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) {
|
func TestUnbanUserRejectsNormalUser(t *testing.T) {
|
||||||
service, fake := newManagerCenterTestService(t)
|
service, fake := newManagerCenterTestService(t)
|
||||||
db := service.db.(*gorm.DB)
|
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) {
|
func seedManager(t *testing.T, db *gorm.DB, userID int64) {
|
||||||
t.Helper()
|
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)
|
t.Fatalf("seed manager: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func boolPtr(value bool) *bool {
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
|
||||||
func seedUsers(t *testing.T, db *gorm.DB, rows ...userBaseInfoRow) {
|
func seedUsers(t *testing.T, db *gorm.DB, rows ...userBaseInfoRow) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
|
|||||||
@ -38,6 +38,15 @@ const (
|
|||||||
accountStatusArchiveDevice = "ARCHIVE_DEVICE"
|
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 AppError = common.AppError
|
||||||
type AuthUser = common.AuthUser
|
type AuthUser = common.AuthUser
|
||||||
|
|
||||||
@ -106,7 +115,15 @@ func (id *ID) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProfileResponse struct {
|
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 {
|
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