经理中心

This commit is contained in:
hy001 2026-06-12 19:37:04 +08:00
parent e086a518bc
commit 995268fd70
9 changed files with 410 additions and 16 deletions

View File

@ -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 展示并进入已有 WebViewlaunch 路由再按 hg_* 分发回真实热游 provider。
switch strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(provider), "_", "")) {
case "HOTGAME", "REYOU", "LALU":
return "LEADER"
}
return provider
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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: "百顺"},

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View 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;