472 lines
18 KiB
Go
472 lines
18 KiB
Go
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(),
|
||
}
|
||
}
|