10.15 抓包确认:积分中心商城 PlayMallUI.queryAllocation 的商品与 webActUI.getActPrizeList(sid=2277) pid 完全一致,属于同一批商品,因此列表无需接商城链路;真号实测 sid=2277 返回 30 项(限量返场 99 共 12 项 + 精英专享 101 共 18 项),sid=2203 已返回空。\n\n- huya:解析商品 tag23/25/26/27/28(发布时间、上架/下架时间、每日时段),新增 PrizeInfo.CategoryIDs/InCategory/StartsAt/NotYetOnSale 与响应级 CategoryStats/CategorySummary/FilterByCategory,列表日志带类目统计\n- sniper:Config.Category 限定自动选品范围,未命中提示带类目\n- CLI:新增 -category / -category-only\n- GUI:Config.Category、ListPrizes 回传类目字典与统计;界面商品列表按类目分页签、每行标注类目、未上架显示上架时间与倒计时,所选类目随配置提交给监控\n- 测试:新增 internal/huya/category_test.go 与 sniper 类目用例;README/WSS_NOTES 补记结论
845 lines
23 KiB
Go
845 lines
23 KiB
Go
package huya
|
||
|
||
import (
|
||
"fmt"
|
||
"math"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"huya-go/internal/taf"
|
||
)
|
||
|
||
const (
|
||
ActivityWebUA = "web&1.0&huya"
|
||
ActivityRecordUA = "webh5&1.0.0&huya"
|
||
ActivityBindUA = "pc&1.0.0&huya"
|
||
ServantWebActUI = "webActUI"
|
||
FuncGetUserScore = "getUserScore"
|
||
FuncPrizeList = "getActPrizeList"
|
||
FuncPrizeRecords = "getUserPrizeRecords"
|
||
FuncExchangePrize = "scoreExchangePrize"
|
||
)
|
||
|
||
type ActivityUserID struct {
|
||
UID int64 `json:"uid"`
|
||
GUID string `json:"guid,omitempty"`
|
||
Token string `json:"token,omitempty"`
|
||
HuyaUA string `json:"huya_ua"`
|
||
Cookie string `json:"-"`
|
||
TokenType int32 `json:"token_type"`
|
||
DeviceInfo string `json:"device_info,omitempty"`
|
||
}
|
||
|
||
func NewActivityUserID(uid int64, cookie, ua string) ActivityUserID {
|
||
return ActivityUserID{
|
||
UID: uid,
|
||
HuyaUA: ua,
|
||
Cookie: NormalizeCookie(cookie),
|
||
TokenType: 0,
|
||
}
|
||
}
|
||
|
||
func (u *ActivityUserID) WriteTo(w *taf.Writer) error {
|
||
if err := w.WriteInt64(0, u.UID); err != nil {
|
||
return err
|
||
}
|
||
if err := w.WriteString(1, u.GUID); err != nil {
|
||
return err
|
||
}
|
||
if err := w.WriteString(2, u.Token); err != nil {
|
||
return err
|
||
}
|
||
if err := w.WriteString(3, u.HuyaUA); err != nil {
|
||
return err
|
||
}
|
||
if err := w.WriteString(4, u.Cookie); err != nil {
|
||
return err
|
||
}
|
||
if err := w.WriteInt32(5, int64(u.TokenType)); err != nil {
|
||
return err
|
||
}
|
||
return w.WriteString(6, u.DeviceInfo)
|
||
}
|
||
|
||
func (u *ActivityUserID) ReadFrom(r *taf.Reader) error {
|
||
var err error
|
||
if u.UID, err = r.ReadInt64(0, false, u.UID); err != nil {
|
||
return err
|
||
}
|
||
if u.GUID, err = r.ReadString(1, false, u.GUID); err != nil {
|
||
return err
|
||
}
|
||
if u.Token, err = r.ReadString(2, false, u.Token); err != nil {
|
||
return err
|
||
}
|
||
if u.HuyaUA, err = r.ReadString(3, false, u.HuyaUA); err != nil {
|
||
return err
|
||
}
|
||
if u.Cookie, err = r.ReadString(4, false, u.Cookie); err != nil {
|
||
return err
|
||
}
|
||
if u.TokenType, err = r.ReadInt32(5, false, u.TokenType); err != nil {
|
||
return err
|
||
}
|
||
u.DeviceInfo, err = r.ReadString(6, false, u.DeviceInfo)
|
||
return err
|
||
}
|
||
|
||
type GetUserScoreReq struct {
|
||
User ActivityUserID
|
||
SID int32
|
||
}
|
||
|
||
func (q *GetUserScoreReq) WriteTo(w *taf.Writer) error {
|
||
if err := w.WriteStruct(0, &q.User); err != nil {
|
||
return err
|
||
}
|
||
return w.WriteInt32(1, int64(q.SID))
|
||
}
|
||
|
||
func (q *GetUserScoreReq) ReadFrom(r *taf.Reader) error {
|
||
_, err := r.ReadStruct(0, false, &q.User)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
q.SID, err = r.ReadInt32(1, false, q.SID)
|
||
return err
|
||
}
|
||
|
||
type GetUserScoreResp struct {
|
||
Status int32 `json:"status"`
|
||
Msg string `json:"msg"`
|
||
GainScore int32 `json:"gain_score"`
|
||
UsedScore int32 `json:"used_score"`
|
||
NewGainScore int64 `json:"new_gain_score"`
|
||
NewUsedScore int64 `json:"new_used_score"`
|
||
Score int64 `json:"score"`
|
||
IsOpenExchangeTip int32 `json:"is_open_exchange_tip"`
|
||
}
|
||
|
||
func (r *GetUserScoreResp) WriteTo(w *taf.Writer) error { return nil }
|
||
|
||
func (r *GetUserScoreResp) ReadFrom(rd *taf.Reader) error {
|
||
var err error
|
||
if r.Status, err = rd.ReadInt32(0, false, r.Status); err != nil {
|
||
return err
|
||
}
|
||
if r.Msg, err = rd.ReadString(1, false, r.Msg); err != nil {
|
||
return err
|
||
}
|
||
if r.GainScore, err = rd.ReadInt32(2, false, r.GainScore); err != nil {
|
||
return err
|
||
}
|
||
if r.UsedScore, err = rd.ReadInt32(3, false, r.UsedScore); err != nil {
|
||
return err
|
||
}
|
||
if r.NewGainScore, err = rd.ReadInt64(4, false, r.NewGainScore); err != nil {
|
||
return err
|
||
}
|
||
if r.NewUsedScore, err = rd.ReadInt64(5, false, r.NewUsedScore); err != nil {
|
||
return err
|
||
}
|
||
if r.Score, err = rd.ReadInt64(6, false, r.Score); err != nil {
|
||
return err
|
||
}
|
||
r.IsOpenExchangeTip, err = rd.ReadInt32(7, false, r.IsOpenExchangeTip)
|
||
return err
|
||
}
|
||
|
||
func (r GetUserScoreResp) AvailableScore() int64 {
|
||
if r.NewGainScore != 0 || r.NewUsedScore != 0 {
|
||
score := r.NewGainScore - r.NewUsedScore
|
||
if score < 0 {
|
||
return 0
|
||
}
|
||
return score
|
||
}
|
||
return r.Score
|
||
}
|
||
|
||
type GetActPrizeListReq struct {
|
||
User ActivityUserID
|
||
SID int32
|
||
}
|
||
|
||
func (q *GetActPrizeListReq) WriteTo(w *taf.Writer) error {
|
||
if err := w.WriteStruct(0, &q.User); err != nil {
|
||
return err
|
||
}
|
||
return w.WriteInt32(1, int64(q.SID))
|
||
}
|
||
|
||
func (q *GetActPrizeListReq) ReadFrom(r *taf.Reader) error {
|
||
_, err := r.ReadStruct(0, false, &q.User)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
q.SID, err = r.ReadInt32(1, false, q.SID)
|
||
return err
|
||
}
|
||
|
||
type PrizeInfo struct {
|
||
ID int64 `json:"id"`
|
||
SID int64 `json:"sid"`
|
||
Name string `json:"name"`
|
||
Score int64 `json:"score"`
|
||
Type int32 `json:"type"`
|
||
Image string `json:"img,omitempty"`
|
||
Sort int32 `json:"sort"`
|
||
Frequency int32 `json:"frequency"`
|
||
FrequencyLimit int32 `json:"frequency_limit"`
|
||
CreateTime int64 `json:"create_time"`
|
||
Num int64 `json:"num"`
|
||
Status int32 `json:"status"`
|
||
Percent string `json:"percent"`
|
||
NewScore int64 `json:"new_score"`
|
||
// Tags 是奖品归属的类目 id(tag14,多个用逗号分隔)。
|
||
// 2026-10-15 实测:99=限量返场、101=精英专享,与响应 tag4 的字典一一对应。
|
||
Tags string `json:"tags,omitempty"`
|
||
LeftNum int64 `json:"left_num"`
|
||
IsShowNum int32 `json:"is_show_num"`
|
||
// 以下为 2026-10-15 抓包新确认的时间字段:限量返场商品是「预告上架」,
|
||
// 页面的「xx:xx:xx 后上架」倒计时就来自 StartTime。
|
||
PublishTime int64 `json:"publish_time,omitempty"` // tag23 商品发布时间
|
||
StartTime int64 `json:"start_time,omitempty"` // tag25 上架时间(秒)
|
||
EndTime int64 `json:"end_time,omitempty"` // tag26 下架时间(秒)
|
||
DailyStart string `json:"daily_start,omitempty"` // tag27 每日可兑时段开始
|
||
DailyEnd string `json:"daily_end,omitempty"` // tag28 每日可兑时段结束
|
||
}
|
||
|
||
func (p *PrizeInfo) WriteTo(w *taf.Writer) error { return nil }
|
||
|
||
func (p *PrizeInfo) ReadFrom(r *taf.Reader) error {
|
||
var err error
|
||
if p.ID, err = r.ReadInt64(0, false, p.ID); err != nil {
|
||
return err
|
||
}
|
||
if p.SID, err = r.ReadInt64(1, false, p.SID); err != nil {
|
||
return err
|
||
}
|
||
if p.Name, err = r.ReadString(2, false, p.Name); err != nil {
|
||
return err
|
||
}
|
||
if p.Score, err = r.ReadInt64(3, false, p.Score); err != nil {
|
||
return err
|
||
}
|
||
if p.Type, err = r.ReadInt32(4, false, p.Type); err != nil {
|
||
return err
|
||
}
|
||
if p.Image, err = r.ReadString(5, false, p.Image); err != nil {
|
||
return err
|
||
}
|
||
if p.Sort, err = r.ReadInt32(6, false, p.Sort); err != nil {
|
||
return err
|
||
}
|
||
if p.Frequency, err = r.ReadInt32(7, false, p.Frequency); err != nil {
|
||
return err
|
||
}
|
||
if p.FrequencyLimit, err = r.ReadInt32(8, false, p.FrequencyLimit); err != nil {
|
||
return err
|
||
}
|
||
if p.CreateTime, err = r.ReadInt64(9, false, p.CreateTime); err != nil {
|
||
return err
|
||
}
|
||
if p.Num, err = r.ReadInt64(10, false, p.Num); err != nil {
|
||
return err
|
||
}
|
||
if p.Status, err = r.ReadInt32(11, false, p.Status); err != nil {
|
||
return err
|
||
}
|
||
if p.Percent, err = r.ReadString(12, false, p.Percent); err != nil {
|
||
return err
|
||
}
|
||
if p.NewScore, err = r.ReadInt64(13, false, p.NewScore); err != nil {
|
||
return err
|
||
}
|
||
if p.Tags, err = r.ReadString(14, false, p.Tags); err != nil {
|
||
return err
|
||
}
|
||
if p.LeftNum, err = r.ReadInt64(15, false, p.LeftNum); err != nil {
|
||
return err
|
||
}
|
||
if p.IsShowNum, err = r.ReadInt32(16, false, p.IsShowNum); err != nil {
|
||
return err
|
||
}
|
||
if p.PublishTime, err = r.ReadInt64(23, false, p.PublishTime); err != nil {
|
||
return err
|
||
}
|
||
if p.StartTime, err = r.ReadInt64(25, false, p.StartTime); err != nil {
|
||
return err
|
||
}
|
||
if p.EndTime, err = r.ReadInt64(26, false, p.EndTime); err != nil {
|
||
return err
|
||
}
|
||
if p.DailyStart, err = r.ReadString(27, false, p.DailyStart); err != nil {
|
||
return err
|
||
}
|
||
p.DailyEnd, err = r.ReadString(28, false, p.DailyEnd)
|
||
return err
|
||
}
|
||
|
||
// CategoryIDs 返回奖品归属的类目 id(tag14,逗号分隔,可能是空串)。
|
||
func (p PrizeInfo) CategoryIDs() []int64 {
|
||
text := strings.TrimSpace(p.Tags)
|
||
if text == "" {
|
||
return nil
|
||
}
|
||
parts := strings.Split(text, ",")
|
||
ids := make([]int64, 0, len(parts))
|
||
for _, part := range parts {
|
||
id, err := strconv.ParseInt(strings.TrimSpace(part), 10, 64)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
ids = append(ids, id)
|
||
}
|
||
return ids
|
||
}
|
||
|
||
// InCategory 判断奖品是否属于指定类目 id。
|
||
func (p PrizeInfo) InCategory(id int64) bool {
|
||
for _, item := range p.CategoryIDs() {
|
||
if item == id {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// StartsAt 返回上架时间;未下发时返回零值时间。
|
||
func (p PrizeInfo) StartsAt() time.Time {
|
||
if p.StartTime <= 0 {
|
||
return time.Time{}
|
||
}
|
||
return time.Unix(p.StartTime, 0)
|
||
}
|
||
|
||
// EndsAt 返回下架时间;未下发时返回零值时间。
|
||
func (p PrizeInfo) EndsAt() time.Time {
|
||
if p.EndTime <= 0 {
|
||
return time.Time{}
|
||
}
|
||
return time.Unix(p.EndTime, 0)
|
||
}
|
||
|
||
// NotYetOnSale 表示商品只是「预告上架」,还没到可兑换时间。
|
||
func (p PrizeInfo) NotYetOnSale(now time.Time) bool {
|
||
start := p.StartsAt()
|
||
return !start.IsZero() && now.Before(start)
|
||
}
|
||
|
||
func (p PrizeInfo) Price() int64 {
|
||
if p.NewScore != 0 {
|
||
return p.NewScore
|
||
}
|
||
return p.Score
|
||
}
|
||
|
||
func (p PrizeInfo) EstimatedLeft() int64 {
|
||
left, ok := p.InventoryLeft()
|
||
if ok {
|
||
return left
|
||
}
|
||
return 0
|
||
}
|
||
|
||
func (p PrizeInfo) InventoryLeft() (int64, bool) {
|
||
if p.LeftNum > 0 {
|
||
return p.LeftNum, true
|
||
}
|
||
text := strings.TrimSuffix(strings.TrimSpace(p.Percent), "%")
|
||
if text == "" || p.Num <= 0 {
|
||
return 0, false
|
||
}
|
||
percent, err := strconv.ParseFloat(text, 64)
|
||
if err != nil {
|
||
return 0, false
|
||
}
|
||
return int64(math.Floor(float64(p.Num)*percent/100.0 + 1e-9)), true
|
||
}
|
||
|
||
type PrizeTag struct {
|
||
ID int64 `json:"id"`
|
||
Name string `json:"name"`
|
||
Weight int32 `json:"weight"`
|
||
}
|
||
|
||
func (t *PrizeTag) WriteTo(w *taf.Writer) error { return nil }
|
||
|
||
func (t *PrizeTag) ReadFrom(r *taf.Reader) error {
|
||
var err error
|
||
if t.ID, err = r.ReadInt64(0, false, t.ID); err != nil {
|
||
return err
|
||
}
|
||
if t.Name, err = r.ReadString(1, false, t.Name); err != nil {
|
||
return err
|
||
}
|
||
t.Weight, err = r.ReadInt32(2, false, t.Weight)
|
||
return err
|
||
}
|
||
|
||
type GetActPrizeListResp struct {
|
||
Status int32 `json:"status"`
|
||
Msg string `json:"msg"`
|
||
Prizes []PrizeInfo `json:"prizes"`
|
||
ModuleID int32 `json:"module_id"`
|
||
Tags []PrizeTag `json:"tags"`
|
||
}
|
||
|
||
func (r *GetActPrizeListResp) WriteTo(w *taf.Writer) error { return nil }
|
||
|
||
func (r *GetActPrizeListResp) ReadFrom(rd *taf.Reader) error {
|
||
var err error
|
||
if r.Status, err = rd.ReadInt32(0, false, r.Status); err != nil {
|
||
return err
|
||
}
|
||
if r.Msg, err = rd.ReadString(1, false, r.Msg); err != nil {
|
||
return err
|
||
}
|
||
items, err := rd.ReadListStruct(2, false, func() taf.Unmarshaler { return &PrizeInfo{} })
|
||
if err != nil {
|
||
return err
|
||
}
|
||
r.Prizes = make([]PrizeInfo, 0, len(items))
|
||
for _, item := range items {
|
||
if prize, ok := item.(*PrizeInfo); ok {
|
||
r.Prizes = append(r.Prizes, *prize)
|
||
}
|
||
}
|
||
if r.ModuleID, err = rd.ReadInt32(3, false, r.ModuleID); err != nil {
|
||
return err
|
||
}
|
||
tags, err := rd.ReadListStruct(4, false, func() taf.Unmarshaler { return &PrizeTag{} })
|
||
if err != nil {
|
||
return err
|
||
}
|
||
r.Tags = make([]PrizeTag, 0, len(tags))
|
||
for _, item := range tags {
|
||
if tag, ok := item.(*PrizeTag); ok {
|
||
r.Tags = append(r.Tags, *tag)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// CategoryStat 汇总一个类目下的商品数量与库存。
|
||
type CategoryStat struct {
|
||
ID int64 `json:"id"`
|
||
Name string `json:"name"`
|
||
Count int `json:"count"`
|
||
Left int64 `json:"left"`
|
||
Total int64 `json:"total"`
|
||
}
|
||
|
||
// CategoryName 按类目 id 查名字;字典里没有时返回空串。
|
||
func (r GetActPrizeListResp) CategoryName(id int64) string {
|
||
for _, tag := range r.Tags {
|
||
if tag.ID == id {
|
||
return tag.Name
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// CategoriesOf 返回某个奖品所属的类目(字典里找不到的 id 用 "类目<id>" 兜底)。
|
||
func (r GetActPrizeListResp) CategoriesOf(p PrizeInfo) []PrizeTag {
|
||
ids := p.CategoryIDs()
|
||
items := make([]PrizeTag, 0, len(ids))
|
||
for _, id := range ids {
|
||
if name := r.CategoryName(id); name != "" {
|
||
items = append(items, PrizeTag{ID: id, Name: name})
|
||
continue
|
||
}
|
||
items = append(items, PrizeTag{ID: id, Name: fmt.Sprintf("类目%d", id)})
|
||
}
|
||
return items
|
||
}
|
||
|
||
// CategoryStats 按响应里的类目字典统计商品数、剩余量与总量。
|
||
//
|
||
// 统计顺序与响应下发的类目顺序一致(页面 tab 顺序),便于日志与界面直接复用。
|
||
func (r GetActPrizeListResp) CategoryStats() []CategoryStat {
|
||
stats := make([]CategoryStat, 0, len(r.Tags))
|
||
for _, tag := range r.Tags {
|
||
prizes := make([]PrizeInfo, 0, len(r.Prizes))
|
||
for _, prize := range r.Prizes {
|
||
if prize.InCategory(tag.ID) {
|
||
prizes = append(prizes, prize)
|
||
}
|
||
}
|
||
left, total, _, _ := prizeListInventorySummary(prizes)
|
||
stats = append(stats, CategoryStat{ID: tag.ID, Name: tag.Name, Count: len(prizes), Left: left, Total: total})
|
||
}
|
||
return stats
|
||
}
|
||
|
||
// CategorySummary 生成「限量返场(99)=12项/精英专享(101)=18项」这样的单行摘要,供日志使用。
|
||
func (r GetActPrizeListResp) CategorySummary() string {
|
||
stats := r.CategoryStats()
|
||
if len(stats) == 0 {
|
||
return fmt.Sprintf("未下发类目(共%d项)", len(r.Prizes))
|
||
}
|
||
parts := make([]string, 0, len(stats))
|
||
for _, stat := range stats {
|
||
parts = append(parts, fmt.Sprintf("%s(%d)=%d项", stat.Name, stat.ID, stat.Count))
|
||
}
|
||
return strings.Join(parts, "/")
|
||
}
|
||
|
||
// FilterByCategory 按类目过滤商品。
|
||
//
|
||
// category 支持:空串(不过滤)、类目 id(如 "99")、类目名(如 "限量返场",
|
||
// 先精确匹配再模糊匹配)。类目字典里没有匹配项时,退回用原始 tag14 文本匹配,
|
||
// 这样服务端新增类目但没下发字典时也不会把商品全部过滤掉。
|
||
func (r GetActPrizeListResp) FilterByCategory(category string) []PrizeInfo {
|
||
key := strings.TrimSpace(category)
|
||
if key == "" {
|
||
return r.Prizes
|
||
}
|
||
ids := map[int64]bool{}
|
||
if id, err := strconv.ParseInt(key, 10, 64); err == nil {
|
||
ids[id] = true
|
||
} else {
|
||
for _, tag := range r.Tags {
|
||
if strings.EqualFold(strings.TrimSpace(tag.Name), key) {
|
||
ids[tag.ID] = true
|
||
}
|
||
}
|
||
if len(ids) == 0 {
|
||
for _, tag := range r.Tags {
|
||
if strings.Contains(tag.Name, key) {
|
||
ids[tag.ID] = true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
out := make([]PrizeInfo, 0, len(r.Prizes))
|
||
for _, prize := range r.Prizes {
|
||
if len(ids) > 0 {
|
||
matched := false
|
||
for id := range ids {
|
||
if prize.InCategory(id) {
|
||
matched = true
|
||
break
|
||
}
|
||
}
|
||
if matched {
|
||
out = append(out, prize)
|
||
}
|
||
continue
|
||
}
|
||
if strings.Contains(prize.Tags, key) {
|
||
out = append(out, prize)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
type GetUserPrizeRecordsReq struct {
|
||
User ActivityUserID
|
||
SID int32
|
||
}
|
||
|
||
func (q *GetUserPrizeRecordsReq) WriteTo(w *taf.Writer) error {
|
||
if err := w.WriteStruct(0, &q.User); err != nil {
|
||
return err
|
||
}
|
||
return w.WriteInt32(1, int64(q.SID))
|
||
}
|
||
|
||
func (q *GetUserPrizeRecordsReq) ReadFrom(r *taf.Reader) error {
|
||
_, err := r.ReadStruct(0, false, &q.User)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
q.SID, err = r.ReadInt32(1, false, q.SID)
|
||
return err
|
||
}
|
||
|
||
// UserPrizeInfo 对应 getUserPrizeRecords 的一条兑换记录。
|
||
//
|
||
// 除 tag0~tag10 外,2026-10-13 HAR 实测还有 tag11~tag23:
|
||
// 12=orderId、14=奖品图、16=奖品 pid、17=商城 itemId(数量级与 shopMiddleUI
|
||
// 的 itemId 一致,语义未在样本中最终确认)、19=prizeItemId、20=扩展 map
|
||
// (game/cjm/plat/isRole/liveLinkActId)、21=兑换日期(如 20261013)。
|
||
type UserPrizeInfo struct {
|
||
ID int64 `json:"id"`
|
||
Name string `json:"name"`
|
||
Score int64 `json:"score"`
|
||
CreateTime int64 `json:"create_time"`
|
||
Status int32 `json:"status"`
|
||
Type int32 `json:"type"`
|
||
IsReal int32 `json:"is_real"`
|
||
NewScore int64 `json:"new_score"`
|
||
Account string `json:"account,omitempty"`
|
||
Code string `json:"code,omitempty"`
|
||
CommonPrizeType int32 `json:"common_prize_type"`
|
||
OrderID string `json:"order_id,omitempty"`
|
||
Image string `json:"img,omitempty"`
|
||
PrizeID int64 `json:"prize_id,omitempty"`
|
||
ShopItemID int64 `json:"shop_item_id,omitempty"`
|
||
ItemID int64 `json:"item_id,omitempty"`
|
||
Ext map[string]string `json:"ext,omitempty"`
|
||
ExchangeDate int32 `json:"exchange_date,omitempty"`
|
||
}
|
||
|
||
func (p *UserPrizeInfo) WriteTo(w *taf.Writer) error { return nil }
|
||
|
||
func (p *UserPrizeInfo) ReadFrom(r *taf.Reader) error {
|
||
var err error
|
||
if p.ID, err = r.ReadInt64(0, false, p.ID); err != nil {
|
||
return err
|
||
}
|
||
if p.Name, err = r.ReadString(1, false, p.Name); err != nil {
|
||
return err
|
||
}
|
||
if p.Score, err = r.ReadInt64(2, false, p.Score); err != nil {
|
||
return err
|
||
}
|
||
if p.CreateTime, err = r.ReadInt64(3, false, p.CreateTime); err != nil {
|
||
return err
|
||
}
|
||
if p.Status, err = r.ReadInt32(4, false, p.Status); err != nil {
|
||
return err
|
||
}
|
||
if p.Type, err = r.ReadInt32(5, false, p.Type); err != nil {
|
||
return err
|
||
}
|
||
if p.IsReal, err = r.ReadInt32(6, false, p.IsReal); err != nil {
|
||
return err
|
||
}
|
||
if p.NewScore, err = r.ReadInt64(7, false, p.NewScore); err != nil {
|
||
return err
|
||
}
|
||
if p.Account, err = r.ReadString(8, false, p.Account); err != nil {
|
||
return err
|
||
}
|
||
if p.Code, err = r.ReadString(9, false, p.Code); err != nil {
|
||
return err
|
||
}
|
||
if p.CommonPrizeType, err = r.ReadInt32(10, false, p.CommonPrizeType); err != nil {
|
||
return err
|
||
}
|
||
if p.OrderID, err = r.ReadString(12, false, p.OrderID); err != nil {
|
||
return err
|
||
}
|
||
if p.Image, err = r.ReadString(14, false, p.Image); err != nil {
|
||
return err
|
||
}
|
||
if p.PrizeID, err = r.ReadInt64(16, false, p.PrizeID); err != nil {
|
||
return err
|
||
}
|
||
if p.ShopItemID, err = r.ReadInt64(17, false, p.ShopItemID); err != nil {
|
||
return err
|
||
}
|
||
if p.ItemID, err = r.ReadInt64(19, false, p.ItemID); err != nil {
|
||
return err
|
||
}
|
||
if p.Ext, err = r.ReadMapStringString(20, false); err != nil {
|
||
return err
|
||
}
|
||
p.ExchangeDate, err = r.ReadInt32(21, false, p.ExchangeDate)
|
||
return err
|
||
}
|
||
|
||
type GetUserPrizeRecordsResp struct {
|
||
Status int32 `json:"status"`
|
||
Msg string `json:"msg"`
|
||
Records []UserPrizeInfo `json:"records"`
|
||
}
|
||
|
||
func (r *GetUserPrizeRecordsResp) WriteTo(w *taf.Writer) error { return nil }
|
||
|
||
func (r *GetUserPrizeRecordsResp) ReadFrom(rd *taf.Reader) error {
|
||
var err error
|
||
if r.Status, err = rd.ReadInt32(0, false, r.Status); err != nil {
|
||
return err
|
||
}
|
||
if r.Msg, err = rd.ReadString(1, false, r.Msg); err != nil {
|
||
return err
|
||
}
|
||
items, err := rd.ReadListStruct(2, false, func() taf.Unmarshaler { return &UserPrizeInfo{} })
|
||
if err != nil {
|
||
return err
|
||
}
|
||
r.Records = make([]UserPrizeInfo, 0, len(items))
|
||
for _, item := range items {
|
||
if record, ok := item.(*UserPrizeInfo); ok {
|
||
r.Records = append(r.Records, *record)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
type ScoreExchangePrizeReq struct {
|
||
User ActivityUserID
|
||
SID int32
|
||
PID int32
|
||
IP string
|
||
ClientEnv map[string]string
|
||
Source string
|
||
IsRole int32
|
||
}
|
||
|
||
func (q *ScoreExchangePrizeReq) WriteTo(w *taf.Writer) error {
|
||
if err := w.WriteStruct(0, &q.User); err != nil {
|
||
return err
|
||
}
|
||
if err := w.WriteInt32(1, int64(q.SID)); err != nil {
|
||
return err
|
||
}
|
||
if err := w.WriteInt32(2, int64(q.PID)); err != nil {
|
||
return err
|
||
}
|
||
if err := w.WriteString(3, q.IP); err != nil {
|
||
return err
|
||
}
|
||
if err := w.WriteMapStringString(4, q.ClientEnv); err != nil {
|
||
return err
|
||
}
|
||
if err := w.WriteString(5, q.Source); err != nil {
|
||
return err
|
||
}
|
||
if q.IsRole == 0 {
|
||
q.IsRole = 1
|
||
}
|
||
return w.WriteInt32(6, int64(q.IsRole))
|
||
}
|
||
|
||
func (q *ScoreExchangePrizeReq) ReadFrom(r *taf.Reader) error {
|
||
_, err := r.ReadStruct(0, false, &q.User)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if q.SID, err = r.ReadInt32(1, false, q.SID); err != nil {
|
||
return err
|
||
}
|
||
if q.PID, err = r.ReadInt32(2, false, q.PID); err != nil {
|
||
return err
|
||
}
|
||
if q.IP, err = r.ReadString(3, false, q.IP); err != nil {
|
||
return err
|
||
}
|
||
if q.ClientEnv, err = r.ReadMapStringString(4, false); err != nil {
|
||
return err
|
||
}
|
||
if q.Source, err = r.ReadString(5, false, q.Source); err != nil {
|
||
return err
|
||
}
|
||
q.IsRole, err = r.ReadInt32(6, false, q.IsRole)
|
||
return err
|
||
}
|
||
|
||
type ExchangeInfo struct {
|
||
PID int32 `json:"pid"`
|
||
ExchangeScore int64 `json:"exchange_score"`
|
||
Desc string `json:"desc"`
|
||
}
|
||
|
||
func (e *ExchangeInfo) WriteTo(w *taf.Writer) error { return nil }
|
||
|
||
func (e *ExchangeInfo) ReadFrom(r *taf.Reader) error {
|
||
var err error
|
||
if e.PID, err = r.ReadInt32(1, false, e.PID); err != nil {
|
||
return err
|
||
}
|
||
if e.ExchangeScore, err = r.ReadInt64(2, false, e.ExchangeScore); err != nil {
|
||
return err
|
||
}
|
||
e.Desc, err = r.ReadString(3, false, e.Desc)
|
||
return err
|
||
}
|
||
|
||
type ActPreCondition struct {
|
||
ConditionType int32 `json:"condition_type"`
|
||
ConditionParams map[string]string `json:"condition_params,omitempty"`
|
||
Tip string `json:"tip,omitempty"`
|
||
JumpURL string `json:"jump_url,omitempty"`
|
||
ButtonDesc string `json:"button_desc,omitempty"`
|
||
}
|
||
|
||
func (c *ActPreCondition) WriteTo(w *taf.Writer) error { return nil }
|
||
|
||
func (c *ActPreCondition) ReadFrom(r *taf.Reader) error {
|
||
var err error
|
||
if c.ConditionType, err = r.ReadInt32(0, false, c.ConditionType); err != nil {
|
||
return err
|
||
}
|
||
if c.ConditionParams, err = r.ReadMapStringString(1, false); err != nil {
|
||
return err
|
||
}
|
||
if c.Tip, err = r.ReadString(2, false, c.Tip); err != nil {
|
||
return err
|
||
}
|
||
if c.JumpURL, err = r.ReadString(3, false, c.JumpURL); err != nil {
|
||
return err
|
||
}
|
||
c.ButtonDesc, err = r.ReadString(4, false, c.ButtonDesc)
|
||
return err
|
||
}
|
||
|
||
type ScoreExchangePrizeResp struct {
|
||
Status int32 `json:"status"`
|
||
Msg string `json:"msg"`
|
||
OrderID string `json:"order_id,omitempty"`
|
||
ExchangeInfo ExchangeInfo `json:"exchange_info"`
|
||
ActPreCondition ActPreCondition `json:"act_pre_condition"`
|
||
}
|
||
|
||
func (r *ScoreExchangePrizeResp) WriteTo(w *taf.Writer) error { return nil }
|
||
|
||
func (r *ScoreExchangePrizeResp) ReadFrom(rd *taf.Reader) error {
|
||
var err error
|
||
if r.Status, err = rd.ReadInt32(0, false, r.Status); err != nil {
|
||
return err
|
||
}
|
||
if r.Msg, err = rd.ReadString(1, false, r.Msg); err != nil {
|
||
return err
|
||
}
|
||
if r.OrderID, err = rd.ReadString(3, false, r.OrderID); err != nil {
|
||
return err
|
||
}
|
||
if _, err = rd.ReadStruct(4, false, &r.ExchangeInfo); err != nil {
|
||
return err
|
||
}
|
||
_, err = rd.ReadStruct(5, false, &r.ActPreCondition)
|
||
return err
|
||
}
|
||
|
||
func (r ScoreExchangePrizeResp) Success() bool {
|
||
return r.Status == 200 && (r.Msg == "" || strings.Contains(r.Msg, "成功"))
|
||
}
|
||
|
||
func prizeListInventorySummary(prizes []PrizeInfo) (left, total int64, leftKnown, totalKnown int) {
|
||
for _, prize := range prizes {
|
||
if prize.Num > 0 {
|
||
total += prize.Num
|
||
totalKnown++
|
||
}
|
||
if itemLeft, ok := prize.InventoryLeft(); ok {
|
||
left += itemLeft
|
||
leftKnown++
|
||
}
|
||
}
|
||
return left, total, leftKnown, totalKnown
|
||
}
|
||
|
||
func exchangeResponseMessage(resp *ScoreExchangePrizeResp) string {
|
||
if resp == nil {
|
||
return ""
|
||
}
|
||
for _, text := range []string{
|
||
resp.Msg,
|
||
resp.ActPreCondition.Tip,
|
||
resp.ActPreCondition.ButtonDesc,
|
||
resp.ExchangeInfo.Desc,
|
||
resp.OrderID,
|
||
} {
|
||
if strings.TrimSpace(text) != "" {
|
||
return strings.TrimSpace(text)
|
||
}
|
||
}
|
||
return ""
|
||
}
|