800 lines
23 KiB
Go
800 lines
23 KiB
Go
package levelconfig
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"hyapp-admin-server/internal/appctx"
|
|
"hyapp-admin-server/internal/integration/activityclient"
|
|
"hyapp-admin-server/internal/integration/walletclient"
|
|
"hyapp-admin-server/internal/middleware"
|
|
"hyapp-admin-server/internal/modules/shared"
|
|
"hyapp-admin-server/internal/response"
|
|
activityv1 "hyapp.local/api/proto/activity/v1"
|
|
walletv1 "hyapp.local/api/proto/wallet/v1"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
const (
|
|
statusActive = "active"
|
|
statusDisabled = "disabled"
|
|
|
|
longBadgeResourceIDKey = "long_badge_resource_id"
|
|
)
|
|
|
|
type Handler struct {
|
|
activity activityclient.Client
|
|
wallet walletclient.Client
|
|
audit shared.OperationLogger
|
|
}
|
|
|
|
func New(activity activityclient.Client, wallet walletclient.Client, audit shared.OperationLogger) *Handler {
|
|
return &Handler{activity: activity, wallet: wallet, audit: audit}
|
|
}
|
|
|
|
type levelConfigDTO struct {
|
|
Tracks []trackDTO `json:"tracks"`
|
|
ServerTimeMS int64 `json:"server_time_ms"`
|
|
}
|
|
|
|
type trackDTO struct {
|
|
Track string `json:"track"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
SortOrder int32 `json:"sort_order"`
|
|
DisplayConfigJSON string `json:"display_config_json"`
|
|
Rules []ruleDTO `json:"rules"`
|
|
Tiers []tierDTO `json:"tiers"`
|
|
CreatedAtMS int64 `json:"created_at_ms"`
|
|
UpdatedAtMS int64 `json:"updated_at_ms"`
|
|
}
|
|
|
|
type ruleDTO struct {
|
|
Track string `json:"track"`
|
|
Level int32 `json:"level"`
|
|
RequiredValue int64 `json:"required_value"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
|
|
SortOrder int32 `json:"sort_order"`
|
|
LongBadgeResourceID int64 `json:"long_badge_resource_id"`
|
|
DisplayConfigJSON string `json:"display_config_json"`
|
|
CreatedByAdminID int64 `json:"created_by_admin_id"`
|
|
UpdatedByAdminID int64 `json:"updated_by_admin_id"`
|
|
CreatedAtMS int64 `json:"created_at_ms"`
|
|
UpdatedAtMS int64 `json:"updated_at_ms"`
|
|
}
|
|
|
|
type tierDTO struct {
|
|
TierID int64 `json:"tier_id"`
|
|
Track string `json:"track"`
|
|
MinLevel int32 `json:"min_level"`
|
|
MaxLevel int32 `json:"max_level"`
|
|
Name string `json:"name"`
|
|
DisplayAvatarFrameResourceID int64 `json:"display_avatar_frame_resource_id"`
|
|
DisplayBadgeResourceID int64 `json:"display_badge_resource_id"`
|
|
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
|
|
Status string `json:"status"`
|
|
DisplayConfigJSON string `json:"display_config_json"`
|
|
CreatedByAdminID int64 `json:"created_by_admin_id"`
|
|
UpdatedByAdminID int64 `json:"updated_by_admin_id"`
|
|
CreatedAtMS int64 `json:"created_at_ms"`
|
|
UpdatedAtMS int64 `json:"updated_at_ms"`
|
|
}
|
|
|
|
type trackRequest struct {
|
|
Name *string `json:"name"`
|
|
Status *string `json:"status"`
|
|
SortOrder *int32 `json:"sort_order"`
|
|
DisplayConfigJSON *string `json:"display_config_json"`
|
|
}
|
|
|
|
type ruleRequest struct {
|
|
RequiredValue *int64 `json:"required_value"`
|
|
Name *string `json:"name"`
|
|
Status *string `json:"status"`
|
|
RewardResourceGroupID *int64 `json:"reward_resource_group_id"`
|
|
SortOrder *int32 `json:"sort_order"`
|
|
LongBadgeResourceID *int64 `json:"long_badge_resource_id"`
|
|
DisplayConfigJSON *string `json:"display_config_json"`
|
|
}
|
|
|
|
type tierRequest struct {
|
|
Track *string `json:"track"`
|
|
MinLevel *int32 `json:"min_level"`
|
|
MaxLevel *int32 `json:"max_level"`
|
|
Name *string `json:"name"`
|
|
DisplayAvatarFrameResourceID *int64 `json:"display_avatar_frame_resource_id"`
|
|
DisplayBadgeResourceID *int64 `json:"display_badge_resource_id"`
|
|
RewardResourceGroupID *int64 `json:"reward_resource_group_id"`
|
|
Status *string `json:"status"`
|
|
DisplayConfigJSON *string `json:"display_config_json"`
|
|
}
|
|
|
|
// ListLevelConfig 返回用户管理 > 等级配置页面需要的三条等级轨道完整配置。
|
|
func (h *Handler) ListLevelConfig(c *gin.Context) {
|
|
track := normalizeTrack(c.Query("track"))
|
|
status := normalizeStatusFilter(c.Query("status"))
|
|
if track != "" && !validTrack(track) {
|
|
response.BadRequest(c, "等级类型不正确")
|
|
return
|
|
}
|
|
if status != "" && !validStatus(status) {
|
|
response.BadRequest(c, "状态参数不正确")
|
|
return
|
|
}
|
|
resp, err := h.activity.ListLevelConfig(c.Request.Context(), &activityv1.ListLevelConfigRequest{
|
|
Meta: h.meta(c),
|
|
Track: track,
|
|
Status: status,
|
|
})
|
|
if err != nil {
|
|
response.ServerError(c, "获取等级配置失败")
|
|
return
|
|
}
|
|
response.OK(c, configFromProto(resp))
|
|
}
|
|
|
|
// UpdateTrack 更新 wealth/game/charm 三条固定等级轨道的基础展示配置。
|
|
func (h *Handler) UpdateTrack(c *gin.Context) {
|
|
track := normalizeTrack(c.Param("track"))
|
|
if !validTrack(track) {
|
|
response.BadRequest(c, "等级类型不正确")
|
|
return
|
|
}
|
|
var req trackRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "等级轨道参数不正确")
|
|
return
|
|
}
|
|
current, ok, err := h.currentTrack(c, track)
|
|
if err != nil {
|
|
response.ServerError(c, "读取等级轨道失败")
|
|
return
|
|
}
|
|
name := current.Name
|
|
if req.Name != nil {
|
|
name = strings.TrimSpace(*req.Name)
|
|
}
|
|
if name == "" {
|
|
name = defaultTrackName(track)
|
|
}
|
|
status := current.Status
|
|
if req.Status != nil {
|
|
status = normalizeStatus(*req.Status)
|
|
}
|
|
if status == "" {
|
|
status = statusActive
|
|
}
|
|
if !validStatus(status) {
|
|
response.BadRequest(c, "状态参数不正确")
|
|
return
|
|
}
|
|
sortOrder := current.SortOrder
|
|
if !ok {
|
|
sortOrder = defaultTrackSort(track)
|
|
}
|
|
if req.SortOrder != nil {
|
|
sortOrder = *req.SortOrder
|
|
}
|
|
displayConfig := current.DisplayConfigJSON
|
|
if req.DisplayConfigJSON != nil {
|
|
displayConfig = strings.TrimSpace(*req.DisplayConfigJSON)
|
|
}
|
|
displayConfig, err = normalizeJSONObject(displayConfig)
|
|
if err != nil {
|
|
response.BadRequest(c, "展示配置 JSON 不正确")
|
|
return
|
|
}
|
|
resp, err := h.activity.UpsertLevelTrack(c.Request.Context(), &activityv1.UpsertLevelTrackRequest{
|
|
Meta: h.meta(c),
|
|
Track: track,
|
|
Name: name,
|
|
Status: status,
|
|
SortOrder: sortOrder,
|
|
DisplayConfigJson: displayConfig,
|
|
})
|
|
if err != nil {
|
|
response.BadRequest(c, err.Error())
|
|
return
|
|
}
|
|
item := trackFromProto(resp.GetTrack(), nil, nil)
|
|
h.auditLog(c, "update-level-track", "growth_level_tracks", track, fmt.Sprintf("status=%s", item.Status))
|
|
response.OK(c, item)
|
|
}
|
|
|
|
// UpsertRule 配置某条轨道某一级的升级阈值、等级奖励资源组和每级长徽章素材。
|
|
func (h *Handler) UpsertRule(c *gin.Context) {
|
|
track := normalizeTrack(c.Param("track"))
|
|
level, ok := parseNonNegativeInt32(c.Param("level"))
|
|
if !ok || !validTrack(track) {
|
|
response.BadRequest(c, "等级参数不正确")
|
|
return
|
|
}
|
|
var req ruleRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "等级规则参数不正确")
|
|
return
|
|
}
|
|
config, err := h.loadConfig(c, track, "")
|
|
if err != nil {
|
|
response.ServerError(c, "读取等级配置失败")
|
|
return
|
|
}
|
|
current, exists := findRule(config.GetRules(), track, level)
|
|
trackName := defaultTrackName(track)
|
|
if currentTrack, found := findTrack(config.GetTracks(), track); found {
|
|
trackName = currentTrack.GetName()
|
|
}
|
|
|
|
requiredValue := current.GetRequiredValue()
|
|
if req.RequiredValue != nil {
|
|
requiredValue = *req.RequiredValue
|
|
}
|
|
if requiredValue < 0 {
|
|
response.BadRequest(c, "升级所需数值不能小于 0")
|
|
return
|
|
}
|
|
name := current.GetName()
|
|
if req.Name != nil {
|
|
name = strings.TrimSpace(*req.Name)
|
|
}
|
|
if name == "" {
|
|
name = fmt.Sprintf("%s Lv.%d", trackName, level)
|
|
}
|
|
status := current.GetStatus()
|
|
if req.Status != nil {
|
|
status = normalizeStatus(*req.Status)
|
|
}
|
|
if status == "" {
|
|
status = statusActive
|
|
}
|
|
if !validStatus(status) {
|
|
response.BadRequest(c, "状态参数不正确")
|
|
return
|
|
}
|
|
rewardResourceGroupID := current.GetRewardResourceGroupId()
|
|
if req.RewardResourceGroupID != nil {
|
|
rewardResourceGroupID = *req.RewardResourceGroupID
|
|
}
|
|
if rewardResourceGroupID < 0 {
|
|
response.BadRequest(c, "奖励资源组 ID 不正确")
|
|
return
|
|
}
|
|
sortOrder := current.GetSortOrder()
|
|
if !exists {
|
|
sortOrder = level
|
|
}
|
|
if req.SortOrder != nil {
|
|
sortOrder = *req.SortOrder
|
|
}
|
|
displayConfig := current.GetDisplayConfigJson()
|
|
if req.DisplayConfigJSON != nil {
|
|
displayConfig = strings.TrimSpace(*req.DisplayConfigJSON)
|
|
}
|
|
displayConfig, err = mergeLongBadgeResourceID(displayConfig, req.LongBadgeResourceID)
|
|
if err != nil {
|
|
response.BadRequest(c, "展示配置 JSON 不正确")
|
|
return
|
|
}
|
|
if badgeID := longBadgeResourceID(displayConfig); badgeID > 0 {
|
|
if err := h.validateLevelLongBadgeResource(c, track, badgeID); err != nil {
|
|
response.BadRequest(c, err.Error())
|
|
return
|
|
}
|
|
}
|
|
resp, err := h.activity.UpsertLevelRule(c.Request.Context(), &activityv1.UpsertLevelRuleRequest{
|
|
Meta: h.meta(c),
|
|
Track: track,
|
|
Level: level,
|
|
RequiredValue: requiredValue,
|
|
Name: name,
|
|
Status: status,
|
|
RewardResourceGroupId: rewardResourceGroupID,
|
|
SortOrder: sortOrder,
|
|
DisplayConfigJson: displayConfig,
|
|
OperatorAdminId: int64(middleware.CurrentUserID(c)),
|
|
})
|
|
if err != nil {
|
|
response.BadRequest(c, err.Error())
|
|
return
|
|
}
|
|
item := ruleFromProto(resp.GetRule())
|
|
h.auditLog(c, "upsert-level-rule", "growth_level_rules", fmt.Sprintf("%s:%d", track, level), fmt.Sprintf("required_value=%d long_badge_resource_id=%d", item.RequiredValue, item.LongBadgeResourceID))
|
|
response.OK(c, item)
|
|
}
|
|
|
|
func (h *Handler) CreateTier(c *gin.Context) {
|
|
h.upsertTier(c, 0)
|
|
}
|
|
|
|
func (h *Handler) UpdateTier(c *gin.Context) {
|
|
tierID, ok := parsePositiveInt64(c.Param("tier_id"))
|
|
if !ok {
|
|
response.BadRequest(c, "等级段 ID 不正确")
|
|
return
|
|
}
|
|
h.upsertTier(c, tierID)
|
|
}
|
|
|
|
// upsertTier 复用已有等级段配置能力,保持 1-10、11-20 这类展示徽章仍由 activity-service 拥有。
|
|
func (h *Handler) upsertTier(c *gin.Context, tierID int64) {
|
|
var req tierRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "等级段参数不正确")
|
|
return
|
|
}
|
|
var current *activityv1.LevelTier
|
|
if tierID > 0 {
|
|
config, err := h.loadConfig(c, "", "")
|
|
if err != nil {
|
|
response.ServerError(c, "读取等级段配置失败")
|
|
return
|
|
}
|
|
var found bool
|
|
current, found = findTier(config.GetTiers(), tierID)
|
|
if !found {
|
|
response.NotFound(c, "等级段不存在")
|
|
return
|
|
}
|
|
}
|
|
track := ""
|
|
if current != nil {
|
|
track = current.GetTrack()
|
|
}
|
|
if req.Track != nil {
|
|
track = normalizeTrack(*req.Track)
|
|
}
|
|
if !validTrack(track) {
|
|
response.BadRequest(c, "等级类型不正确")
|
|
return
|
|
}
|
|
minLevel := int32(0)
|
|
if current != nil {
|
|
minLevel = current.GetMinLevel()
|
|
}
|
|
if req.MinLevel != nil {
|
|
minLevel = *req.MinLevel
|
|
}
|
|
maxLevel := int32(0)
|
|
if current != nil {
|
|
maxLevel = current.GetMaxLevel()
|
|
}
|
|
if req.MaxLevel != nil {
|
|
maxLevel = *req.MaxLevel
|
|
}
|
|
if minLevel < 0 || maxLevel < minLevel {
|
|
response.BadRequest(c, "等级段范围不正确")
|
|
return
|
|
}
|
|
name := ""
|
|
if current != nil {
|
|
name = current.GetName()
|
|
}
|
|
if req.Name != nil {
|
|
name = strings.TrimSpace(*req.Name)
|
|
}
|
|
if name == "" {
|
|
name = fmt.Sprintf("%s Lv.%d-%d", defaultTrackName(track), minLevel, maxLevel)
|
|
}
|
|
status := statusActive
|
|
if current != nil {
|
|
status = current.GetStatus()
|
|
}
|
|
if req.Status != nil {
|
|
status = normalizeStatus(*req.Status)
|
|
}
|
|
if !validStatus(status) {
|
|
response.BadRequest(c, "状态参数不正确")
|
|
return
|
|
}
|
|
avatarFrameResourceID := int64(0)
|
|
if current != nil {
|
|
avatarFrameResourceID = current.GetDisplayAvatarFrameResourceId()
|
|
}
|
|
if req.DisplayAvatarFrameResourceID != nil {
|
|
avatarFrameResourceID = *req.DisplayAvatarFrameResourceID
|
|
}
|
|
badgeResourceID := int64(0)
|
|
if current != nil {
|
|
badgeResourceID = current.GetDisplayBadgeResourceId()
|
|
}
|
|
if req.DisplayBadgeResourceID != nil {
|
|
badgeResourceID = *req.DisplayBadgeResourceID
|
|
}
|
|
rewardResourceGroupID := int64(0)
|
|
if current != nil {
|
|
rewardResourceGroupID = current.GetRewardResourceGroupId()
|
|
}
|
|
if req.RewardResourceGroupID != nil {
|
|
rewardResourceGroupID = *req.RewardResourceGroupID
|
|
}
|
|
if avatarFrameResourceID < 0 || badgeResourceID < 0 || rewardResourceGroupID < 0 {
|
|
response.BadRequest(c, "等级段资源 ID 不正确")
|
|
return
|
|
}
|
|
displayConfig := "{}"
|
|
if current != nil {
|
|
displayConfig = current.GetDisplayConfigJson()
|
|
}
|
|
if req.DisplayConfigJSON != nil {
|
|
displayConfig = strings.TrimSpace(*req.DisplayConfigJSON)
|
|
}
|
|
var err error
|
|
displayConfig, err = normalizeJSONObject(displayConfig)
|
|
if err != nil {
|
|
response.BadRequest(c, "展示配置 JSON 不正确")
|
|
return
|
|
}
|
|
resp, err := h.activity.UpsertLevelTier(c.Request.Context(), &activityv1.UpsertLevelTierRequest{
|
|
Meta: h.meta(c),
|
|
TierId: tierID,
|
|
Track: track,
|
|
MinLevel: minLevel,
|
|
MaxLevel: maxLevel,
|
|
Name: name,
|
|
DisplayAvatarFrameResourceId: avatarFrameResourceID,
|
|
DisplayBadgeResourceId: badgeResourceID,
|
|
RewardResourceGroupId: rewardResourceGroupID,
|
|
Status: status,
|
|
DisplayConfigJson: displayConfig,
|
|
OperatorAdminId: int64(middleware.CurrentUserID(c)),
|
|
})
|
|
if err != nil {
|
|
response.BadRequest(c, err.Error())
|
|
return
|
|
}
|
|
item := tierFromProto(resp.GetTier())
|
|
h.auditLog(c, "upsert-level-tier", "growth_level_tiers", strconv.FormatInt(item.TierID, 10), fmt.Sprintf("%s:%d-%d", item.Track, item.MinLevel, item.MaxLevel))
|
|
response.OK(c, item)
|
|
}
|
|
|
|
func (h *Handler) loadConfig(c *gin.Context, track string, status string) (*activityv1.ListLevelConfigResponse, error) {
|
|
return h.activity.ListLevelConfig(c.Request.Context(), &activityv1.ListLevelConfigRequest{
|
|
Meta: h.meta(c),
|
|
Track: track,
|
|
Status: status,
|
|
})
|
|
}
|
|
|
|
func (h *Handler) validateLevelLongBadgeResource(c *gin.Context, track string, resourceID int64) error {
|
|
if resourceID <= 0 {
|
|
return nil
|
|
}
|
|
if h.wallet == nil {
|
|
return fmt.Errorf("资源服务不可用")
|
|
}
|
|
resp, err := h.wallet.GetResource(c.Request.Context(), &walletv1.GetResourceRequest{
|
|
RequestId: middleware.CurrentRequestID(c),
|
|
AppCode: appctx.FromContext(c.Request.Context()),
|
|
ResourceId: resourceID,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("等级徽章资源不存在")
|
|
}
|
|
resource := resp.GetResource()
|
|
if resource.GetResourceType() != "badge" {
|
|
return fmt.Errorf("等级长徽章必须选择徽章资源")
|
|
}
|
|
metadata := levelBadgeMetadataFromJSON(resource.GetMetadataJson())
|
|
if metadata.BadgeForm != "strip" {
|
|
return fmt.Errorf("等级长徽章必须选择长徽章素材")
|
|
}
|
|
if metadata.BadgeKind != "level" {
|
|
return fmt.Errorf("等级长徽章必须选择等级徽章素材")
|
|
}
|
|
if metadata.LevelTrack != track {
|
|
return fmt.Errorf("等级长徽章所属等级不匹配")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *Handler) currentTrack(c *gin.Context, track string) (trackDTO, bool, error) {
|
|
config, err := h.loadConfig(c, track, "")
|
|
if err != nil {
|
|
return trackDTO{}, false, err
|
|
}
|
|
item, ok := findTrack(config.GetTracks(), track)
|
|
if !ok {
|
|
return trackDTO{}, false, nil
|
|
}
|
|
return trackFromProto(item, nil, nil), true, nil
|
|
}
|
|
|
|
func (h *Handler) meta(c *gin.Context) *activityv1.RequestMeta {
|
|
return &activityv1.RequestMeta{
|
|
RequestId: middleware.CurrentRequestID(c),
|
|
Caller: "admin-server",
|
|
AppCode: appctx.FromContext(c.Request.Context()),
|
|
SentAtMs: time.Now().UnixMilli(),
|
|
}
|
|
}
|
|
|
|
func (h *Handler) auditLog(c *gin.Context, action string, resource string, resourceID string, detail string) {
|
|
shared.OperationLogWithResourceID(c, h.audit, action, resource, resourceID, "success", detail)
|
|
}
|
|
|
|
func configFromProto(resp *activityv1.ListLevelConfigResponse) levelConfigDTO {
|
|
if resp == nil {
|
|
return levelConfigDTO{Tracks: []trackDTO{}}
|
|
}
|
|
rulesByTrack := make(map[string][]ruleDTO)
|
|
for _, rule := range resp.GetRules() {
|
|
item := ruleFromProto(rule)
|
|
rulesByTrack[item.Track] = append(rulesByTrack[item.Track], item)
|
|
}
|
|
tiersByTrack := make(map[string][]tierDTO)
|
|
for _, tier := range resp.GetTiers() {
|
|
item := tierFromProto(tier)
|
|
tiersByTrack[item.Track] = append(tiersByTrack[item.Track], item)
|
|
}
|
|
tracks := make([]trackDTO, 0, len(resp.GetTracks()))
|
|
for _, track := range resp.GetTracks() {
|
|
code := track.GetTrack()
|
|
tracks = append(tracks, trackFromProto(track, rulesByTrack[code], tiersByTrack[code]))
|
|
}
|
|
return levelConfigDTO{Tracks: tracks, ServerTimeMS: resp.GetServerTimeMs()}
|
|
}
|
|
|
|
func trackFromProto(item *activityv1.LevelTrack, rules []ruleDTO, tiers []tierDTO) trackDTO {
|
|
if item == nil {
|
|
return trackDTO{Rules: []ruleDTO{}, Tiers: []tierDTO{}}
|
|
}
|
|
if rules == nil {
|
|
rules = []ruleDTO{}
|
|
}
|
|
if tiers == nil {
|
|
tiers = []tierDTO{}
|
|
}
|
|
return trackDTO{
|
|
Track: item.GetTrack(),
|
|
Name: item.GetName(),
|
|
Status: item.GetStatus(),
|
|
SortOrder: item.GetSortOrder(),
|
|
DisplayConfigJSON: item.GetDisplayConfigJson(),
|
|
Rules: rules,
|
|
Tiers: tiers,
|
|
CreatedAtMS: item.GetCreatedAtMs(),
|
|
UpdatedAtMS: item.GetUpdatedAtMs(),
|
|
}
|
|
}
|
|
|
|
func ruleFromProto(item *activityv1.LevelRule) ruleDTO {
|
|
if item == nil {
|
|
return ruleDTO{}
|
|
}
|
|
return ruleDTO{
|
|
Track: item.GetTrack(),
|
|
Level: item.GetLevel(),
|
|
RequiredValue: item.GetRequiredValue(),
|
|
Name: item.GetName(),
|
|
Status: item.GetStatus(),
|
|
RewardResourceGroupID: item.GetRewardResourceGroupId(),
|
|
SortOrder: item.GetSortOrder(),
|
|
LongBadgeResourceID: longBadgeResourceID(item.GetDisplayConfigJson()),
|
|
DisplayConfigJSON: item.GetDisplayConfigJson(),
|
|
CreatedByAdminID: item.GetCreatedByAdminId(),
|
|
UpdatedByAdminID: item.GetUpdatedByAdminId(),
|
|
CreatedAtMS: item.GetCreatedAtMs(),
|
|
UpdatedAtMS: item.GetUpdatedAtMs(),
|
|
}
|
|
}
|
|
|
|
func tierFromProto(item *activityv1.LevelTier) tierDTO {
|
|
if item == nil {
|
|
return tierDTO{}
|
|
}
|
|
return tierDTO{
|
|
TierID: item.GetTierId(),
|
|
Track: item.GetTrack(),
|
|
MinLevel: item.GetMinLevel(),
|
|
MaxLevel: item.GetMaxLevel(),
|
|
Name: item.GetName(),
|
|
DisplayAvatarFrameResourceID: item.GetDisplayAvatarFrameResourceId(),
|
|
DisplayBadgeResourceID: item.GetDisplayBadgeResourceId(),
|
|
RewardResourceGroupID: item.GetRewardResourceGroupId(),
|
|
Status: item.GetStatus(),
|
|
DisplayConfigJSON: item.GetDisplayConfigJson(),
|
|
CreatedByAdminID: item.GetCreatedByAdminId(),
|
|
UpdatedByAdminID: item.GetUpdatedByAdminId(),
|
|
CreatedAtMS: item.GetCreatedAtMs(),
|
|
UpdatedAtMS: item.GetUpdatedAtMs(),
|
|
}
|
|
}
|
|
|
|
func findTrack(items []*activityv1.LevelTrack, track string) (*activityv1.LevelTrack, bool) {
|
|
for _, item := range items {
|
|
if item.GetTrack() == track {
|
|
return item, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func findRule(items []*activityv1.LevelRule, track string, level int32) (*activityv1.LevelRule, bool) {
|
|
for _, item := range items {
|
|
if item.GetTrack() == track && item.GetLevel() == level {
|
|
return item, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func findTier(items []*activityv1.LevelTier, tierID int64) (*activityv1.LevelTier, bool) {
|
|
for _, item := range items {
|
|
if item.GetTierId() == tierID {
|
|
return item, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func mergeLongBadgeResourceID(raw string, value *int64) (string, error) {
|
|
payload, err := objectFromJSON(raw)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if value != nil {
|
|
if *value < 0 {
|
|
return "", fmt.Errorf("long badge resource id is invalid")
|
|
}
|
|
// 长徽章是每一级展示素材;沿用 display_config_json 可避免为展示扩展反复改表。
|
|
payload[longBadgeResourceIDKey] = *value
|
|
}
|
|
data, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
func normalizeJSONObject(raw string) (string, error) {
|
|
payload, err := objectFromJSON(raw)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
data, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
func objectFromJSON(raw string) (map[string]any, error) {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
raw = "{}"
|
|
}
|
|
decoder := json.NewDecoder(bytes.NewBufferString(raw))
|
|
decoder.UseNumber()
|
|
var payload map[string]any
|
|
if err := decoder.Decode(&payload); err != nil {
|
|
return nil, err
|
|
}
|
|
if payload == nil {
|
|
payload = map[string]any{}
|
|
}
|
|
return payload, nil
|
|
}
|
|
|
|
func longBadgeResourceID(raw string) int64 {
|
|
payload, err := objectFromJSON(raw)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
value, ok := payload[longBadgeResourceIDKey]
|
|
if !ok {
|
|
return 0
|
|
}
|
|
switch typed := value.(type) {
|
|
case json.Number:
|
|
parsed, err := typed.Int64()
|
|
if err == nil && parsed > 0 {
|
|
return parsed
|
|
}
|
|
case float64:
|
|
if typed > 0 {
|
|
return int64(typed)
|
|
}
|
|
case string:
|
|
parsed, err := strconv.ParseInt(strings.TrimSpace(typed), 10, 64)
|
|
if err == nil && parsed > 0 {
|
|
return parsed
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
type levelBadgeMetadata struct {
|
|
BadgeForm string `json:"badge_form"`
|
|
BadgeKind string `json:"badge_kind"`
|
|
LevelTrack string `json:"level_track"`
|
|
}
|
|
|
|
func levelBadgeMetadataFromJSON(raw string) levelBadgeMetadata {
|
|
payload := levelBadgeMetadata{}
|
|
if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &payload); err != nil {
|
|
return levelBadgeMetadata{}
|
|
}
|
|
payload.BadgeForm = strings.ToLower(strings.TrimSpace(payload.BadgeForm))
|
|
payload.BadgeKind = strings.ToLower(strings.TrimSpace(payload.BadgeKind))
|
|
payload.LevelTrack = normalizeTrack(payload.LevelTrack)
|
|
return payload
|
|
}
|
|
|
|
func parseNonNegativeInt32(raw string) (int32, bool) {
|
|
value, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 32)
|
|
if err != nil || value < 0 {
|
|
return 0, false
|
|
}
|
|
return int32(value), true
|
|
}
|
|
|
|
func parsePositiveInt64(raw string) (int64, bool) {
|
|
value, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
|
|
if err != nil || value <= 0 {
|
|
return 0, false
|
|
}
|
|
return value, true
|
|
}
|
|
|
|
func normalizeTrack(value string) string {
|
|
return strings.ToLower(strings.TrimSpace(value))
|
|
}
|
|
|
|
func normalizeStatus(value string) string {
|
|
return strings.ToLower(strings.TrimSpace(value))
|
|
}
|
|
|
|
func normalizeStatusFilter(value string) string {
|
|
value = normalizeStatus(value)
|
|
if value == "all" {
|
|
return ""
|
|
}
|
|
return value
|
|
}
|
|
|
|
func validTrack(value string) bool {
|
|
switch value {
|
|
case "wealth", "game", "charm":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func validStatus(value string) bool {
|
|
return value == statusActive || value == statusDisabled
|
|
}
|
|
|
|
func defaultTrackName(track string) string {
|
|
switch track {
|
|
case "wealth":
|
|
return "财富等级"
|
|
case "game":
|
|
return "游戏等级"
|
|
case "charm":
|
|
return "魅力等级"
|
|
default:
|
|
return "等级"
|
|
}
|
|
}
|
|
|
|
func defaultTrackSort(track string) int32 {
|
|
switch track {
|
|
case "wealth":
|
|
return 10
|
|
case "game":
|
|
return 20
|
|
case "charm":
|
|
return 30
|
|
default:
|
|
return 0
|
|
}
|
|
}
|