248 lines
8.0 KiB
Go
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
|
|
}
|
|
}
|