增加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/:key", middleware.RequirePermission("app-config:update"), h.UpdateH5Link)
|
||||
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.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)
|
||||
|
||||
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,不能因配置故障切回旧财务入口。
|
||||
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)
|
||||
if err != nil {
|
||||
@ -219,6 +224,14 @@ func (h *Handler) serveRoomGiftPanel(writer http.ResponseWriter, request *http.R
|
||||
}
|
||||
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(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),
|
||||
Tabs: roomGiftTabs(panelGifts, panelConfig.GiftTypes),
|
||||
Gifts: panelGifts,
|
||||
QuantityPresets: []int32{1, 9, 99, 999},
|
||||
QuantityPresets: quantityPresets,
|
||||
ComboConfig: comboConfig,
|
||||
}
|
||||
logRoomGiftPanelStage(totalCtx, requestID, roomID, "total", totalStartedAt, nil, false)
|
||||
|
||||
@ -43,6 +43,7 @@ type Handler struct {
|
||||
userHostClient client.UserHostClient
|
||||
appConfigReader appapi.ConfigReader
|
||||
giftComboReader giftComboConfigReader
|
||||
giftQuantityReader giftQuantityPresetReader
|
||||
walletClient client.WalletClient
|
||||
giftPanelCache *roomGiftPanelConfigCache
|
||||
growthLevelClient client.GrowthLevelClient
|
||||
@ -102,6 +103,7 @@ func New(config Config) *Handler {
|
||||
giftMaxUnits = 5_000
|
||||
}
|
||||
comboReader, _ := any(config.AppConfigReader).(giftComboConfigReader)
|
||||
giftQuantityReader, _ := any(config.AppConfigReader).(giftQuantityPresetReader)
|
||||
roomVIPClient, _ := any(config.RoomClient).(client.RoomVIPFeatureClient)
|
||||
roomMediaGuard, _ := any(config.RoomGuardClient).(client.RoomMediaGuardClient)
|
||||
urlVerifier, _ := any(config.ObjectUploader).(objectURLVerifier)
|
||||
@ -120,6 +122,7 @@ func New(config Config) *Handler {
|
||||
userHostClient: config.UserHostClient,
|
||||
appConfigReader: config.AppConfigReader,
|
||||
giftComboReader: comboReader,
|
||||
giftQuantityReader: giftQuantityReader,
|
||||
walletClient: config.WalletClient,
|
||||
giftPanelCache: newRoomGiftPanelConfigCache(45 * time.Second),
|
||||
growthLevelClient: config.GrowthLevelClient,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user