package redpacket 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 { Enabled bool `json:"enabled"` CountTiers []int32 `json:"count_tiers"` AmountTiers []int64 `json:"amount_tiers"` DelayedOpenSeconds int32 `json:"delayed_open_seconds"` ExpireSeconds int32 `json:"expire_seconds"` DailySendLimit int32 `json:"daily_send_limit"` } type configDTO struct { AppCode string `json:"app_code"` Enabled bool `json:"enabled"` CountTiers []int32 `json:"count_tiers"` AmountTiers []int64 `json:"amount_tiers"` DelayedOpenSeconds int32 `json:"delayed_open_seconds"` ExpireSeconds int32 `json:"expire_seconds"` DailySendLimit int32 `json:"daily_send_limit"` UpdatedByAdminID int64 `json:"updated_by_admin_id"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` } type packetDTO struct { AppCode string `json:"app_code"` PacketID string `json:"packet_id"` CommandID string `json:"command_id"` SenderUserID int64 `json:"sender_user_id"` RoomID string `json:"room_id"` RegionID int64 `json:"region_id"` PacketType string `json:"packet_type"` TotalAmount int64 `json:"total_amount"` PacketCount int32 `json:"packet_count"` RemainingAmount int64 `json:"remaining_amount"` RemainingCount int32 `json:"remaining_count"` Status string `json:"status"` OpenAtMS int64 `json:"open_at_ms"` ExpiresAtMS int64 `json:"expires_at_ms"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` Claims []claimDTO `json:"claims,omitempty"` } type claimDTO struct { AppCode string `json:"app_code"` ClaimID string `json:"claim_id"` CommandID string `json:"command_id"` PacketID string `json:"packet_id"` UserID int64 `json:"user_id"` Amount int64 `json:"amount"` WalletTransactionID string `json:"wallet_transaction_id"` Status string `json:"status"` FailureReason string `json:"failure_reason"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` } func (h *Handler) GetConfig(c *gin.Context) { if h.wallet == nil { response.ServerError(c, "钱包服务未配置") return } resp, err := h.wallet.GetRedPacketConfig(c.Request.Context(), &walletv1.GetRedPacketConfigRequest{ AppCode: appctx.FromContext(c.Request.Context()), }) if err != nil { response.ServerError(c, "获取红包配置失败") return } response.OK(c, configFromProto(resp.GetConfig())) } func (h *Handler) UpdateConfig(c *gin.Context) { if h.wallet == nil { response.ServerError(c, "钱包服务未配置") return } var req configRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "红包配置参数不正确") return } resp, err := h.wallet.UpdateRedPacketConfig(c.Request.Context(), &walletv1.UpdateRedPacketConfigRequest{ AppCode: appctx.FromContext(c.Request.Context()), Enabled: req.Enabled, CountTiers: req.CountTiers, AmountTiers: req.AmountTiers, DelayedOpenSeconds: req.DelayedOpenSeconds, ExpireSeconds: req.ExpireSeconds, DailySendLimit: req.DailySendLimit, OperatorUserId: int64(middleware.CurrentUserID(c)), }) if err != nil { response.BadRequest(c, err.Error()) return } item := configFromProto(resp.GetConfig()) shared.OperationLogWithResourceID(c, h.audit, "update-red-packet-config", "red_packet_configs", item.AppCode, "success", "") response.OK(c, item) } func (h *Handler) ListPackets(c *gin.Context) { if h.wallet == nil { response.ServerError(c, "钱包服务未配置") return } options := shared.ListOptions(c) req := &walletv1.ListRedPacketsRequest{ AppCode: appctx.FromContext(c.Request.Context()), RoomId: strings.TrimSpace(c.Query("room_id")), PacketType: strings.TrimSpace(c.Query("packet_type")), Status: strings.TrimSpace(options.Status), Page: int32(options.Page), PageSize: int32(options.PageSize), SenderUserId: parseInt64Query(c, "sender_user_id"), RegionId: parseInt64Query(c, "region_id"), StartAtMs: parseInt64Query(c, "start_at_ms"), EndAtMs: parseInt64Query(c, "end_at_ms"), } resp, err := h.wallet.ListRedPackets(c.Request.Context(), req) if err != nil { response.ServerError(c, "获取红包列表失败") return } items := make([]packetDTO, 0, len(resp.GetPackets())) for _, packet := range resp.GetPackets() { items = append(items, packetFromProto(packet)) } response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()}) } func (h *Handler) GetPacket(c *gin.Context) { if h.wallet == nil { response.ServerError(c, "钱包服务未配置") return } packetID := strings.TrimSpace(c.Param("packet_id")) if packetID == "" { response.BadRequest(c, "红包 ID 不正确") return } resp, err := h.wallet.GetRedPacket(c.Request.Context(), &walletv1.GetRedPacketRequest{ AppCode: appctx.FromContext(c.Request.Context()), PacketId: packetID, IncludeClaims: true, }) if err != nil { response.ServerError(c, "获取红包详情失败") return } response.OK(c, packetFromProto(resp.GetPacket())) } func configFromProto(config *walletv1.RedPacketConfig) configDTO { if config == nil { return configDTO{} } return configDTO{ AppCode: config.GetAppCode(), Enabled: config.GetEnabled(), CountTiers: append([]int32(nil), config.GetCountTiers()...), AmountTiers: append([]int64(nil), config.GetAmountTiers()...), DelayedOpenSeconds: config.GetDelayedOpenSeconds(), ExpireSeconds: config.GetExpireSeconds(), DailySendLimit: config.GetDailySendLimit(), UpdatedByAdminID: config.GetUpdatedByAdminId(), CreatedAtMS: config.GetCreatedAtMs(), UpdatedAtMS: config.GetUpdatedAtMs(), } } func packetFromProto(packet *walletv1.RedPacket) packetDTO { if packet == nil { return packetDTO{} } claims := make([]claimDTO, 0, len(packet.GetClaims())) for _, claim := range packet.GetClaims() { claims = append(claims, claimFromProto(claim)) } return packetDTO{ AppCode: packet.GetAppCode(), PacketID: packet.GetPacketId(), CommandID: packet.GetCommandId(), SenderUserID: packet.GetSenderUserId(), RoomID: packet.GetRoomId(), RegionID: packet.GetRegionId(), PacketType: packet.GetPacketType(), TotalAmount: packet.GetTotalAmount(), PacketCount: packet.GetPacketCount(), RemainingAmount: packet.GetRemainingAmount(), RemainingCount: packet.GetRemainingCount(), Status: packet.GetStatus(), OpenAtMS: packet.GetOpenAtMs(), ExpiresAtMS: packet.GetExpiresAtMs(), CreatedAtMS: packet.GetCreatedAtMs(), UpdatedAtMS: packet.GetUpdatedAtMs(), Claims: claims, } } func claimFromProto(claim *walletv1.RedPacketClaim) claimDTO { if claim == nil { return claimDTO{} } return claimDTO{ AppCode: claim.GetAppCode(), ClaimID: claim.GetClaimId(), CommandID: claim.GetCommandId(), PacketID: claim.GetPacketId(), UserID: claim.GetUserId(), Amount: claim.GetAmount(), WalletTransactionID: claim.GetWalletTransactionId(), Status: claim.GetStatus(), FailureReason: claim.GetFailureReason(), CreatedAtMS: claim.GetCreatedAtMs(), UpdatedAtMS: claim.GetUpdatedAtMs(), } } func parseInt64Query(c *gin.Context, key string) int64 { value := strings.TrimSpace(c.Query(key)) if value == "" { return 0 } parsed, _ := strconv.ParseInt(value, 10, 64) if parsed < 0 { return 0 } return parsed }