优化和平小店碎片兑换链路
This commit is contained in:
@@ -6,6 +6,7 @@ import { LoadProducts } from '@wails/go/app/App'
|
||||
export interface Product {
|
||||
commodityId: string
|
||||
commodityName: string
|
||||
actionId?: string
|
||||
category: string
|
||||
score: number
|
||||
payType?: number
|
||||
|
||||
@@ -13,6 +13,12 @@ const seckillStateEvent = "seckill:state"
|
||||
|
||||
// StartSeckill 立即启动抢购。
|
||||
func (a *App) StartSeckill(accountProducts map[string]string) types.MessageResult {
|
||||
// 兼容旧前端/旧构建:和平小店只能走 livebuy 专用链路,不能进入 CSRF 兑换流程。
|
||||
if a.getClient().Activity.IsXiaodian {
|
||||
return a.startXiaodianSeckill(types.FixedSeckillRequest{
|
||||
AccountProducts: accountProducts,
|
||||
}, "和平小店抢购已启动")
|
||||
}
|
||||
if len(accountProducts) == 0 {
|
||||
return types.MessageResult{Error: "请选择账号和商品"}
|
||||
}
|
||||
|
||||
@@ -256,6 +256,7 @@ type xiaodianRecommendation struct {
|
||||
type xiaodianItem struct {
|
||||
GoodsID string `json:"iGoodsId"`
|
||||
GoodsName string `json:"sGoodsName"`
|
||||
ActionID xiaodianFlexibleString `json:"iActionId"`
|
||||
Category xiaodianFlexibleString `json:"iCategoryId"`
|
||||
Price string `json:"iPrice"`
|
||||
JB2Price string `json:"iJb2Price"`
|
||||
@@ -264,7 +265,6 @@ type xiaodianItem struct {
|
||||
GoodsPic string `json:"sGoodsPic"`
|
||||
ShowBegin string `json:"dtShowBeginTime"`
|
||||
ShowEnd string `json:"dtShowEndTime"`
|
||||
ActionID string `json:"iActionId"`
|
||||
}
|
||||
|
||||
type xiaodianFlexibleString string
|
||||
@@ -328,6 +328,16 @@ type xiaodianBuyInfo struct {
|
||||
|
||||
// xiaodianEmbedQuery 获取斗鱼为当前账号签发的短时道具城参数。
|
||||
func (c *DouyuClient) xiaodianEmbedQuery(cookie string) (url.Values, error) {
|
||||
return c.xiaodianEmbedQueryWithCache(cookie, true)
|
||||
}
|
||||
|
||||
// xiaodianEmbedQueryFresh 每次调用都重新获取 H5 授权,供会真实扣款的兑换请求使用。
|
||||
// code/sig/timestamp 是短时凭证,不应跨兑换轮次长期复用。
|
||||
func (c *DouyuClient) xiaodianEmbedQueryFresh(cookie string) (url.Values, error) {
|
||||
return c.xiaodianEmbedQueryWithCache(cookie, false)
|
||||
}
|
||||
|
||||
func (c *DouyuClient) xiaodianEmbedQueryWithCache(cookie string, useCache bool) (url.Values, error) {
|
||||
uid := cookieValue(cookie, "acf_uid")
|
||||
stk := cookieValue(cookie, "acf_stk")
|
||||
ltkid := cookieValue(cookie, "acf_ltkid")
|
||||
@@ -335,8 +345,10 @@ func (c *DouyuClient) xiaodianEmbedQuery(cookie string) (url.Values, error) {
|
||||
return nil, errors.New("斗鱼 Cookie 缺少 acf_uid、acf_stk 或 acf_ltkid,无法获取和平小店授权")
|
||||
}
|
||||
cacheKey := uid + ":" + c.Activity.ActAlias + ":" + c.Activity.RoomID
|
||||
if cached, ok := c.getXiaodianEmbedCache(cacheKey); ok {
|
||||
return cached, nil
|
||||
if useCache {
|
||||
if cached, ok := c.getXiaodianEmbedCache(cacheKey); ok {
|
||||
return cached, nil
|
||||
}
|
||||
}
|
||||
endpoint, err := url.Parse(c.Activity.RoleParamURL)
|
||||
if err != nil {
|
||||
@@ -539,6 +551,7 @@ func (c *DouyuClient) xiaodianProducts(cookie string, options types.XiaodianProd
|
||||
products = append(products, types.Product{
|
||||
CommodityID: item.GoodsID,
|
||||
CommodityName: item.GoodsName,
|
||||
ActionID: string(item.ActionID),
|
||||
Category: string(item.Category),
|
||||
Score: price,
|
||||
PayType: payType,
|
||||
@@ -1231,7 +1244,7 @@ func (c *DouyuClient) xiaodianExchange(cookie string, product types.Product) (st
|
||||
if strings.TrimSpace(product.CommodityID) == "" {
|
||||
return "", errors.New("商品 ID 为空")
|
||||
}
|
||||
embedQuery, err := c.xiaodianEmbedQuery(cookie)
|
||||
embedQuery, err := c.xiaodianEmbedQueryFresh(cookie)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -1254,7 +1267,11 @@ func (c *DouyuClient) xiaodianExchange(cookie string, product types.Product) (st
|
||||
query.Set("_jsvar", "buyInfo")
|
||||
query.Set("acctype", "livelink")
|
||||
query.Set("_app_id", "2123")
|
||||
query.Set("iActionId", c.Activity.ActID)
|
||||
actionID := strings.TrimSpace(product.ActionID)
|
||||
if actionID == "" {
|
||||
actionID = c.Activity.ActID
|
||||
}
|
||||
query.Set("iActionId", actionID)
|
||||
query.Set("propid", product.CommodityID)
|
||||
query.Set("paytype", strconv.Itoa(payType))
|
||||
query.Set("source", c.Activity.RoomID)
|
||||
|
||||
@@ -177,7 +177,7 @@ func TestProductsAutomaticallyResolvesXiaodianRoleAndPaginates(t *testing.T) {
|
||||
} else {
|
||||
switch offset {
|
||||
case "0":
|
||||
items = []map[string]string{{"iGoodsId": "g1", "sGoodsName": "商品一", "iCategoryId": "1", "iPrice": "10", "sPayType": "qpqb", "iGoodsLeft": "-1"}, {"iGoodsId": "g2", "sGoodsName": "特惠商品", "iCategoryId": "45", "iPrice": "0", "iJb2Price": "20", "sPayType": "jb2", "iGoodsLeft": "3"}}
|
||||
items = []map[string]string{{"iGoodsId": "g1", "iActionId": "act-g1", "sGoodsName": "商品一", "iCategoryId": "1", "iPrice": "10", "sPayType": "qpqb", "iGoodsLeft": "-1"}, {"iGoodsId": "g2", "sGoodsName": "特惠商品", "iCategoryId": "45", "iPrice": "0", "iJb2Price": "20", "sPayType": "jb2", "iGoodsLeft": "3"}}
|
||||
case "2":
|
||||
items = []map[string]string{{"iGoodsId": "g2", "sGoodsName": "重复商品", "iCategoryId": "45", "iPrice": "0", "iJb2Price": "20", "sPayType": "jb2", "iGoodsLeft": "3"}, {"iGoodsId": "g4", "sGoodsName": "商品四", "iCategoryId": "1", "iPrice": "40", "sPayType": "qpqb", "iGoodsLeft": "0"}}
|
||||
}
|
||||
@@ -219,6 +219,9 @@ func TestProductsAutomaticallyResolvesXiaodianRoleAndPaginates(t *testing.T) {
|
||||
if products[1].Category != "45" || products[3].Score != 30 {
|
||||
t.Fatalf("商品字段 = %#v", products)
|
||||
}
|
||||
if products[0].ActionID != "act-g1" {
|
||||
t.Fatalf("商品动作 ID = %q", products[0].ActionID)
|
||||
}
|
||||
if products[0].PayType != 1 || products[0].PayLabel != "点券" || products[1].PayType != 5 || products[1].PayLabel != "扭蛋碎片" || products[1].Score != 20 {
|
||||
t.Fatalf("支付字段 = %#v", products)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ import (
|
||||
// runAttempt 标准模式兑换:完整执行鉴权 token→BatchLimit→PreExchange→Exchange 链路。
|
||||
func (s *Scheduler) runAttempt(attempt types.ExchangeAttempt) types.ExchangeResult {
|
||||
cli := s.clientSnapshot()
|
||||
// 最终防护:任何旧入口在和平小店活动下都必须使用 livebuy。
|
||||
if cli.Activity.IsXiaodian {
|
||||
return s.runXiaodianAttempt(attempt)
|
||||
}
|
||||
result := types.ExchangeResult{
|
||||
Account: attempt.Account.Name,
|
||||
Product: attempt.Product.CommodityName,
|
||||
@@ -101,6 +105,9 @@ func (s *Scheduler) runAttempt(attempt types.ExchangeAttempt) types.ExchangeResu
|
||||
// runAttemptFast 激进模式的兑换逻辑,复用缓存的鉴权 token,跳过 BatchExchangeLimit 和 PreExchange。
|
||||
func (s *Scheduler) runAttemptFast(attempt types.ExchangeAttempt) types.ExchangeResult {
|
||||
cli := s.clientSnapshot()
|
||||
if cli.Activity.IsXiaodian {
|
||||
return s.runXiaodianAttempt(attempt)
|
||||
}
|
||||
result := types.ExchangeResult{
|
||||
Account: attempt.Account.Name,
|
||||
Product: attempt.Product.CommodityName,
|
||||
|
||||
+14
-12
@@ -96,18 +96,20 @@ type Account struct {
|
||||
type Product struct {
|
||||
CommodityID string `json:"commodityId"`
|
||||
CommodityName string `json:"commodityName"`
|
||||
Category string `json:"category"`
|
||||
Score int `json:"score"`
|
||||
PayType int `json:"payType,omitempty"`
|
||||
PayLabel string `json:"payLabel,omitempty"`
|
||||
Count int `json:"count"`
|
||||
Pic string `json:"pic"`
|
||||
Status int `json:"status"`
|
||||
OpenStatus int `json:"openStatus"`
|
||||
StoreStatus int `json:"storeStatus"`
|
||||
TimeLimited int `json:"timeLimited"`
|
||||
CountLimited int `json:"countLimited"`
|
||||
EliteLimited int `json:"eliteLimited"`
|
||||
// ActionID 是和平小店商品所属的动作 ID;不同商品可能不属于同一个动作。
|
||||
ActionID string `json:"actionId,omitempty"`
|
||||
Category string `json:"category"`
|
||||
Score int `json:"score"`
|
||||
PayType int `json:"payType,omitempty"`
|
||||
PayLabel string `json:"payLabel,omitempty"`
|
||||
Count int `json:"count"`
|
||||
Pic string `json:"pic"`
|
||||
Status int `json:"status"`
|
||||
OpenStatus int `json:"openStatus"`
|
||||
StoreStatus int `json:"storeStatus"`
|
||||
TimeLimited int `json:"timeLimited"`
|
||||
CountLimited int `json:"countLimited"`
|
||||
EliteLimited int `json:"eliteLimited"`
|
||||
}
|
||||
|
||||
// ProductPayload 前端商品响应。
|
||||
|
||||
Reference in New Issue
Block a user