248 lines
8.0 KiB
Go

package vipconfig
import (
"strconv"
"strings"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/integration/walletclient"
"hyapp-admin-server/internal/middleware"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/response"
walletv1 "hyapp.local/api/proto/wallet/v1"
"github.com/gin-gonic/gin"
)
type Handler struct {
wallet walletclient.Client
audit shared.OperationLogger
}
func New(wallet walletclient.Client, audit shared.OperationLogger) *Handler {
return &Handler{wallet: wallet, audit: audit}
}
type configRequest struct {
Levels []vipLevelDTO `json:"levels"`
}
type grantVipRequest struct {
CommandID string `json:"commandId"`
TargetUserID any `json:"targetUserId"`
Level int32 `json:"level"`
Reason string `json:"reason"`
}
type configDTO struct {
Levels []vipLevelDTO `json:"levels"`
ServerTimeMS int64 `json:"serverTimeMs"`
}
type grantVipDTO struct {
TransactionID string `json:"transactionId"`
VIP userVipDTO `json:"vip"`
RewardItems []vipRewardItemDTO `json:"rewardItems"`
ServerTimeMS int64 `json:"serverTimeMs"`
}
type userVipDTO struct {
UserID string `json:"userId"`
Level int32 `json:"level"`
Name string `json:"name"`
Active bool `json:"active"`
StartedAtMS int64 `json:"startedAtMs"`
ExpiresAtMS int64 `json:"expiresAtMs"`
UpdatedAtMS int64 `json:"updatedAtMs"`
}
type vipLevelDTO struct {
Level int32 `json:"level"`
Name string `json:"name"`
Status string `json:"status"`
PriceCoin int64 `json:"priceCoin"`
DurationMS int64 `json:"durationMs"`
RewardResourceGroupID int64 `json:"rewardResourceGroupId"`
SortOrder int32 `json:"sortOrder"`
RechargeGateRequired bool `json:"rechargeGateRequired"`
RequiredRechargeCoinAmount int64 `json:"requiredRechargeCoinAmount"`
UserRechargeCoinAmount int64 `json:"userRechargeCoinAmount"`
PurchaseLockedReason string `json:"purchaseLockedReason"`
CreatedAtMS int64 `json:"createdAtMs"`
UpdatedAtMS int64 `json:"updatedAtMs"`
}
type vipRewardItemDTO struct {
ResourceID string `json:"resourceId"`
ResourceCode string `json:"resourceCode"`
ResourceType string `json:"resourceType"`
Name string `json:"name"`
Quantity int64 `json:"quantity"`
ExpiresAtMS int64 `json:"expiresAtMs"`
}
func (h *Handler) ListLevels(c *gin.Context) {
resp, err := h.wallet.ListAdminVipLevels(c.Request.Context(), &walletv1.ListAdminVipLevelsRequest{
RequestId: middleware.CurrentRequestID(c),
AppCode: appctx.FromContext(c.Request.Context()),
})
if err != nil {
response.ServerError(c, "获取 VIP 配置失败")
return
}
response.OK(c, configDTO{Levels: vipLevelsFromProto(resp.GetLevels()), ServerTimeMS: resp.GetServerTimeMs()})
}
func (h *Handler) GrantVIP(c *gin.Context) {
var req grantVipRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "VIP 赠送参数不正确")
return
}
targetUserID, ok := parseFlexibleUserID(req.TargetUserID)
if !ok || req.Level <= 0 {
response.BadRequest(c, "VIP 赠送参数不正确")
return
}
reason := strings.TrimSpace(req.Reason)
if reason == "" {
response.BadRequest(c, "赠送原因不能为空")
return
}
commandID := strings.TrimSpace(req.CommandID)
if commandID == "" {
commandID = "admin_vip_grant:" + middleware.CurrentRequestID(c)
}
resp, err := h.wallet.GrantVip(c.Request.Context(), &walletv1.GrantVipRequest{
CommandId: commandID,
AppCode: appctx.FromContext(c.Request.Context()),
TargetUserId: targetUserID,
Level: req.Level,
GrantSource: "admin_grant",
OperatorUserId: int64(middleware.CurrentUserID(c)),
Reason: reason,
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
shared.OperationLogWithResourceID(c, h.audit, "grant-vip", "user_vip_memberships", strconv.FormatInt(targetUserID, 10), "success", "level="+strconv.Itoa(int(req.Level)))
response.Created(c, grantVipFromProto(resp))
}
func (h *Handler) UpdateLevels(c *gin.Context) {
var req configRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "VIP 配置参数不正确")
return
}
input := make([]*walletv1.AdminVipLevelInput, 0, len(req.Levels))
for _, level := range req.Levels {
input = append(input, &walletv1.AdminVipLevelInput{
Level: level.Level,
Name: strings.TrimSpace(level.Name),
Status: strings.TrimSpace(level.Status),
PriceCoin: level.PriceCoin,
DurationMs: level.DurationMS,
RewardResourceGroupId: level.RewardResourceGroupID,
RequiredRechargeCoinAmount: level.RequiredRechargeCoinAmount,
SortOrder: level.SortOrder,
})
}
resp, err := h.wallet.UpdateAdminVipLevels(c.Request.Context(), &walletv1.UpdateAdminVipLevelsRequest{
RequestId: middleware.CurrentRequestID(c),
AppCode: appctx.FromContext(c.Request.Context()),
Levels: input,
OperatorUserId: int64(middleware.CurrentUserID(c)),
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
shared.OperationLogWithResourceID(c, h.audit, "update-vip-config", "vip_levels", appctx.FromContext(c.Request.Context()), "success", strconv.Itoa(len(resp.GetLevels())))
response.OK(c, configDTO{Levels: vipLevelsFromProto(resp.GetLevels()), ServerTimeMS: resp.GetServerTimeMs()})
}
func vipLevelsFromProto(items []*walletv1.VipLevel) []vipLevelDTO {
levels := make([]vipLevelDTO, 0, len(items))
for _, item := range items {
if item == nil {
continue
}
levels = append(levels, vipLevelDTO{
Level: item.GetLevel(),
Name: item.GetName(),
Status: item.GetStatus(),
PriceCoin: item.GetPriceCoin(),
DurationMS: item.GetDurationMs(),
RewardResourceGroupID: item.GetRewardResourceGroupId(),
SortOrder: item.GetSortOrder(),
RechargeGateRequired: item.GetRechargeGateRequired(),
RequiredRechargeCoinAmount: item.GetRequiredRechargeCoinAmount(),
UserRechargeCoinAmount: item.GetUserRechargeCoinAmount(),
PurchaseLockedReason: item.GetPurchaseLockedReason(),
CreatedAtMS: item.GetCreatedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
})
}
return levels
}
func grantVipFromProto(resp *walletv1.GrantVipResponse) grantVipDTO {
if resp == nil {
return grantVipDTO{}
}
return grantVipDTO{
TransactionID: resp.GetTransactionId(),
VIP: userVipFromProto(resp.GetVip()),
RewardItems: vipRewardItemsFromProto(resp.GetRewardItems()),
ServerTimeMS: resp.GetServerTimeMs(),
}
}
func userVipFromProto(item *walletv1.UserVip) userVipDTO {
if item == nil {
return userVipDTO{}
}
return userVipDTO{
UserID: strconv.FormatInt(item.GetUserId(), 10),
Level: item.GetLevel(),
Name: item.GetName(),
Active: item.GetActive(),
StartedAtMS: item.GetStartedAtMs(),
ExpiresAtMS: item.GetExpiresAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
}
}
func vipRewardItemsFromProto(items []*walletv1.VipRewardItem) []vipRewardItemDTO {
rewards := make([]vipRewardItemDTO, 0, len(items))
for _, item := range items {
if item == nil {
continue
}
rewards = append(rewards, vipRewardItemDTO{
ResourceID: strconv.FormatInt(item.GetResourceId(), 10),
ResourceCode: item.GetResourceCode(),
ResourceType: item.GetResourceType(),
Name: item.GetName(),
Quantity: item.GetQuantity(),
ExpiresAtMS: item.GetExpiresAtMs(),
})
}
return rewards
}
func parseFlexibleUserID(value any) (int64, bool) {
switch typed := value.(type) {
case float64:
userID := int64(typed)
return userID, userID > 0 && float64(userID) == typed
case string:
userID, err := strconv.ParseInt(strings.TrimSpace(typed), 10, 64)
return userID, err == nil && userID > 0
default:
return 0, false
}
}