修改礼物tab接口

This commit is contained in:
zhx 2026-05-25 21:03:37 +08:00
parent 9040b4b161
commit 5000076365
5 changed files with 137 additions and 16 deletions

View File

@ -6,6 +6,7 @@
- 礼物 Tab 基础配置入口是 `GET /api/v1/gift-tabs`,不需要 `room_id`,只返回后台启用的 Tab 配置。 - 礼物 Tab 基础配置入口是 `GET /api/v1/gift-tabs`,不需要 `room_id`,只返回后台启用的 Tab 配置。
- `GET /api/v1/gift-tabs` 不按礼物数量过滤;只要后台启用了 Tab即使该 Tab 下当前没有礼物也必须返回给 App 展示。 - `GET /api/v1/gift-tabs` 不按礼物数量过滤;只要后台启用了 Tab即使该 Tab 下当前没有礼物也必须返回给 App 展示。
- `GET /api/v1/gift-tabs` 会按 Tab 返回当前 active 礼物列表App 可以用它提前预加载礼物资源;无礼物的 Tab 返回 `gifts: []`
- 房间送礼面板入口是 `GET /api/v1/rooms/{room_id}/gift-panel`,返回当前房间可送礼物、可收礼人、金币余额和房间内可展示 Tab。 - 房间送礼面板入口是 `GET /api/v1/rooms/{room_id}/gift-panel`,返回当前房间可送礼物、可收礼人、金币余额和房间内可展示 Tab。
- `GET /api/v1/gift-tabs` 不能替代 `GET /api/v1/rooms/{room_id}/gift-panel`App 真正送礼前必须以房间面板返回的数据为准。 - `GET /api/v1/gift-tabs` 不能替代 `GET /api/v1/rooms/{room_id}/gift-panel`App 真正送礼前必须以房间面板返回的数据为准。
- 送礼只调用 `POST /api/v1/rooms/gift/send` - 送礼只调用 `POST /api/v1/rooms/gift/send`
@ -33,6 +34,12 @@ GET /api/v1/gift-tabs
X-App-Code: lalu X-App-Code: lalu
``` ```
查询参数:
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `region_id` | int64 | 否 | 不传时预加载全部 active 礼物;传入时只返回 GLOBAL 和该区域可用礼物。 |
成功响应: 成功响应:
```json ```json
@ -52,7 +59,24 @@ X-App-Code: lalu
"order": 10, "order": 10,
"sort_order": 10, "sort_order": 10,
"created_at_ms": 1710000000000, "created_at_ms": 1710000000000,
"updated_at_ms": 1710000000000 "updated_at_ms": 1710000000000,
"gifts": [
{
"gift_id": "rose",
"resource_id": 11,
"gift_type_code": "normal",
"name": "Rose",
"coin_price": 100,
"resource": {
"resource_id": 11,
"resource_code": "gift_rose",
"resource_type": "gift",
"name": "Rose",
"status": "active",
"asset_url": "https://cdn.example/rose.png"
}
}
]
} }
], ],
"total": 1 "total": 1
@ -74,6 +98,7 @@ X-App-Code: lalu
| `sort_order` | int | 后台配置排序值。 | | `sort_order` | int | 后台配置排序值。 |
| `created_at_ms` | int64 | 创建时间Unix epoch milliseconds。 | | `created_at_ms` | int64 | 创建时间Unix epoch milliseconds。 |
| `updated_at_ms` | int64 | 更新时间Unix epoch milliseconds。 | | `updated_at_ms` | int64 | 更新时间Unix epoch milliseconds。 |
| `gifts` | array | 当前 Tab 下的 active 礼物列表;没有礼物时返回空数组。 |
## 获取礼物面板 ## 获取礼物面板

View File

@ -1904,9 +1904,16 @@ paths:
- resources - resources
summary: App 礼物 Tab 配置列表 summary: App 礼物 Tab 配置列表
operationId: listGiftTabs operationId: listGiftTabs
parameters:
- name: region_id
in: query
required: false
type: integer
format: int64
description: 不传时预加载全部 active 礼物;传入时只返回 GLOBAL 和该区域可用礼物。
responses: responses:
"200": "200":
description: 返回后台启用的礼物 Tab 配置;不按礼物数量过滤,即使 Tab 下当前没有礼物也返回。 description: 返回后台启用的礼物 Tab 配置和当前 active 礼物;不按礼物数量过滤 Tab,即使 Tab 下当前没有礼物也返回 gifts 空数组
schema: schema:
$ref: "#/definitions/GiftTabListEnvelope" $ref: "#/definitions/GiftTabListEnvelope"
"502": "502":
@ -2580,6 +2587,11 @@ definitions:
updated_at_ms: updated_at_ms:
type: integer type: integer
format: int64 format: int64
gifts:
type: array
description: 当前 Tab 下的 active 礼物列表;没有礼物时返回空数组。
items:
$ref: "#/definitions/GiftConfigData"
DeletePushTokenData: DeletePushTokenData:
type: object type: object
required: required:

View File

@ -84,7 +84,7 @@
| GET | `/api/v1/resources` | resources | `listResources` | App 资源列表 | | GET | `/api/v1/resources` | resources | `listResources` | App 资源列表 |
| GET | `/api/v1/resource-groups/{group_id}` | resources | `getResourceGroup` | App 资源组详情 | | GET | `/api/v1/resource-groups/{group_id}` | resources | `getResourceGroup` | App 资源组详情 |
| GET | `/api/v1/gifts` | resources | `listGifts` | App 礼物列表 | | GET | `/api/v1/gifts` | resources | `listGifts` | App 礼物列表 |
| GET | `/api/v1/gift-tabs` | resources | `listGiftTabs` | App 礼物 Tab 配置列表,不依赖房间 | | GET | `/api/v1/gift-tabs` | resources | `listGiftTabs` | App 礼物 Tab 配置和礼物预加载列表,不依赖房间 |
| GET | `/api/v1/users/me/resources` | resources | `listMyResources` | 我的资源权益 | | GET | `/api/v1/users/me/resources` | resources | `listMyResources` | 我的资源权益 |
| POST | `/api/v1/users/me/resources/{resource_id}/equip` | resources | `equipMyResource` | 佩戴我的资源 | | POST | `/api/v1/users/me/resources/{resource_id}/equip` | resources | `equipMyResource` | 佩戴我的资源 |
| GET | `/api/v1/messages/tabs` | messages | `listMessageTabs` | 消息 tab 分区摘要 | | GET | `/api/v1/messages/tabs` | messages | `listMessageTabs` | 消息 tab 分区摘要 |

View File

@ -92,16 +92,17 @@ type giftConfigData struct {
} }
type giftTabData struct { type giftTabData struct {
Key string `json:"key"` Key string `json:"key"`
GiftTypeCode string `json:"gift_type_code"` GiftTypeCode string `json:"gift_type_code"`
Name string `json:"name"` Name string `json:"name"`
Label string `json:"label"` Label string `json:"label"`
TabKey string `json:"tab_key"` TabKey string `json:"tab_key"`
Status string `json:"status"` Status string `json:"status"`
Order int32 `json:"order"` Order int32 `json:"order"`
SortOrder int32 `json:"sort_order"` SortOrder int32 `json:"sort_order"`
CreatedAtMS int64 `json:"created_at_ms"` CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"`
Gifts []giftConfigData `json:"gifts"`
} }
type emojiPackData struct { type emojiPackData struct {
@ -236,6 +237,11 @@ func (h *Handler) listGiftTabs(writer http.ResponseWriter, request *http.Request
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return return
} }
regionID, filterRegion, ok := optionalNonNegativeInt64Query(request, "region_id")
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
resp, err := h.walletClient.ListGiftTypeConfigs(request.Context(), &walletv1.ListGiftTypeConfigsRequest{ resp, err := h.walletClient.ListGiftTypeConfigs(request.Context(), &walletv1.ListGiftTypeConfigsRequest{
RequestId: httpkit.RequestIDFromContext(request.Context()), RequestId: httpkit.RequestIDFromContext(request.Context()),
AppCode: appcode.FromContext(request.Context()), AppCode: appcode.FromContext(request.Context()),
@ -245,13 +251,52 @@ func (h *Handler) listGiftTabs(writer http.ResponseWriter, request *http.Request
httpkit.WriteRPCError(writer, request, err) httpkit.WriteRPCError(writer, request, err)
return return
} }
gifts, err := h.listAllActiveGifts(request, regionID, filterRegion)
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
giftsByType := make(map[string][]giftConfigData, len(gifts))
for _, item := range gifts {
giftsByType[item.GiftTypeCode] = append(giftsByType[item.GiftTypeCode], item)
}
items := make([]giftTabData, 0, len(resp.GetGiftTypes())) items := make([]giftTabData, 0, len(resp.GetGiftTypes()))
for _, item := range resp.GetGiftTypes() { for _, item := range resp.GetGiftTypes() {
items = append(items, giftTabFromProto(item)) tab := giftTabFromProto(item)
tab.Gifts = giftsByType[tab.GiftTypeCode]
if tab.Gifts == nil {
tab.Gifts = []giftConfigData{}
}
items = append(items, tab)
} }
httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items)}) httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items)})
} }
func (h *Handler) listAllActiveGifts(request *http.Request, regionID int64, filterRegion bool) ([]giftConfigData, error) {
const pageSize int32 = 100
items := make([]giftConfigData, 0, pageSize)
for page := int32(1); ; page++ {
resp, err := h.walletClient.ListGiftConfigs(request.Context(), &walletv1.ListGiftConfigsRequest{
RequestId: httpkit.RequestIDFromContext(request.Context()),
AppCode: appcode.FromContext(request.Context()),
Page: page,
PageSize: pageSize,
ActiveOnly: true,
RegionId: regionID,
FilterRegion: filterRegion,
})
if err != nil {
return nil, err
}
for _, gift := range resp.GetGifts() {
items = append(items, giftFromProto(gift))
}
if len(resp.GetGifts()) == 0 || int64(len(items)) >= resp.GetTotal() {
return items, nil
}
}
}
func (h *Handler) listEmojiPacks(writer http.ResponseWriter, request *http.Request) { func (h *Handler) listEmojiPacks(writer http.ResponseWriter, request *http.Request) {
if h.walletClient == nil { if h.walletClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
@ -460,6 +505,7 @@ func giftTabFromProto(item *walletv1.GiftTypeConfig) giftTabData {
SortOrder: item.GetSortOrder(), SortOrder: item.GetSortOrder(),
CreatedAtMS: item.GetCreatedAtMs(), CreatedAtMS: item.GetCreatedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(), UpdatedAtMS: item.GetUpdatedAtMs(),
Gifts: []giftConfigData{},
} }
} }

View File

@ -369,6 +369,7 @@ type fakeWalletClient struct {
lastGetResourceGroup *walletv1.GetResourceGroupRequest lastGetResourceGroup *walletv1.GetResourceGroupRequest
resourceGroupsByID map[int64]*walletv1.ResourceGroup resourceGroupsByID map[int64]*walletv1.ResourceGroup
lastListGiftConfigs *walletv1.ListGiftConfigsRequest lastListGiftConfigs *walletv1.ListGiftConfigsRequest
listGiftConfigsResp *walletv1.ListGiftConfigsResponse
lastListGiftTypes *walletv1.ListGiftTypeConfigsRequest lastListGiftTypes *walletv1.ListGiftTypeConfigsRequest
listGiftTypesResp *walletv1.ListGiftTypeConfigsResponse listGiftTypesResp *walletv1.ListGiftTypeConfigsResponse
lastGrantResource *walletv1.GrantResourceRequest lastGrantResource *walletv1.GrantResourceRequest
@ -1153,6 +1154,12 @@ func (f *fakeWalletClient) GetResourceGroup(_ context.Context, req *walletv1.Get
func (f *fakeWalletClient) ListGiftConfigs(_ context.Context, req *walletv1.ListGiftConfigsRequest) (*walletv1.ListGiftConfigsResponse, error) { func (f *fakeWalletClient) ListGiftConfigs(_ context.Context, req *walletv1.ListGiftConfigsRequest) (*walletv1.ListGiftConfigsResponse, error) {
f.lastListGiftConfigs = req f.lastListGiftConfigs = req
if f.err != nil {
return nil, f.err
}
if f.listGiftConfigsResp != nil {
return f.listGiftConfigsResp, nil
}
return &walletv1.ListGiftConfigsResponse{}, nil return &walletv1.ListGiftConfigsResponse{}, nil
} }
@ -3252,6 +3259,26 @@ func TestListEmojiPacksFiltersRegionAndReturnsAssets(t *testing.T) {
func TestListGiftTabsReturnsActiveTypeConfigs(t *testing.T) { func TestListGiftTabsReturnsActiveTypeConfigs(t *testing.T) {
walletClient := &fakeWalletClient{ walletClient := &fakeWalletClient{
listGiftConfigsResp: &walletv1.ListGiftConfigsResponse{
Gifts: []*walletv1.GiftConfig{
{
GiftId: "rose",
ResourceId: 11,
Status: "active",
Name: "Rose",
GiftTypeCode: "normal",
CoinPrice: 100,
Resource: &walletv1.Resource{
ResourceId: 11,
ResourceCode: "gift_rose",
ResourceType: "gift",
Name: "Rose",
Status: "active",
},
},
},
Total: 1,
},
listGiftTypesResp: &walletv1.ListGiftTypeConfigsResponse{ listGiftTypesResp: &walletv1.ListGiftTypeConfigsResponse{
GiftTypes: []*walletv1.GiftTypeConfig{ GiftTypes: []*walletv1.GiftTypeConfig{
{ {
@ -3296,6 +3323,11 @@ func TestListGiftTabsReturnsActiveTypeConfigs(t *testing.T) {
Order int32 `json:"order"` Order int32 `json:"order"`
SortOrder int32 `json:"sort_order"` SortOrder int32 `json:"sort_order"`
UpdatedAtMS int64 `json:"updated_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"`
Gifts []struct {
GiftID string `json:"gift_id"`
GiftTypeCode string `json:"gift_type_code"`
CoinPrice int64 `json:"coin_price"`
} `json:"gifts"`
} `json:"items"` } `json:"items"`
Total int `json:"total"` Total int `json:"total"`
} `json:"data"` } `json:"data"`
@ -3306,8 +3338,8 @@ func TestListGiftTabsReturnsActiveTypeConfigs(t *testing.T) {
if walletClient.lastListGiftTypes == nil || walletClient.lastListGiftTypes.GetAppCode() != "lalu" || !walletClient.lastListGiftTypes.GetActiveOnly() { if walletClient.lastListGiftTypes == nil || walletClient.lastListGiftTypes.GetAppCode() != "lalu" || !walletClient.lastListGiftTypes.GetActiveOnly() {
t.Fatalf("gift tabs request mismatch: %+v", walletClient.lastListGiftTypes) t.Fatalf("gift tabs request mismatch: %+v", walletClient.lastListGiftTypes)
} }
if walletClient.lastListGiftConfigs != nil { if walletClient.lastListGiftConfigs == nil || walletClient.lastListGiftConfigs.GetAppCode() != "lalu" || !walletClient.lastListGiftConfigs.GetActiveOnly() || walletClient.lastListGiftConfigs.GetPageSize() != 100 || walletClient.lastListGiftConfigs.GetFilterRegion() {
t.Fatalf("gift tabs must not filter tabs by gift configs: %+v", walletClient.lastListGiftConfigs) t.Fatalf("gift tabs gift preload request mismatch: %+v", walletClient.lastListGiftConfigs)
} }
if envelope.Code != httpkit.CodeOK || envelope.Data.Total != 2 || len(envelope.Data.Items) != 2 { if envelope.Code != httpkit.CodeOK || envelope.Data.Total != 2 || len(envelope.Data.Items) != 2 {
t.Fatalf("gift tabs envelope mismatch: %+v", envelope) t.Fatalf("gift tabs envelope mismatch: %+v", envelope)
@ -3315,6 +3347,12 @@ func TestListGiftTabsReturnsActiveTypeConfigs(t *testing.T) {
if envelope.Data.Items[0].Key != "normal" || envelope.Data.Items[0].GiftTypeCode != "normal" || envelope.Data.Items[0].Name != "普通礼物" || envelope.Data.Items[0].Label != "Gift" || envelope.Data.Items[0].TabKey != "Gift" || envelope.Data.Items[0].Order != 10 || envelope.Data.Items[0].SortOrder != 10 || envelope.Data.Items[0].UpdatedAtMS != 1_700_000_000_100 { if envelope.Data.Items[0].Key != "normal" || envelope.Data.Items[0].GiftTypeCode != "normal" || envelope.Data.Items[0].Name != "普通礼物" || envelope.Data.Items[0].Label != "Gift" || envelope.Data.Items[0].TabKey != "Gift" || envelope.Data.Items[0].Order != 10 || envelope.Data.Items[0].SortOrder != 10 || envelope.Data.Items[0].UpdatedAtMS != 1_700_000_000_100 {
t.Fatalf("first gift tab mismatch: %+v", envelope.Data.Items[0]) t.Fatalf("first gift tab mismatch: %+v", envelope.Data.Items[0])
} }
if len(envelope.Data.Items[0].Gifts) != 1 || envelope.Data.Items[0].Gifts[0].GiftID != "rose" || envelope.Data.Items[0].Gifts[0].GiftTypeCode != "normal" || envelope.Data.Items[0].Gifts[0].CoinPrice != 100 {
t.Fatalf("first gift tab gifts mismatch: %+v", envelope.Data.Items[0].Gifts)
}
if len(envelope.Data.Items[1].Gifts) != 0 {
t.Fatalf("empty gift tab must still be returned with empty gifts: %+v", envelope.Data.Items[1])
}
} }
func TestMessageReadAllReadAndDeleteRoutes(t *testing.T) { func TestMessageReadAllReadAndDeleteRoutes(t *testing.T) {