增加7777
This commit is contained in:
parent
d3f29ae57e
commit
c169dfb5e7
110
server/admin/internal/modules/appconfig/gift_config.go
Normal file
110
server/admin/internal/modules/appconfig/gift_config.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package appconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"hyapp-admin-server/internal/appctx"
|
||||||
|
"hyapp-admin-server/internal/model"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
giftConfigGroup = "room-gift"
|
||||||
|
giftQuantityPresetsKey = "quantity-presets"
|
||||||
|
giftQuantityPresetMaxCount = 8
|
||||||
|
giftQuantityPresetMaxValue = 9_999
|
||||||
|
giftQuantityPresetsDescription = "语音房送礼数量档位,严格递增且每档为 1-9999"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultGiftQuantityPresets = []int32{1, 9, 99, 999}
|
||||||
|
|
||||||
|
type giftConfigRequest struct {
|
||||||
|
QuantityPresets []int32 `json:"quantityPresets"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GiftConfig 是当前 App 的语音房送礼配置;缺少持久化行时仍返回兼容旧客户端的默认档位。
|
||||||
|
type GiftConfig struct {
|
||||||
|
AppCode string `json:"appCode"`
|
||||||
|
QuantityPresets []int32 `json:"quantityPresets"`
|
||||||
|
UpdatedAtMS int64 `json:"updatedAtMs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AppConfigService) GetGiftConfig(appCode string) (GiftConfig, error) {
|
||||||
|
appCode = appctx.Normalize(appCode)
|
||||||
|
item, err := s.store.GetOwnedAppConfig(appCode, giftConfigGroup, giftQuantityPresetsKey)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return GiftConfig{
|
||||||
|
AppCode: appCode,
|
||||||
|
QuantityPresets: cloneGiftQuantityPresets(defaultGiftQuantityPresets),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return GiftConfig{}, err
|
||||||
|
}
|
||||||
|
return giftConfigFromModel(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AppConfigService) UpdateGiftConfig(appCode string, request giftConfigRequest) (GiftConfig, error) {
|
||||||
|
appCode = appctx.Normalize(appCode)
|
||||||
|
presets, err := normalizeGiftQuantityPresets(request.QuantityPresets)
|
||||||
|
if err != nil {
|
||||||
|
return GiftConfig{}, err
|
||||||
|
}
|
||||||
|
raw, err := json.Marshal(presets)
|
||||||
|
if err != nil {
|
||||||
|
return GiftConfig{}, err
|
||||||
|
}
|
||||||
|
// App 作用域只取鉴权上下文;请求体没有 app_code,保存 Lalu 时不会覆盖 Fami 或其他 App。
|
||||||
|
if err := s.store.UpsertScopedAppConfigs(appCode, []model.AppConfig{{
|
||||||
|
AppCode: appCode,
|
||||||
|
Group: giftConfigGroup,
|
||||||
|
Key: giftQuantityPresetsKey,
|
||||||
|
Value: string(raw),
|
||||||
|
Description: giftQuantityPresetsDescription,
|
||||||
|
}}); err != nil {
|
||||||
|
return GiftConfig{}, err
|
||||||
|
}
|
||||||
|
return s.GetGiftConfig(appCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func giftConfigFromModel(item model.AppConfig) (GiftConfig, error) {
|
||||||
|
var presets []int32
|
||||||
|
if err := json.Unmarshal([]byte(item.Value), &presets); err != nil {
|
||||||
|
return GiftConfig{}, fmt.Errorf("送礼数量档位配置格式不正确: %w", err)
|
||||||
|
}
|
||||||
|
presets, err := normalizeGiftQuantityPresets(presets)
|
||||||
|
if err != nil {
|
||||||
|
return GiftConfig{}, err
|
||||||
|
}
|
||||||
|
return GiftConfig{
|
||||||
|
AppCode: appctx.Normalize(item.AppCode),
|
||||||
|
QuantityPresets: presets,
|
||||||
|
UpdatedAtMS: item.UpdatedAtMS,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeGiftQuantityPresets(values []int32) ([]int32, error) {
|
||||||
|
if len(values) == 0 || len(values) > giftQuantityPresetMaxCount {
|
||||||
|
return nil, fmt.Errorf("送礼数量档位必须配置 1-%d 个", giftQuantityPresetMaxCount)
|
||||||
|
}
|
||||||
|
normalized := make([]int32, len(values))
|
||||||
|
var previous int32
|
||||||
|
for index, value := range values {
|
||||||
|
if value < 1 || value > giftQuantityPresetMaxValue {
|
||||||
|
return nil, fmt.Errorf("送礼数量档位必须在 1-%d 之间", giftQuantityPresetMaxValue)
|
||||||
|
}
|
||||||
|
if index > 0 && value <= previous {
|
||||||
|
return nil, errors.New("送礼数量档位必须严格递增且不能重复")
|
||||||
|
}
|
||||||
|
normalized[index] = value
|
||||||
|
previous = value
|
||||||
|
}
|
||||||
|
return normalized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneGiftQuantityPresets(values []int32) []int32 {
|
||||||
|
return append([]int32(nil), values...)
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package appconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hyapp-admin-server/internal/appctx"
|
||||||
|
"hyapp-admin-server/internal/modules/shared"
|
||||||
|
"hyapp-admin-server/internal/response"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) GetGiftConfig(c *gin.Context) {
|
||||||
|
item, err := h.service.GetGiftConfig(appctx.FromContext(c.Request.Context()))
|
||||||
|
if err != nil {
|
||||||
|
response.ServerError(c, "获取送礼配置失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OK(c, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) UpdateGiftConfig(c *gin.Context) {
|
||||||
|
var request giftConfigRequest
|
||||||
|
if err := c.ShouldBindJSON(&request); err != nil {
|
||||||
|
response.BadRequest(c, "参数不正确")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
appCode := appctx.FromContext(c.Request.Context())
|
||||||
|
item, err := h.service.UpdateGiftConfig(appCode, request)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 审计记录最终规范化档位,避免仅有“保存成功”而无法还原当时对客户端下发的数量列表。
|
||||||
|
parts := make([]string, 0, len(item.QuantityPresets))
|
||||||
|
for _, value := range item.QuantityPresets {
|
||||||
|
parts = append(parts, strconv.FormatInt(int64(value), 10))
|
||||||
|
}
|
||||||
|
shared.OperationLog(c, h.audit, "update-room-gift-config", "admin_app_configs", "success", fmt.Sprintf("app_code=%s quantity_presets=%s", item.AppCode, strings.Join(parts, ",")))
|
||||||
|
response.OK(c, item)
|
||||||
|
}
|
||||||
@ -16,6 +16,8 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
|
|||||||
protected.PUT("/admin/app-config/h5-links", middleware.RequirePermission("app-config:update"), h.UpdateH5Links)
|
protected.PUT("/admin/app-config/h5-links", middleware.RequirePermission("app-config:update"), h.UpdateH5Links)
|
||||||
protected.PUT("/admin/app-config/h5-links/:key", middleware.RequirePermission("app-config:update"), h.UpdateH5Link)
|
protected.PUT("/admin/app-config/h5-links/:key", middleware.RequirePermission("app-config:update"), h.UpdateH5Link)
|
||||||
protected.DELETE("/admin/app-config/h5-links/:key", middleware.RequirePermission("app-config:update"), h.DeleteH5Link)
|
protected.DELETE("/admin/app-config/h5-links/:key", middleware.RequirePermission("app-config:update"), h.DeleteH5Link)
|
||||||
|
protected.GET("/admin/app-config/gift", middleware.RequirePermission("app-config:view"), h.GetGiftConfig)
|
||||||
|
protected.PUT("/admin/app-config/gift", middleware.RequirePermission("app-config:update"), h.UpdateGiftConfig)
|
||||||
protected.GET("/admin/app-config/gift-combo", middleware.RequirePermission("app-config:view"), h.GetGiftComboConfig)
|
protected.GET("/admin/app-config/gift-combo", middleware.RequirePermission("app-config:view"), h.GetGiftComboConfig)
|
||||||
protected.PUT("/admin/app-config/gift-combo", middleware.RequirePermission("app-config:update"), h.UpdateGiftComboConfig)
|
protected.PUT("/admin/app-config/gift-combo", middleware.RequirePermission("app-config:update"), h.UpdateGiftComboConfig)
|
||||||
protected.GET("/admin/app-config/floating-screen", middleware.RequirePermission("app-config:view"), h.GetFloatingScreenConfig)
|
protected.GET("/admin/app-config/floating-screen", middleware.RequirePermission("app-config:view"), h.GetFloatingScreenConfig)
|
||||||
|
|||||||
25
server/admin/migrations/126_gift_quantity_presets_config.sql
Normal file
25
server/admin/migrations/126_gift_quantity_presets_config.sql
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED);
|
||||||
|
|
||||||
|
-- 查询与写入都命中 uk_admin_app_config_app_group_key(app_code, group, key),只处理两个常量行,
|
||||||
|
-- 不扫描礼物、账务或配置事实表。重复执行时保留管理员已经保存的值,避免回滚线上档位。
|
||||||
|
INSERT INTO admin_app_configs (app_code, `group`, `key`, value, description, is_deleted, created_at_ms, updated_at_ms)
|
||||||
|
VALUES
|
||||||
|
('lalu', 'room-gift', 'quantity-presets', '[1,7,77,777,7777]', '语音房送礼数量档位,严格递增且每档为 1-9999', FALSE, @now_ms, @now_ms),
|
||||||
|
('fami', 'room-gift', 'quantity-presets', '[1,7,77,777,7777]', '语音房送礼数量档位,严格递增且每档为 1-9999', FALSE, @now_ms, @now_ms)
|
||||||
|
ON DUPLICATE KEY UPDATE id = id;
|
||||||
|
|
||||||
|
INSERT INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms)
|
||||||
|
SELECT parent.id, '送礼配置', 'app-config-gift', '/app-config/gift', 'gift', 'app-config:view', 74, TRUE, @now_ms, @now_ms
|
||||||
|
FROM admin_menus parent
|
||||||
|
WHERE parent.code = 'app-config'
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
parent_id = VALUES(parent_id),
|
||||||
|
title = VALUES(title),
|
||||||
|
path = VALUES(path),
|
||||||
|
icon = VALUES(icon),
|
||||||
|
permission_code = VALUES(permission_code),
|
||||||
|
sort = VALUES(sort),
|
||||||
|
visible = VALUES(visible),
|
||||||
|
updated_at_ms = VALUES(updated_at_ms);
|
||||||
74
services/gateway-service/internal/appconfig/gift_config.go
Normal file
74
services/gateway-service/internal/appconfig/gift_config.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package appconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
giftConfigGroup = "room-gift"
|
||||||
|
giftQuantityPresetsKey = "quantity-presets"
|
||||||
|
giftQuantityPresetMaxCount = 8
|
||||||
|
giftQuantityPresetMaxValue = 9_999
|
||||||
|
)
|
||||||
|
|
||||||
|
const getGiftQuantityPresetsSQL = `
|
||||||
|
SELECT COALESCE(value, '')
|
||||||
|
FROM admin_app_configs
|
||||||
|
WHERE app_code = ? AND ` + "`group`" + ` = ? AND ` + "`key`" + ` = ? AND is_deleted = FALSE
|
||||||
|
LIMIT 1`
|
||||||
|
|
||||||
|
// DefaultGiftQuantityPresets preserves the existing product behavior for Apps that have not saved
|
||||||
|
// their own room-gift configuration. Callers receive a copy so request-local fallback cannot mutate it.
|
||||||
|
func DefaultGiftQuantityPresets() []int32 {
|
||||||
|
return []int32{1, 9, 99, 999}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GiftQuantityPresets resolves only the selected App's row. The three-column unique index makes this
|
||||||
|
// a bounded point lookup; a missing row is a normal compatibility fallback, not a cross-App baseline.
|
||||||
|
func (r *MySQLReader) GiftQuantityPresets(ctx context.Context, appCode string) ([]int32, error) {
|
||||||
|
if r == nil || r.db == nil {
|
||||||
|
return nil, errors.New("app config reader is not configured")
|
||||||
|
}
|
||||||
|
appCode = normalizeAppCode(appCode)
|
||||||
|
var raw string
|
||||||
|
err := r.db.QueryRowContext(ctx, getGiftQuantityPresetsSQL, appCode, giftConfigGroup, giftQuantityPresetsKey).Scan(&raw)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return DefaultGiftQuantityPresets(), nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var presets []int32
|
||||||
|
if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &presets); err != nil {
|
||||||
|
return nil, fmt.Errorf("parse gift quantity presets for app %s: %w", appCode, err)
|
||||||
|
}
|
||||||
|
return normalizeGiftQuantityPresets(presets)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeGiftQuantityPresets(values []int32) ([]int32, error) {
|
||||||
|
if len(values) == 0 || len(values) > giftQuantityPresetMaxCount {
|
||||||
|
return nil, fmt.Errorf("gift quantity presets must contain between 1 and %d values", giftQuantityPresetMaxCount)
|
||||||
|
}
|
||||||
|
normalized := make([]int32, len(values))
|
||||||
|
var previous int32
|
||||||
|
for index, value := range values {
|
||||||
|
if value < 1 || value > giftQuantityPresetMaxValue {
|
||||||
|
return nil, fmt.Errorf("gift quantity preset must be between 1 and %d", giftQuantityPresetMaxValue)
|
||||||
|
}
|
||||||
|
if index > 0 && value <= previous {
|
||||||
|
return nil, errors.New("gift quantity presets must be strictly increasing and unique")
|
||||||
|
}
|
||||||
|
normalized[index] = value
|
||||||
|
previous = value
|
||||||
|
}
|
||||||
|
return normalized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ interface {
|
||||||
|
GiftQuantityPresets(context.Context, string) ([]int32, error)
|
||||||
|
} = (*MySQLReader)(nil)
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package roomapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"hyapp/services/gateway-service/internal/appconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
type giftQuantityPresetReader interface {
|
||||||
|
GiftQuantityPresets(ctx context.Context, appCode string) ([]int32, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) roomGiftQuantityPresets(ctx context.Context, appCode string) ([]int32, error) {
|
||||||
|
if h.giftQuantityReader == nil {
|
||||||
|
return appconfig.DefaultGiftQuantityPresets(), nil
|
||||||
|
}
|
||||||
|
return h.giftQuantityReader.GiftQuantityPresets(ctx, appCode)
|
||||||
|
}
|
||||||
@ -162,6 +162,11 @@ func (h *Handler) serveRoomGiftPanel(writer http.ResponseWriter, request *http.R
|
|||||||
// 当前 Flutter 无论是否聚合都继续走 V2,不能因配置故障切回旧财务入口。
|
// 当前 Flutter 无论是否聚合都继续走 V2,不能因配置故障切回旧财务入口。
|
||||||
return config, err != nil, err
|
return config, err != nil, err
|
||||||
})
|
})
|
||||||
|
quantityPresetsCh := startRoomGiftPanelStage(&stageGroup, totalCtx, requestID, roomID, "quantity_presets", func() ([]int32, bool, error) {
|
||||||
|
presets, err := h.roomGiftQuantityPresets(totalCtx, app)
|
||||||
|
// 配置库故障不能阻断礼物面板;降级到旧档位并保留阶段错误,便于区分未配置和运行时依赖异常。
|
||||||
|
return presets, err != nil, err
|
||||||
|
})
|
||||||
|
|
||||||
snapshotResp, _, err := waitRoomGiftPanelStage(totalCtx, snapshotCh)
|
snapshotResp, _, err := waitRoomGiftPanelStage(totalCtx, snapshotCh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -219,6 +224,14 @@ func (h *Handler) serveRoomGiftPanel(writer http.ResponseWriter, request *http.R
|
|||||||
}
|
}
|
||||||
comboConfig = disabledGiftComboFallback()
|
comboConfig = disabledGiftComboFallback()
|
||||||
}
|
}
|
||||||
|
quantityPresets, _, quantityPresetsErr := waitRoomGiftPanelStage(totalCtx, quantityPresetsCh)
|
||||||
|
if quantityPresetsErr != nil {
|
||||||
|
if totalCtx.Err() != nil {
|
||||||
|
h.writeRoomGiftPanelFailure(writer, request, requestID, roomID, totalStartedAt, quantityPresetsErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
quantityPresets = appconfig.DefaultGiftQuantityPresets()
|
||||||
|
}
|
||||||
|
|
||||||
panelGifts := append([]giftConfigData{}, panelConfig.Gifts...)
|
panelGifts := append([]giftConfigData{}, panelConfig.Gifts...)
|
||||||
panelGifts = append(panelGifts, roomGiftBagGiftsFromResources(bagResources, panelConfig.Gifts, snapshot.GetVisibleRegionId())...)
|
panelGifts = append(panelGifts, roomGiftBagGiftsFromResources(bagResources, panelConfig.Gifts, snapshot.GetVisibleRegionId())...)
|
||||||
@ -227,7 +240,7 @@ func (h *Handler) serveRoomGiftPanel(writer http.ResponseWriter, request *http.R
|
|||||||
Recipients: roomGiftRecipients(snapshot, recipientProfiles),
|
Recipients: roomGiftRecipients(snapshot, recipientProfiles),
|
||||||
Tabs: roomGiftTabs(panelGifts, panelConfig.GiftTypes),
|
Tabs: roomGiftTabs(panelGifts, panelConfig.GiftTypes),
|
||||||
Gifts: panelGifts,
|
Gifts: panelGifts,
|
||||||
QuantityPresets: []int32{1, 9, 99, 999},
|
QuantityPresets: quantityPresets,
|
||||||
ComboConfig: comboConfig,
|
ComboConfig: comboConfig,
|
||||||
}
|
}
|
||||||
logRoomGiftPanelStage(totalCtx, requestID, roomID, "total", totalStartedAt, nil, false)
|
logRoomGiftPanelStage(totalCtx, requestID, roomID, "total", totalStartedAt, nil, false)
|
||||||
|
|||||||
@ -43,6 +43,7 @@ type Handler struct {
|
|||||||
userHostClient client.UserHostClient
|
userHostClient client.UserHostClient
|
||||||
appConfigReader appapi.ConfigReader
|
appConfigReader appapi.ConfigReader
|
||||||
giftComboReader giftComboConfigReader
|
giftComboReader giftComboConfigReader
|
||||||
|
giftQuantityReader giftQuantityPresetReader
|
||||||
walletClient client.WalletClient
|
walletClient client.WalletClient
|
||||||
giftPanelCache *roomGiftPanelConfigCache
|
giftPanelCache *roomGiftPanelConfigCache
|
||||||
growthLevelClient client.GrowthLevelClient
|
growthLevelClient client.GrowthLevelClient
|
||||||
@ -102,6 +103,7 @@ func New(config Config) *Handler {
|
|||||||
giftMaxUnits = 5_000
|
giftMaxUnits = 5_000
|
||||||
}
|
}
|
||||||
comboReader, _ := any(config.AppConfigReader).(giftComboConfigReader)
|
comboReader, _ := any(config.AppConfigReader).(giftComboConfigReader)
|
||||||
|
giftQuantityReader, _ := any(config.AppConfigReader).(giftQuantityPresetReader)
|
||||||
roomVIPClient, _ := any(config.RoomClient).(client.RoomVIPFeatureClient)
|
roomVIPClient, _ := any(config.RoomClient).(client.RoomVIPFeatureClient)
|
||||||
roomMediaGuard, _ := any(config.RoomGuardClient).(client.RoomMediaGuardClient)
|
roomMediaGuard, _ := any(config.RoomGuardClient).(client.RoomMediaGuardClient)
|
||||||
urlVerifier, _ := any(config.ObjectUploader).(objectURLVerifier)
|
urlVerifier, _ := any(config.ObjectUploader).(objectURLVerifier)
|
||||||
@ -120,6 +122,7 @@ func New(config Config) *Handler {
|
|||||||
userHostClient: config.UserHostClient,
|
userHostClient: config.UserHostClient,
|
||||||
appConfigReader: config.AppConfigReader,
|
appConfigReader: config.AppConfigReader,
|
||||||
giftComboReader: comboReader,
|
giftComboReader: comboReader,
|
||||||
|
giftQuantityReader: giftQuantityReader,
|
||||||
walletClient: config.WalletClient,
|
walletClient: config.WalletClient,
|
||||||
giftPanelCache: newRoomGiftPanelConfigCache(45 * time.Second),
|
giftPanelCache: newRoomGiftPanelConfigCache(45 * time.Second),
|
||||||
growthLevelClient: config.GrowthLevelClient,
|
growthLevelClient: config.GrowthLevelClient,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user