2026-05-13 10:58:33 +08:00

472 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package http
import (
"hyapp/services/gateway-service/internal/transport/http/httpkit"
"net/http"
"strings"
userv1 "hyapp.local/api/proto/user/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/pkg/appcode"
"hyapp/services/gateway-service/internal/auth"
)
type rechargeProductData struct {
ProductID int64 `json:"product_id"`
ProductCode string `json:"product_code"`
ProductName string `json:"product_name"`
Description string `json:"description"`
Platform string `json:"platform"`
Channel string `json:"channel"`
CurrencyCode string `json:"currency_code"`
CoinAmount int64 `json:"coin_amount"`
AmountMinor int64 `json:"amount_minor"`
AmountMicro int64 `json:"amount_micro"`
PolicyVersion string `json:"policy_version"`
RegionIDs []int64 `json:"region_ids"`
ResourceAssetType string `json:"resource_asset_type"`
Status string `json:"status"`
Enabled bool `json:"enabled"`
SortOrder int32 `json:"sort_order"`
}
type diamondExchangeRuleData struct {
ExchangeType string `json:"exchange_type"`
FromAssetType string `json:"from_asset_type"`
ToAssetType string `json:"to_asset_type"`
FromAmount int64 `json:"from_amount"`
ToAmount int64 `json:"to_amount"`
Enabled bool `json:"enabled"`
}
type walletOverviewData struct {
Balances []assetBalanceData `json:"balances"`
FeatureFlags walletFeatureFlagsData `json:"feature_flags"`
}
type walletFeatureFlagsData struct {
RechargeEnabled bool `json:"recharge_enabled"`
DiamondExchangeEnabled bool `json:"diamond_exchange_enabled"`
WithdrawEnabled bool `json:"withdraw_enabled"`
}
type walletTransactionData struct {
EntryID int64 `json:"entry_id"`
TransactionID string `json:"transaction_id"`
BizType string `json:"biz_type"`
AssetType string `json:"asset_type"`
AvailableDelta int64 `json:"available_delta"`
FrozenDelta int64 `json:"frozen_delta"`
AvailableAfter int64 `json:"available_after"`
FrozenAfter int64 `json:"frozen_after"`
CounterpartyUserID int64 `json:"counterparty_user_id"`
RoomID string `json:"room_id"`
CreatedAtMS int64 `json:"created_at_ms"`
}
type giftWallData struct {
Items []giftWallItemData `json:"items"`
GiftKindCount int64 `json:"gift_kind_count"`
GiftTotalCount int64 `json:"gift_total_count"`
TotalValue int64 `json:"total_value"`
TotalCoinValue int64 `json:"total_coin_value"`
TotalDiamondValue int64 `json:"total_diamond_value"`
TotalGiftPoint int64 `json:"total_gift_point"`
TotalHeatValue int64 `json:"total_heat_value"`
}
type giftWallItemData struct {
GiftID string `json:"gift_id"`
GiftName string `json:"gift_name"`
ResourceID int64 `json:"resource_id"`
Resource *resourceData `json:"resource,omitempty"`
ResourceSnapshotJSON string `json:"resource_snapshot_json"`
GiftTypeCode string `json:"gift_type_code"`
PresentationJSON string `json:"presentation_json"`
GiftCount int64 `json:"gift_count"`
TotalValue int64 `json:"total_value"`
TotalCoinValue int64 `json:"total_coin_value"`
TotalDiamondValue int64 `json:"total_diamond_value"`
TotalGiftPoint int64 `json:"total_gift_point"`
TotalHeatValue int64 `json:"total_heat_value"`
ChargeAssetType string `json:"charge_asset_type"`
LastPriceVersion string `json:"last_price_version"`
FirstReceivedAtMS int64 `json:"first_received_at_ms"`
LastReceivedAtMS int64 `json:"last_received_at_ms"`
SortOrder int32 `json:"sort_order"`
}
type withdrawalApplyRequestBody struct {
CommandID string `json:"command_id"`
CommandIDAlt string `json:"commandId"`
Amount int64 `json:"amount"`
PayoutAccount string `json:"payout_account"`
PayoutAlt string `json:"payoutAccount"`
Reason string `json:"reason"`
}
type withdrawalData struct {
WithdrawalID string `json:"withdrawal_id"`
UserID int64 `json:"user_id"`
AssetType string `json:"asset_type"`
Amount int64 `json:"amount"`
Status string `json:"status"`
PayoutAccount string `json:"payout_account"`
Reason string `json:"reason"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
type vipPurchaseRequestBody struct {
CommandID string `json:"command_id"`
CommandIDAlt string `json:"commandId"`
Level int32 `json:"level"`
}
type vipPackageData struct {
Level int32 `json:"level"`
Name string `json:"name"`
Status string `json:"status"`
PriceCoin int64 `json:"price_coin"`
DurationMS int64 `json:"duration_ms"`
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
RewardItems []vipRewardItemData `json:"reward_items"`
CanPurchase bool `json:"can_purchase"`
SortOrder int32 `json:"sort_order"`
}
type vipRewardItemData struct {
ResourceID int64 `json:"resource_id"`
ResourceCode string `json:"resource_code"`
ResourceType string `json:"resource_type"`
Name string `json:"name"`
Quantity int64 `json:"quantity"`
ExpiresAtMS int64 `json:"expires_at_ms"`
}
// getWalletOverview 返回钱包二级页摘要;我的页首屏使用更轻的 WalletValueSummary。
func (h *Handler) getWalletOverview(writer http.ResponseWriter, request *http.Request) {
if h.walletClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
resp, err := h.walletClient.GetWalletOverview(request.Context(), &walletv1.GetWalletOverviewRequest{
RequestId: httpkit.RequestIDFromContext(request.Context()),
AppCode: appcode.FromContext(request.Context()),
UserId: auth.UserIDFromContext(request.Context()),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, walletOverviewFromProto(resp))
}
// listRechargeProducts 返回用户当前区域可用的充值档位。
func (h *Handler) listRechargeProducts(writer http.ResponseWriter, request *http.Request) {
if h.walletClient == nil || h.userProfileClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
userID := auth.UserIDFromContext(request.Context())
profileResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: authRequestMeta(request, ""),
UserId: userID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
resp, err := h.walletClient.ListRechargeProducts(request.Context(), &walletv1.ListRechargeProductsRequest{
RequestId: httpkit.RequestIDFromContext(request.Context()),
AppCode: appcode.FromContext(request.Context()),
UserId: userID,
RegionId: profileResp.GetUser().GetRegionId(),
Platform: firstQueryOrHeader(request, "platform", "X-App-Platform", "X-Platform"),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
products := make([]rechargeProductData, 0, len(resp.GetProducts()))
for _, product := range resp.GetProducts() {
products = append(products, rechargeProductFromProto(product))
}
httpkit.WriteOK(writer, request, map[string]any{"channels": resp.GetChannels(), "products": products})
}
// getDiamondExchangeConfig 返回钻石兑换配置。
func (h *Handler) getDiamondExchangeConfig(writer http.ResponseWriter, request *http.Request) {
if h.walletClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
resp, err := h.walletClient.GetDiamondExchangeConfig(request.Context(), &walletv1.GetDiamondExchangeConfigRequest{
RequestId: httpkit.RequestIDFromContext(request.Context()),
AppCode: appcode.FromContext(request.Context()),
UserId: auth.UserIDFromContext(request.Context()),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
rules := make([]diamondExchangeRuleData, 0, len(resp.GetRules()))
for _, rule := range resp.GetRules() {
rules = append(rules, diamondExchangeRuleFromProto(rule))
}
httpkit.WriteOK(writer, request, map[string]any{"rules": rules})
}
// getMyGiftWall 返回当前登录用户已经收到的礼物墙gateway 不接受 user_id避免越权查询他人收礼事实。
func (h *Handler) getMyGiftWall(writer http.ResponseWriter, request *http.Request) {
if h.walletClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
resp, err := h.walletClient.GetUserGiftWall(request.Context(), &walletv1.GetUserGiftWallRequest{
RequestId: httpkit.RequestIDFromContext(request.Context()),
AppCode: appcode.FromContext(request.Context()),
UserId: auth.UserIDFromContext(request.Context()),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, giftWallFromProto(resp))
}
// listWalletTransactions 返回当前用户钱包流水分页。
func (h *Handler) listWalletTransactions(writer http.ResponseWriter, request *http.Request) {
h.listWalletTransactionsByAsset(writer, request, strings.TrimSpace(request.URL.Query().Get("asset_type")), 20)
}
// listCoinTransactions 返回当前用户金币流水分页;默认 page_size=30 对齐 App 最近 30 条展示。
func (h *Handler) listCoinTransactions(writer http.ResponseWriter, request *http.Request) {
h.listWalletTransactionsByAsset(writer, request, "COIN", 30)
}
func (h *Handler) listWalletTransactionsByAsset(writer http.ResponseWriter, request *http.Request, assetType string, defaultPageSize int32) {
if h.walletClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
page, ok := parsePositiveInt32Query(request, "page", 1)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
pageSize, ok := parsePositiveInt32Query(request, "page_size", defaultPageSize)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
resp, err := h.walletClient.ListWalletTransactions(request.Context(), &walletv1.ListWalletTransactionsRequest{
RequestId: httpkit.RequestIDFromContext(request.Context()),
AppCode: appcode.FromContext(request.Context()),
UserId: auth.UserIDFromContext(request.Context()),
AssetType: assetType,
Page: page,
PageSize: pageSize,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
items := make([]walletTransactionData, 0, len(resp.GetTransactions()))
for _, item := range resp.GetTransactions() {
items = append(items, walletTransactionFromProto(item))
}
httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": resp.GetTotal(), "page": page, "page_size": pageSize})
}
// applyWithdrawal 创建待人工审核的提现申请。
func (h *Handler) applyWithdrawal(writer http.ResponseWriter, request *http.Request) {
if h.walletClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
var body withdrawalApplyRequestBody
if !decode(writer, request, &body) {
return
}
commandID := strings.TrimSpace(body.CommandID)
if commandID == "" {
commandID = strings.TrimSpace(body.CommandIDAlt)
}
payoutAccount := strings.TrimSpace(body.PayoutAccount)
if payoutAccount == "" {
payoutAccount = strings.TrimSpace(body.PayoutAlt)
}
if commandID == "" || body.Amount <= 0 || payoutAccount == "" {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
resp, err := h.walletClient.ApplyWithdrawal(request.Context(), &walletv1.ApplyWithdrawalRequest{
CommandId: commandID,
AppCode: appcode.FromContext(request.Context()),
UserId: auth.UserIDFromContext(request.Context()),
Amount: body.Amount,
PayoutAccount: payoutAccount,
Reason: strings.TrimSpace(body.Reason),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, map[string]any{
"withdrawal": withdrawalFromProto(resp.GetWithdrawal()),
"balance": balanceFromProto(resp.GetBalance()),
})
}
func rechargeProductFromProto(product *walletv1.RechargeProduct) rechargeProductData {
if product == nil {
return rechargeProductData{}
}
return rechargeProductData{
ProductID: product.GetProductId(),
ProductCode: product.GetProductCode(),
ProductName: product.GetProductName(),
Description: product.GetDescription(),
Platform: product.GetPlatform(),
Channel: product.GetChannel(),
CurrencyCode: product.GetCurrencyCode(),
CoinAmount: product.GetCoinAmount(),
AmountMinor: product.GetAmountMinor(),
AmountMicro: product.GetAmountMicro(),
PolicyVersion: product.GetPolicyVersion(),
RegionIDs: product.GetRegionIds(),
ResourceAssetType: product.GetResourceAssetType(),
Status: product.GetStatus(),
Enabled: product.GetEnabled(),
SortOrder: product.GetSortOrder(),
}
}
func diamondExchangeRuleFromProto(rule *walletv1.DiamondExchangeRule) diamondExchangeRuleData {
if rule == nil {
return diamondExchangeRuleData{}
}
return diamondExchangeRuleData{
ExchangeType: rule.GetExchangeType(),
FromAssetType: rule.GetFromAssetType(),
ToAssetType: rule.GetToAssetType(),
FromAmount: rule.GetFromAmount(),
ToAmount: rule.GetToAmount(),
Enabled: rule.GetEnabled(),
}
}
func walletOverviewFromProto(resp *walletv1.GetWalletOverviewResponse) walletOverviewData {
if resp == nil {
return walletOverviewData{}
}
balances := make([]assetBalanceData, 0, len(resp.GetBalances()))
for _, balance := range resp.GetBalances() {
balances = append(balances, balanceFromProto(balance))
}
flags := resp.GetFeatureFlags()
return walletOverviewData{
Balances: balances,
FeatureFlags: walletFeatureFlagsData{
RechargeEnabled: flags.GetRechargeEnabled(),
DiamondExchangeEnabled: flags.GetDiamondExchangeEnabled(),
WithdrawEnabled: flags.GetWithdrawEnabled(),
},
}
}
func walletTransactionFromProto(item *walletv1.WalletTransaction) walletTransactionData {
if item == nil {
return walletTransactionData{}
}
return walletTransactionData{
EntryID: item.GetEntryId(),
TransactionID: item.GetTransactionId(),
BizType: item.GetBizType(),
AssetType: item.GetAssetType(),
AvailableDelta: item.GetAvailableDelta(),
FrozenDelta: item.GetFrozenDelta(),
AvailableAfter: item.GetAvailableAfter(),
FrozenAfter: item.GetFrozenAfter(),
CounterpartyUserID: item.GetCounterpartyUserId(),
RoomID: item.GetRoomId(),
CreatedAtMS: item.GetCreatedAtMs(),
}
}
func giftWallFromProto(resp *walletv1.GetUserGiftWallResponse) giftWallData {
if resp == nil {
return giftWallData{}
}
items := make([]giftWallItemData, 0, len(resp.GetItems()))
for _, item := range resp.GetItems() {
items = append(items, giftWallItemFromProto(item))
}
return giftWallData{
Items: items,
GiftKindCount: resp.GetGiftKindCount(),
GiftTotalCount: resp.GetGiftTotalCount(),
TotalValue: resp.GetTotalValue(),
TotalCoinValue: resp.GetTotalCoinValue(),
TotalDiamondValue: resp.GetTotalDiamondValue(),
TotalGiftPoint: resp.GetTotalGiftPoint(),
TotalHeatValue: resp.GetTotalHeatValue(),
}
}
func giftWallItemFromProto(item *walletv1.GiftWallItem) giftWallItemData {
if item == nil {
return giftWallItemData{}
}
return giftWallItemData{
GiftID: item.GetGiftId(),
GiftName: item.GetGiftName(),
ResourceID: item.GetResourceId(),
Resource: resourcePointerFromProto(item.GetResource()),
ResourceSnapshotJSON: item.GetResourceSnapshotJson(),
GiftTypeCode: item.GetGiftTypeCode(),
PresentationJSON: item.GetPresentationJson(),
GiftCount: item.GetGiftCount(),
TotalValue: item.GetTotalValue(),
TotalCoinValue: item.GetTotalCoinValue(),
TotalDiamondValue: item.GetTotalDiamondValue(),
TotalGiftPoint: item.GetTotalGiftPoint(),
TotalHeatValue: item.GetTotalHeatValue(),
ChargeAssetType: item.GetChargeAssetType(),
LastPriceVersion: item.GetLastPriceVersion(),
FirstReceivedAtMS: item.GetFirstReceivedAtMs(),
LastReceivedAtMS: item.GetLastReceivedAtMs(),
SortOrder: item.GetSortOrder(),
}
}
func withdrawalFromProto(item *walletv1.WithdrawalRequest) withdrawalData {
if item == nil {
return withdrawalData{}
}
return withdrawalData{
WithdrawalID: item.GetWithdrawalId(),
UserID: item.GetUserId(),
AssetType: item.GetAssetType(),
Amount: item.GetAmount(),
Status: item.GetStatus(),
PayoutAccount: item.GetPayoutAccount(),
Reason: item.GetReason(),
CreatedAtMS: item.GetCreatedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
}
}
func balanceFromProto(balance *walletv1.AssetBalance) assetBalanceData {
if balance == nil {
return assetBalanceData{}
}
return assetBalanceData{
AssetType: balance.GetAssetType(),
AvailableAmount: balance.GetAvailableAmount(),
FrozenAmount: balance.GetFrozenAmount(),
Version: balance.GetVersion(),
}
}