2026-02-01 00:27:38 +08:00

576 lines
22 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 douyin
import (
"bindbox-game/internal/pkg/logger"
"bindbox-game/internal/repository/mysql"
"bindbox-game/internal/repository/mysql/dao"
"bindbox-game/internal/repository/mysql/model"
"bindbox-game/internal/service/game"
"bindbox-game/internal/service/sysconfig"
"context"
"encoding/json"
"fmt"
"io"
"math"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"unicode"
"go.uber.org/zap"
"bindbox-game/internal/service/user"
)
// 系统配置键
const (
ConfigKeyDouyinCookie = "douyin_cookie"
ConfigKeyDouyinInterval = "douyin_sync_interval_minutes"
)
type Service interface {
// FetchAndSyncOrders 从抖店 API 获取订单并同步到本地 (按绑定用户同步)
FetchAndSyncOrders(ctx context.Context) (*SyncResult, error)
// SyncAllOrders 批量同步所有订单变更 (基于更新时间,不分状态)
SyncAllOrders(ctx context.Context, duration time.Duration) (*SyncResult, error)
// ListOrders 获取本地抖店订单列表
ListOrders(ctx context.Context, page, pageSize int, status *int) ([]*model.DouyinOrders, int64, error)
// GetConfig 获取抖店配置
GetConfig(ctx context.Context) (*DouyinConfig, error)
// SaveConfig 保存抖店配置
SaveConfig(ctx context.Context, cookie string, intervalMinutes int) error
// SyncOrder 同步单个订单到本地可传入建议关联的用户ID和商品ID
SyncOrder(ctx context.Context, item *DouyinOrderItem, suggestUserID int64, productID string) (isNew bool, isMatched bool)
// GrantMinesweeperQualifications 自动补发扫雷资格
GrantMinesweeperQualifications(ctx context.Context) error
// GrantLivestreamPrizes 自动发放直播间奖品
GrantLivestreamPrizes(ctx context.Context) error
// SyncRefundStatus 同步退款状态
SyncRefundStatus(ctx context.Context) error
}
type DouyinConfig struct {
Cookie string `json:"cookie"`
IntervalMinutes int `json:"interval_minutes"`
}
type SyncResult struct {
TotalFetched int `json:"total_fetched"`
NewOrders int `json:"new_orders"`
MatchedUsers int `json:"matched_users"`
Orders []*model.DouyinOrders `json:"orders"` // 新增:返回详情以供后续处理
DebugInfo string `json:"debug_info"`
}
type service struct {
logger logger.CustomLogger
repo mysql.Repo
readDB *dao.Query
writeDB *dao.Query
syscfg sysconfig.Service
ticketSvc game.TicketService
userSvc user.Service
rewardDispatcher *RewardDispatcher
}
func New(l logger.CustomLogger, repo mysql.Repo, syscfg sysconfig.Service, ticketSvc game.TicketService, userSvc user.Service, titleSvc TitleAssigner) Service {
// 创建奖励发放器
var dispatcher *RewardDispatcher
if titleSvc != nil {
dispatcher = NewRewardDispatcher(ticketSvc, userSvc, titleSvc)
}
return &service{
logger: l,
repo: repo,
readDB: dao.Use(repo.GetDbR()),
writeDB: dao.Use(repo.GetDbW()),
syscfg: syscfg,
ticketSvc: ticketSvc,
userSvc: userSvc,
rewardDispatcher: dispatcher,
}
}
// GetConfig 获取抖店配置
func (s *service) GetConfig(ctx context.Context) (*DouyinConfig, error) {
cfg := &DouyinConfig{IntervalMinutes: 5}
if c, err := s.syscfg.GetByKey(ctx, ConfigKeyDouyinCookie); err == nil && c != nil {
cfg.Cookie = c.ConfigValue
}
if c, err := s.syscfg.GetByKey(ctx, ConfigKeyDouyinInterval); err == nil && c != nil {
if v, e := strconv.Atoi(c.ConfigValue); e == nil && v > 0 {
cfg.IntervalMinutes = v
}
}
return cfg, nil
}
// SaveConfig 保存抖店配置
func (s *service) SaveConfig(ctx context.Context, cookie string, intervalMinutes int) error {
if _, err := s.syscfg.UpsertByKey(ctx, ConfigKeyDouyinCookie, cookie, "抖店Cookie"); err != nil {
return err
}
if intervalMinutes < 1 {
intervalMinutes = 5
}
if _, err := s.syscfg.UpsertByKey(ctx, ConfigKeyDouyinInterval, strconv.Itoa(intervalMinutes), "抖店订单同步间隔(分钟)"); err != nil {
return err
}
return nil
}
// ListOrders 获取本地抖店订单列表
func (s *service) ListOrders(ctx context.Context, page, pageSize int, status *int) ([]*model.DouyinOrders, int64, error) {
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 20
}
db := s.repo.GetDbR().WithContext(ctx).Model(&model.DouyinOrders{})
if status != nil {
db = db.Where("order_status = ?", *status)
}
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
var orders []*model.DouyinOrders
if err := db.Order("id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&orders).Error; err != nil {
return nil, 0, err
}
return orders, total, nil
}
// FetchAndSyncOrders 遍历所有已绑定抖音号的用户并同步其订单
func (s *service) FetchAndSyncOrders(ctx context.Context) (*SyncResult, error) {
cfg, err := s.GetConfig(ctx)
if err != nil {
return nil, fmt.Errorf("获取配置失败: %w", err)
}
if cfg.Cookie == "" {
return nil, fmt.Errorf("抖店 Cookie 未配置")
}
// 1. 获取所有绑定了抖音号的用户
var users []model.Users
if err := s.repo.GetDbR().WithContext(ctx).Where("douyin_user_id != ''").Find(&users).Error; err != nil {
return nil, fmt.Errorf("获取绑定用户失败: %w", err)
}
result := &SyncResult{}
fmt.Printf("[DEBUG] 开始全量同步,共 %d 个绑定用户\n", len(users))
// 2. 遍历用户,按 buyer 抓取订单
for _, u := range users {
fmt.Printf("[DEBUG] 正在同步用户 ID: %d (昵称: %s, 抖音号: %s) 的订单...\n", u.ID, u.Nickname, u.DouyinUserID)
orders, err := s.fetchDouyinOrdersByBuyer(cfg.Cookie, u.DouyinUserID)
if err != nil {
fmt.Printf("[DEBUG] 抓取用户 %s 订单失败: %v\n", u.DouyinUserID, err)
continue
}
result.TotalFetched += len(orders)
// 3. 同步
for _, order := range orders {
// 同步订单(传入建议关联的用户 ID
isNew, matched := s.SyncOrder(ctx, &order, u.ID, "")
if isNew {
result.NewOrders++
}
if matched {
result.MatchedUsers++
}
}
}
result.DebugInfo += fmt.Sprintf("\n同步完成: 总抓取 %d, 新订单 %d, 匹配用户 %d", result.TotalFetched, result.NewOrders, result.MatchedUsers)
return result, nil
}
// removed SyncShopOrders
// 抖店 API 响应结构
type douyinOrderResponse struct {
Code int `json:"code"`
St int `json:"st"` // 抖店实际返回的是 st 而非 code
Msg string `json:"msg"`
Data []DouyinOrderItem `json:"data"` // data 直接是数组
}
type DouyinOrderItem struct {
ShopOrderID string `json:"shop_order_id"`
OrderStatus int `json:"order_status"`
UserID string `json:"user_id"`
ActualReceiveAmount any `json:"actual_receive_amount"`
ActualPayAmount any `json:"actual_pay_amount"`
PayTypeDesc string `json:"pay_type_desc"`
Remark string `json:"remark"`
UserNickname string `json:"user_nickname"`
ProductCount int64 `json:"product_count"` // 抖店返回的商品数量
ProductItemList []DouyinProductItem `json:"product_item"` // 商品详情列表
SkuOrderList []SkuOrderItem `json:"sku_order_list"`
}
type DouyinProductItem struct {
ProductID string `json:"product_id"`
ProductName string `json:"product_name"`
ComboNum int64 `json:"combo_num"`
TotalProductCount int64 `json:"total_product_count"`
}
type SkuOrderItem struct {
ProductID string `json:"product_id"`
ProductName string `json:"product_name"`
SkuID string `json:"sku_id"`
}
// fetchDouyinOrdersByBuyer 调用抖店 API 按 Buyer ID 获取订单 (保持向后兼容)
func (s *service) fetchDouyinOrdersByBuyer(cookie string, buyer string) ([]DouyinOrderItem, error) {
params := url.Values{}
params.Set("page", "0")
params.Set("pageSize", "100")
params.Set("buyer", buyer)
params.Set("order_by", "create_time")
params.Set("order", "desc")
params.Set("tab", "all")
params.Set("appid", "1")
params.Set("_bid", "ffa_order")
params.Set("aid", "4272")
return s.fetchDouyinOrders(cookie, params)
}
// fetchDouyinOrders 通用的抖店订单抓取方法
func (s *service) fetchDouyinOrders(cookie string, params url.Values) ([]DouyinOrderItem, error) {
baseUrl := "https://fxg.jinritemai.com/api/order/searchlist"
fullUrl := baseUrl + "?" + params.Encode()
req, err := http.NewRequest("GET", fullUrl, nil)
if err != nil {
return nil, err
}
// 设置请求头
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36")
req.Header.Set("Accept", "application/json, text/plain, */*")
req.Header.Set("Cookie", cookie)
req.Header.Set("Referer", "https://fxg.jinritemai.com/ffa/morder/order/list")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var respData douyinOrderResponse
if err := json.Unmarshal(body, &respData); err != nil {
s.logger.Error("[抖店API] 解析响应失败", zap.String("response", string(body[:min(len(body), 5000)])))
return nil, fmt.Errorf("解析响应失败: %w", err)
}
// 临时调试日志:打印第一笔订单的金额字段
if len(respData.Data) > 0 {
fmt.Printf("[DEBUG] 抖店订单 0 金额测试: RawBody(500)=%s\n", string(body[:min(len(body), 500)]))
}
if respData.St != 0 && respData.Code != 0 {
return nil, fmt.Errorf("API 返回错误: %s (ST:%d CODE:%d)", respData.Msg, respData.St, respData.Code)
}
return respData.Data, nil
}
// SyncOrder 同步单个订单到本地
func (s *service) SyncOrder(ctx context.Context, item *DouyinOrderItem, suggestUserID int64, productID string) (isNew bool, isMatched bool) {
db := s.repo.GetDbW().WithContext(ctx)
// 解析金额工具函数
parseMoney := func(val any) int64 {
if val == nil {
return 0
}
// JSON 数字会被解析为 float64
if f, ok := val.(float64); ok {
// 如果是数值类型,但带有小数部分(如 138.4),通常是元单位
if f != math.Trunc(f) {
return int64(f*100 + 0.5)
}
// 如果是整数,保持原样(分)
return int64(f)
}
s := fmt.Sprintf("%v", val)
s = strings.TrimSpace(s)
if s == "" {
return 0
}
// 只保留数字和点号 (处理 "¥158.40" 这种情况)
var sb strings.Builder
for _, r := range s {
if unicode.IsDigit(r) || r == '.' {
sb.WriteRune(r)
}
}
cleanStr := sb.String()
if f, err := strconv.ParseFloat(cleanStr, 64); err == nil {
// 字符串一律按元转分处理 (兼容旧逻辑)
return int64(f*100 + 0.5)
}
return 0
}
var order model.DouyinOrders
err := db.Where("shop_order_id = ?", item.ShopOrderID).First(&order).Error
if err == nil {
// 订单已存在
isNew = false
// 只有当订单还没关联用户,且提供了建议用户时,才做关联
if (order.LocalUserID == "" || order.LocalUserID == "0") && suggestUserID > 0 {
order.LocalUserID = strconv.FormatInt(suggestUserID, 10)
db.Model(&order).Update("local_user_id", order.LocalUserID)
fmt.Printf("[DEBUG] 抖店辅助关联成功: %s -> User %d\n", item.ShopOrderID, suggestUserID)
}
// 更新状态与金额 (确保之前因解析失败导致的 0 金额被修复)
db.Model(&order).Updates(map[string]any{
"order_status": item.OrderStatus,
"remark": item.Remark,
"actual_receive_amount": parseMoney(item.ActualReceiveAmount),
"actual_pay_amount": parseMoney(item.ActualPayAmount),
})
// 重要:同步内存状态
order.OrderStatus = int32(item.OrderStatus)
order.Remark = item.Remark
} else {
// 订单不存在,创建新记录
isNew = true
localUserIDStr := "0"
if suggestUserID > 0 {
localUserIDStr = strconv.FormatInt(suggestUserID, 10)
}
fmt.Printf("[DEBUG] 抖店新订单: %s, UserID: %s, Recommend: %s\n", item.ShopOrderID, item.UserID, localUserIDStr)
amount := parseMoney(item.ActualReceiveAmount)
payAmount := parseMoney(item.ActualPayAmount)
// 计算商品数量:如果指定了 productID则只统计该商品的数量否则使用总数量
pCount := item.ProductCount
if productID != "" && len(item.ProductItemList) > 0 {
var matchedCount int64
for _, pi := range item.ProductItemList {
if pi.ProductID == productID {
// 有些情况下 TotalProductCount 准确,有些 ComboNum 准确
// 用户反馈的 JSON 中 ComboNum=2, TotalProductCount=2
// 优先使用 ComboNum
if pi.ComboNum > 0 {
matchedCount += pi.ComboNum
} else {
matchedCount += pi.TotalProductCount
}
}
}
if matchedCount > 0 {
pCount = matchedCount
}
}
// 如果没指定 productID尝试自动填补
if productID == "" && len(item.ProductItemList) > 0 {
if len(item.ProductItemList) == 1 {
// 只有一个商品时自动使用该商品ID
productID = item.ProductItemList[0].ProductID
} else {
// 多个商品时使用第一个商品ID记录主商品
productID = item.ProductItemList[0].ProductID
// 记录日志,方便后续分析多商品订单
fmt.Printf("[WARN] 订单 %s 包含多个商品(%d个)使用第一个商品ID: %s\n",
item.ShopOrderID, len(item.ProductItemList), productID)
}
}
rawData, _ := json.Marshal(item)
order = model.DouyinOrders{
ShopOrderID: item.ShopOrderID,
DouyinProductID: productID, // 写入商品ID
ProductCount: int32(pCount), // 写入计算后的商品数量
OrderStatus: int32(item.OrderStatus),
DouyinUserID: item.UserID,
ActualReceiveAmount: amount,
ActualPayAmount: payAmount,
PayTypeDesc: item.PayTypeDesc,
Remark: item.Remark,
UserNickname: item.UserNickname,
RawData: string(rawData),
RewardGranted: 0,
LocalUserID: localUserIDStr,
}
if err := db.Create(&order).Error; err != nil {
return false, false
}
}
// 如果还没关联用户(比如之前全量抓取的),尝试用抖店的 UID (long string) 匹配
if (order.LocalUserID == "" || order.LocalUserID == "0") && item.UserID != "" {
var user model.Users
if err := s.repo.GetDbR().Where("douyin_user_id = ?", item.UserID).First(&user).Error; err == nil {
order.LocalUserID = strconv.FormatInt(user.ID, 10)
db.Model(&order).Update("local_user_id", order.LocalUserID)
fmt.Printf("[DEBUG] 通过抖店 UID 匹配成功: User %d\n", user.ID)
}
}
// ---- 统一处理:发放奖励 ----
isMatched = order.LocalUserID != "" && order.LocalUserID != "0"
// 订单完成且未发放奖励时根据产品ID查询奖励规则并发放
if isMatched && order.RewardGranted == 0 && order.OrderStatus == 2 && order.DouyinProductID != "" {
// 检查黑名单
var blacklistCount int64
if err := db.Table("douyin_blacklist").Where("douyin_user_id = ?", item.UserID).Count(&blacklistCount).Error; err == nil && blacklistCount > 0 {
fmt.Printf("[DEBUG] 用户 %s 在黑名单中,跳过发奖\n", item.UserID)
return isNew, isMatched
}
localUserID, _ := strconv.ParseInt(order.LocalUserID, 10, 64)
if localUserID <= 0 {
return isNew, isMatched
}
// 查询该商品的所有奖励规则 (status=1 表示启用)
var rewards []model.DouyinProductRewards
if err := db.Where("product_id = ? AND status = 1", order.DouyinProductID).Find(&rewards).Error; err != nil {
fmt.Printf("[DEBUG] 查询奖励规则失败: %v\n", err)
return isNew, isMatched
}
if len(rewards) == 0 {
fmt.Printf("[DEBUG] 订单 %s 未找到奖励规则,跳过发奖\n", item.ShopOrderID)
return isNew, isMatched
}
// 遍历所有规则发放奖励
allSuccess := true
hasFlipCard := false
grantedCount := 0
for _, reward := range rewards {
// 翻牌游戏不自动发放,等待用户手动翻牌
if s.rewardDispatcher != nil && s.rewardDispatcher.IsFlipCardReward(reward) {
fmt.Printf("[DEBUG] 订单 %s 配置为翻牌游戏,跳过自动发放 (规则ID: %d)\n", item.ShopOrderID, reward.ID)
hasFlipCard = true
continue
}
if s.rewardDispatcher == nil {
fmt.Printf("[DEBUG] 订单 %s 奖励发放器未初始化,跳过 (规则ID: %d)\n", item.ShopOrderID, reward.ID)
continue
}
fmt.Printf("[DEBUG] 准备发放奖励: User: %d, ProductID: %s, Type: %s, Quantity: %d, RuleID: %d\n",
localUserID, order.DouyinProductID, reward.RewardType, reward.Quantity, reward.ID)
err := s.rewardDispatcher.GrantReward(ctx, localUserID, reward, int(order.ProductCount), "douyin_order", order.ID)
if err != nil {
fmt.Printf("[DEBUG] 订单 %s 发放奖励失败 (规则ID: %d): %v\n", item.ShopOrderID, reward.ID, err)
allSuccess = false
} else {
grantedCount++
fmt.Printf("[DEBUG] 订单 %s 发放奖励成功: %s × %d (规则ID: %d)\n",
item.ShopOrderID, reward.RewardType, int(reward.Quantity)*int(order.ProductCount), reward.ID)
}
}
// 标记奖励已发放
// - 如果只有翻牌游戏,不标记(让翻牌页面可以查询)
// - 如果有其他奖励成功发放,标记为已发放(防止重复发放)
if allSuccess && grantedCount > 0 {
db.Model(&order).Update("reward_granted", order.ProductCount)
order.RewardGranted = order.ProductCount
} else if hasFlipCard && grantedCount == 0 {
// 纯翻牌游戏,不标记
fmt.Printf("[DEBUG] 订单 %s 仅配置翻牌游戏,等待手动翻牌\n", item.ShopOrderID)
}
}
return isNew, isMatched
}
// min 返回两个整数的最小值
func min(a, b int) int {
if a < b {
return a
}
return b
}
// SyncAllOrders 批量同步所有订单变更 (基于更新时间,不分状态)
func (s *service) SyncAllOrders(ctx context.Context, duration time.Duration) (*SyncResult, error) {
cfg, err := s.GetConfig(ctx)
if err != nil {
return nil, fmt.Errorf("获取配置失败: %w", err)
}
if cfg.Cookie == "" {
return nil, fmt.Errorf("抖店 Cookie 未配置")
}
// 临时:强制使用用户提供的最新 Cookie (与 SyncShopOrders 保持一致的调试逻辑)
if len(cfg.Cookie) < 100 {
cfg.Cookie = "passport_csrf_token=afcc4debfeacce6454979bb9465999dc; passport_csrf_token_default=afcc4debfeacce6454979bb9465999dc; is_staff_user=false; zsgw_business_data=%7B%22uuid%22%3A%22fa769974-ba17-4daf-94cb-3162ba299c40%22%2C%22platform%22%3A%22pc%22%2C%22source%22%3A%22seo.fxg.jinritemai.com%22%7D; s_v_web_id=verify_mjqlw6yx_mNQjOEnB_oXBo_4Etb_AVQ9_7tQGH9WORNRy; SHOP_ID=47668214; PIGEON_CID=3501298428676440; x-web-secsdk-uid=663d5a20-e75c-4789-bc98-839744bf70bc; Hm_lvt_b6520b076191ab4b36812da4c90f7a5e=1766891015,1766979339,1767628404,1768381245; HMACCOUNT=95F3EBE1C47ED196; ttcid=7962a054674f4dd7bf895af73ae3f34142; passport_mfa_token=CjfZetGovLzEQb6MwoEpMQnvCSomMC9o0P776kEFy77vhrRCAdFvvrnTSpTXY2aib8hCdU5w3tQvGkoKPAAAAAAAAAAAAABP88E%2FGYNOqYg7lJ6fcoAzlVHbNi0bqTR%2Fru8noACGHR%2BtNjtq%2FnW9rBK32mcHCC5TzRDW8YYOGPax0WwgAiIBA3WMQyg%3D; source=seo.fxg.jinritemai.com; gfkadpd=4272,23756; csrf_session_id=b7b4150c5eeefaede4ef5e71473e9dc1; Hm_lpvt_b6520b076191ab4b36812da4c90f7a5e=1768381314; ttwid=1%7CAwu3-vdDBhOP12XdEzmCJlbyX3Qt_5RcioPVgjBIDps%7C1768381315%7Ca763fd05ed6fa274ed997007385cc0090896c597cfac0b812c962faf34f04897; tt_scid=f4YqIWnO3OdWrfVz0YVnJmYahx-qu9o9j.VZC2op7nwrQRodgrSh1ka0Ow3g5nyKd42a; odin_tt=bcf942ae72bd6b4b8f357955b71cc21199b6aec5e9acee4ce64f80704f08ea1cbaaa6e70f444f6a09712806aa424f4d0cce236e77b0bfa2991aa8a23dab27e1e; passport_auth_status=b3b3a865e0bd3857e6a28ea5a6854830%2C228cf6630632c26472c096506639ed6e; passport_auth_status_ss=b3b3a865e0bd3857e6a28ea5a6854830%2C228cf6630632c26472c096506639ed6e; uid_tt=4dfa662033e2e4eefe629ad8815f076f; uid_tt_ss=4dfa662033e2e4eefe629ad8815f076f; sid_tt=4cc6aa2f1a6e338ec72d663a0b611d3c; sessionid=4cc6aa2f1a6e338ec72d663a0b611d3c; sessionid_ss=4cc6aa2f1a6e338ec72d663a0b611d3c; PHPSESSID=a1b2fd062c1346e5c6f94bac3073cd7d; PHPSESSID_SS=a1b2fd062c1346e5c6f94bac3073cd7d; ucas_c0=CkEKBTEuMC4wEJOIgezc9NazaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0Cpt53LBkip69nNBlC_vL6Ekt3t1GdYbhIU2LuS6yHmC8_SKu9Jok5ToGxfQIg; ucas_c0_ss=CkEKBTEuMC4wEJOIgezc9NazaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0Cpt53LBkip69nNBlC_vL6Ekt3t1GdYbhIU2LuS6yHmC8_SKu9Jok5ToGxfQIg; ecom_gray_shop_id=156231010; sid_guard=4cc6aa2f1a6e338ec72d663a0b611d3c%7C1768381360%7C5184000%7CSun%2C+15-Mar-2026+09%3A02%3A40+GMT; session_tlb_tag=sttt%7C4%7CTMaqLxpuM47HLWY6C2EdPP________-x3_oZvMYjz8-Uw3dAm6JiPFDhS1ih9XTV79AgAO_5cvo%3D; sid_ucp_v1=1.0.0-KGRmNzNkZjM2YjUwZDk2M2M0MjQ5MGE2NzNkNGZkZjNhZWFhYmJkMmIKGQib1oDYuM3aBxCwt53LBhiwISAMOAZA9AcaAmxmIiA0Y2M2YWEyZjFhNmUzMzhlYzcyZDY2M2EwYjYxMWQzYw; ssid_ucp_v1=1.0.0-KGRmNzNkZjM2YjUwZDk2M2M0MjQ5MGE2NzNkNGZkZjNhZWFhYmJkMmIKGQib1oDYuM3aBxCwt53LBhiwISAMOAZA9AcaAmxmIiA0Y2M2YWEyZjFhNmUzMzhlYzcyZDY2M2EwYjYxMWQzYw; COMPASS_LUOPAN_DT=session_7595137429020049706; BUYIN_SASID=SID2_7595138116287152420"
}
startTime := time.Now().Add(-duration)
queryParams := url.Values{
"page": {"0"},
"pageSize": {"50"},
"order_by": {"update_time"},
"order": {"desc"},
"appid": {"1"},
"_bid": {"ffa_order"},
"aid": {"4272"},
"tab": {"all"}, // 全量状态
"update_time_start": {strconv.FormatInt(startTime.Unix(), 10)},
}
orders, err := s.fetchDouyinOrders(cfg.Cookie, queryParams)
if err != nil {
return nil, fmt.Errorf("抓取增量订单失败: %w", err)
}
result := &SyncResult{
TotalFetched: len(orders),
DebugInfo: fmt.Sprintf("UpdateSince: %s, Fetched: %d", startTime.Format("15:04:05"), len(orders)),
}
for _, order := range orders {
isNew, matched := s.SyncOrder(ctx, &order, 0, "") // 不指定 productID主要用于更新状态
if isNew {
result.NewOrders++
}
if matched {
result.MatchedUsers++
}
}
return result, nil
}