diff --git a/cookies/0d86b848uu2183uu4a08/xhs.json b/cookies/0d86b848uu2183uu4a08/xhs.json index 7214b6d..4b50020 100644 --- a/cookies/0d86b848uu2183uu4a08/xhs.json +++ b/cookies/0d86b848uu2183uu4a08/xhs.json @@ -1 +1 @@ -[{"name":"gid","value":"yjfKDJiJ0J7SyjfKDJi8Dqf884ufUMYf3MlIVqfYTYl3D3q8Svu0VW888yyYW4Y8JD0j8Yd2","domain":".xiaohongshu.com","path":"/","expires":1810179744.008933,"size":75,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"a1","value":"19d6b2f0b04zdps8dison8k8oibcyzaju3ji7d03d30000118748","domain":".xiaohongshu.com","path":"/","expires":1807155738,"size":54,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"galaxy.creator.beaker.session.id","value":"1775619757404082990054","domain":".xiaohongshu.com","path":"/","expires":1778211755.091605,"size":54,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"sec_poison_id","value":"ec8ce30d-a35e-484c-8f64-392a85f99705","domain":".xiaohongshu.com","path":"/","expires":1775762101,"size":49,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"x-user-id-creator.xiaohongshu.com","value":"65d74a4c0000000005032a98","domain":".xiaohongshu.com","path":"/","expires":1810179755.091531,"size":57,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"customerClientId","value":"231145420384063","domain":".xiaohongshu.com","path":"/","expires":1810179755.091552,"size":31,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"loadts","value":"1775761630503","domain":".xiaohongshu.com","path":"/","expires":1807297630,"size":19,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"access-token-creator.xiaohongshu.com","value":"customer.creator.AT-68c517626228786611519495vggizcwmifuvnaaw","domain":".xiaohongshu.com","path":"/","expires":1778211754.091569,"size":96,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"customer-sso-sid","value":"68c5176262287866115194944uxxdffhcemiwvwt","domain":".xiaohongshu.com","path":"/","expires":1776224554.091467,"size":56,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"galaxy_creator_session_id","value":"nWC5PzTFCCSJsLYiRFLFORIWUoUf5c4Egr3v","domain":".xiaohongshu.com","path":"/","expires":1778211755.091586,"size":61,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"webId","value":"f840cc8b10e0a01b0bdb839482af19d1","domain":".xiaohongshu.com","path":"/","expires":1807155738,"size":37,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"ets","value":"1775619738206","domain":".xiaohongshu.com","path":"/","expires":1778211738.206388,"size":16,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"xsecappid","value":"ugc","domain":".xiaohongshu.com","path":"/","expires":1807297630,"size":12,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"acw_tc","value":"0a00074d17757617587134822e6e92a5ae9bdad71669770468266647ffdc11","domain":"creator.xiaohongshu.com","path":"/","expires":1775763295.185423,"size":68,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"websectiga","value":"29098a4cf41f76ee3f8db19051aaa60c0fc7c5e305572fec762da32d457d76ae","domain":".xiaohongshu.com","path":"/","expires":1776020696,"size":74,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443}] \ No newline at end of file +[{"name":"websectiga","value":"7750c37de43b7be9de8ed9ff8ea0e576519e8cd2157322eb972ecb429a7735d4","domain":".xiaohongshu.com","path":"/","expires":1776073705,"size":74,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"x-user-id-creator.xiaohongshu.com","value":"65d74a4c0000000005032a98","domain":".xiaohongshu.com","path":"/","expires":1810179755.091531,"size":57,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"xsecappid","value":"ugc","domain":".xiaohongshu.com","path":"/","expires":1807350714,"size":12,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"a1","value":"19d6b2f0b04zdps8dison8k8oibcyzaju3ji7d03d30000118748","domain":".xiaohongshu.com","path":"/","expires":1807155738,"size":54,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"webId","value":"f840cc8b10e0a01b0bdb839482af19d1","domain":".xiaohongshu.com","path":"/","expires":1807155738,"size":37,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"access-token-creator.xiaohongshu.com","value":"customer.creator.AT-68c517626228786611519495vggizcwmifuvnaaw","domain":".xiaohongshu.com","path":"/","expires":1778211754.091569,"size":96,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"sec_poison_id","value":"0e8199c2-5b2c-4b5d-9423-9ce4ae7c7d4a","domain":".xiaohongshu.com","path":"/","expires":1775815110,"size":49,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"customerClientId","value":"231145420384063","domain":".xiaohongshu.com","path":"/","expires":1810179755.091552,"size":31,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"ets","value":"1775619738206","domain":".xiaohongshu.com","path":"/","expires":1778211738.206388,"size":16,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"gid","value":"yjfKDJiJ0J7SyjfKDJi8Dqf884ufUMYf3MlIVqfYTYl3D3q8Svu0VW888yyYW4Y8JD0j8Yd2","domain":".xiaohongshu.com","path":"/","expires":1810372429.581067,"size":75,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"customer-sso-sid","value":"68c5176262287866115194944uxxdffhcemiwvwt","domain":".xiaohongshu.com","path":"/","expires":1776224554.091467,"size":56,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"galaxy_creator_session_id","value":"nWC5PzTFCCSJsLYiRFLFORIWUoUf5c4Egr3v","domain":".xiaohongshu.com","path":"/","expires":1778211755.091586,"size":61,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"acw_tc","value":"0a0d0eb817758145058628343eac7363daa6c0cadc9daf2e0f80b0fb928719","domain":"creator.xiaohongshu.com","path":"/","expires":1775816304.657778,"size":68,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"galaxy.creator.beaker.session.id","value":"1775619757404082990054","domain":".xiaohongshu.com","path":"/","expires":1778211755.091605,"size":54,"httpOnly":true,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443},{"name":"loadts","value":"1775814714949","domain":".xiaohongshu.com","path":"/","expires":1807350714,"size":19,"httpOnly":false,"secure":false,"session":false,"priority":"Medium","sameParty":false,"sourceScheme":"Secure","sourcePort":443}] \ No newline at end of file diff --git a/internal/manager/publish_manager.go b/internal/manager/publish_manager.go index f0d598b..3867570 100644 --- a/internal/manager/publish_manager.go +++ b/internal/manager/publish_manager.go @@ -1,6 +1,7 @@ package manager import ( + "context" "fmt" "geo/internal/config" "geo/internal/entitys" @@ -14,6 +15,8 @@ import ( "strings" "sync" "time" + + "github.com/google/uuid" ) const ( @@ -24,8 +27,8 @@ const ( StatusSuccess = 4 // 默认并发worker数量 - DefaultWorkerNum = 2 - MaxWorkerNum = 5 + DefaultWorkerNum = 4 + MaxWorkerNum = 100 ) // PublishManager 发布管理器 @@ -186,40 +189,62 @@ func (pm *PublishManager) executeOneTask(workerID int, headless bool) { return } if task == nil { - time.Sleep(10 * time.Second) + time.Sleep(30 * time.Second) return } log.Printf("[Worker-%d] 开始处理任务 requestID=%s", workerID, task.RequestID) - result := pm.processTask(task, headless) - if result == nil { - log.Printf("[Worker-%d] 任务失败: %s", workerID, result.Message) - } else { - if result.Success { - log.Printf("[Worker-%d] 任务成功: %s", workerID, result.Message) - } else { - log.Printf("[Worker-%d] 任务失败: %s", workerID, result.Message) - } - } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + // 使用 channel 接收结果,避免 goroutine 泄漏 + result := pm.processTask(ctx, task, headless) + + if result == nil { + log.Printf("[Worker-%d] 任务返回空结果", workerID) + } else if result.Success { + log.Printf("[Worker-%d] 任务成功: %s", workerID, result.Message) + } else { + log.Printf("[Worker-%d] 任务失败: %s", workerID, result.Message) + } } // acquireTask 原子获取一个待发布任务(使用 GORM 事务 + FOR UPDATE SKIP LOCKED) func (pm *PublishManager) acquireTask() (*entitys.PublishTaskDetail, error) { currentTime := time.Now().Format("2006-01-02 15:04:05") + uid := uuid.NewString()[:16] + // 使用子查询先找出要更新的 request_id + updateSQL := ` + UPDATE publish p + SET p.status = ?,uid=? + WHERE p.request_id = ( + SELECT request_id FROM ( + SELECT p2.request_id + FROM publish p2 + INNER JOIN plat pl ON p2.plat_index COLLATE utf8mb4_unicode_ci = pl.index + WHERE p2.token_id = ? + AND p2.status = ? + AND p2.publish_time <= ? + AND pl.status = 1 + ORDER BY p2.publish_time ASC + LIMIT 1 + ) AS tmp + ) + AND p.status = ? + ` - // 开启事务 - tx := pm.db.Client.Begin() - if tx.Error != nil { - return nil, fmt.Errorf("开启事务失败: %v", tx.Error) + result := pm.db.Client.Exec(updateSQL, StatusProcessing, uid, pm.TokenID, StatusPending, currentTime, StatusPending) + if result.Error != nil { + return nil, result.Error } - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - selectSQL := ` + if result.RowsAffected == 0 { + return nil, nil + } + + // 查询刚更新的任务 + var task entitys.PublishTaskDetail + querySQL := ` SELECT p.request_id, p.plat_index, @@ -238,46 +263,32 @@ func (pm *PublishManager) acquireTask() (*entitys.PublishTaskDetail, error) { pl.desc FROM publish p INNER JOIN plat pl ON p.plat_index COLLATE utf8mb4_unicode_ci = pl.index AND pl.status = 1 - WHERE p.token_id = ? AND p.status = ? AND p.publish_time <= ? - ORDER BY p.publish_time ASC + WHERE uid=? AND p.token_id = ? + ORDER BY p.publish_time DESC LIMIT 1 - FOR UPDATE SKIP LOCKED ` - var task entitys.PublishTaskDetail - err := tx.Raw(selectSQL, pm.TokenID, StatusPending, currentTime).Scan(&task).Error + + err := pm.db.Client.Raw(querySQL, uid, pm.TokenID).Scan(&task).Error if err != nil { - tx.Rollback() - return nil, fmt.Errorf("查询任务失败: %v", err) - } - if task.RequestID == "" { - tx.Rollback() - return nil, nil - } - - updateSQL := "UPDATE publish SET status = ? WHERE request_id = ? AND status = ?" - result := tx.Exec(updateSQL, StatusProcessing, task.RequestID, StatusPending) - if result.Error != nil { - tx.Rollback() - return nil, fmt.Errorf("更新任务状态失败: %v", result.Error) - } - if result.RowsAffected == 0 { - tx.Rollback() - return nil, nil - } - - if err := tx.Commit().Error; err != nil { - return nil, fmt.Errorf("提交事务失败: %v", err) + return nil, err } return &task, nil } // processTask 处理单个任务 -func (pm *PublishManager) processTask(publishData *entitys.PublishTaskDetail, headless bool) *SingleResult { +func (pm *PublishManager) processTask(ctx context.Context, publishData *entitys.PublishTaskDetail, headless bool) *SingleResult { if publishData == nil || publishData.RequestID == "" { return &SingleResult{Success: false, Message: "无效的任务数据"} } + // 检查 context 是否已取消 + select { + case <-ctx.Done(): + return &SingleResult{Success: false, Message: "任务被取消: " + ctx.Err().Error(), RequestId: publishData.RequestID} + default: + } + taskLogger, logFile, err := pm.getTaskLogger(publishData.RequestID) if err != nil { log.Printf("[任务 %s] 创建日志文件失败: %v,使用全局日志", publishData.RequestID, err) @@ -331,7 +342,7 @@ func (pm *PublishManager) processTask(publishData *entitys.PublishTaskDetail, he params.ImagePath = imgPath params.SourcePath = docPath - pub := publisherClass.InitMethod(params, pm.Conf, taskLogger) + pub := publisherClass.InitMethod(ctx, params, pm.Conf, taskLogger) taskLogger.Printf("[任务 %s] 开始执行发布...", publishData.RequestID) success, message := pub.PublishNote() @@ -349,7 +360,7 @@ func (pm *PublishManager) processTask(publishData *entitys.PublishTaskDetail, he } // RetryTask 重试任务(非无头模式) -func (pm *PublishManager) RetryTask(requestID string) *SingleResult { +func (pm *PublishManager) RetryTask(ctx context.Context, requestID string) *SingleResult { if requestID == "" { return &SingleResult{Success: false, Message: "requestID不能为空"} } @@ -360,7 +371,7 @@ func (pm *PublishManager) RetryTask(requestID string) *SingleResult { } log.Printf("[重试] 开始重试任务 requestID=%s(非无头模式)", requestID) - result := pm.processTask(publishData, false) + result := pm.processTask(ctx, publishData, false) if result == nil { result = &SingleResult{Success: false, Message: "系统故障", RequestId: requestID} } @@ -498,9 +509,9 @@ func (pm *PublishManager) updatePublishStatus(requestID string, status int, mess } var err error if message != "" { - _, err = pm.db.Execute("UPDATE publish SET status = ?, msg = ? WHERE request_id = ?", status, message, requestID) + _, err = pm.db.Execute("UPDATE publish SET status = ?, msg = ? WHERE token_id=? AND request_id = ?", status, message, pm.TokenID, requestID) } else { - _, err = pm.db.Execute("UPDATE publish SET status = ? WHERE request_id = ?", status, requestID) + _, err = pm.db.Execute("UPDATE publish SET status = ? WHERE token_id=? AND request_id = ?", status, pm.TokenID, requestID) } if err != nil { log.Printf("更新发布状态失败: requestID=%s, status=%d, error=%v", requestID, status, err) diff --git a/internal/publisher/baijiahao.go b/internal/publisher/baijiahao.go index af9f24b..40d0f5f 100644 --- a/internal/publisher/baijiahao.go +++ b/internal/publisher/baijiahao.go @@ -1,6 +1,7 @@ package publisher import ( + "context" "fmt" "geo/internal/config" "log" @@ -15,8 +16,8 @@ type BaijiahaoPublisher struct { *BasePublisher } -func NewBaijiahaoPublisher(task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { - return &BaijiahaoPublisher{NewBasePublisher(task, cfg, logger)} +func NewBaijiahaoPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { + return &BaijiahaoPublisher{NewBasePublisher(ctx, task, cfg, logger)} } func (p *BaijiahaoPublisher) CheckLoginStatus() bool { @@ -92,18 +93,12 @@ func (p *BaijiahaoPublisher) checkElementExists(selector string, timeout int) bo } func (p *BaijiahaoPublisher) PublishNote() (bool, string) { - driverCreated := false - defer func() { - if driverCreated && p.Browser != nil { - p.Close() - } - }() if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } - driverCreated = true + defer p.Page.Close() if p.LoadCookies() == nil { p.Page.MustNavigate(p.EditorURL) p.WaitForPageReady(5) diff --git a/internal/publisher/base.go b/internal/publisher/base.go index 4bfff01..c75d752 100644 --- a/internal/publisher/base.go +++ b/internal/publisher/base.go @@ -1,6 +1,7 @@ package publisher import ( + "context" "encoding/json" "fmt" "geo/internal/entitys" @@ -11,12 +12,14 @@ import ( "time" "geo/internal/config" + "github.com/go-rod/rod" "github.com/go-rod/rod/lib/launcher" "github.com/go-rod/rod/lib/proto" ) type BasePublisher struct { + ctx context.Context Headless bool Title string Content string @@ -61,7 +64,7 @@ type TaskParams struct { } // NewBasePublisher 构造函数,增加 logger 参数 -func NewBasePublisher(task *TaskParams, config *config.Config, logger *log.Logger) *BasePublisher { +func NewBasePublisher(ctx context.Context, task *TaskParams, config *config.Config, logger *log.Logger) *BasePublisher { cookiesDir := filepath.Join(config.Sys.CookiesDir, task.UserIndex) os.MkdirAll(cookiesDir, 0755) cookiesFile := filepath.Join(cookiesDir, task.PlatIndex+".json") @@ -85,6 +88,7 @@ func NewBasePublisher(task *TaskParams, config *config.Config, logger *log.Logge } return &BasePublisher{ + ctx: ctx, Headless: task.Headless, Title: task.Title, Content: task.Content, @@ -109,9 +113,10 @@ func NewBasePublisher(task *TaskParams, config *config.Config, logger *log.Logge func (b *BasePublisher) SetupDriver() error { b.LogInfo("初始化浏览器。。。。") - b.Headless = false + b.Headless = true l := launcher.New().Bin(b.config.Sys.ChromePath) l.Headless(b.Headless) + // 设置 Leakless 模式(解决 Windows 上的问题) l.Leakless(false) if b.Headless { @@ -122,7 +127,7 @@ func (b *BasePublisher) SetupDriver() error { l.Set("disable-dev-shm-usage") l.Set("no-sandbox") // 禁用虚拟滚动,避免等待 - l.Set("disable-scroll-to-text-fragment") + //l.Set("disable-scroll-to-text-fragment") } // 设置用户数据目录 @@ -130,7 +135,12 @@ func (b *BasePublisher) SetupDriver() error { os.MkdirAll(userDataDir, 0755) l.UserDataDir(userDataDir) - + // 为每个用户分配一个可用的随机端口,确保端口不冲突 + //freePort, err := pkg.GetFreePort() + //if err != nil { + // return fmt.Errorf("获取空闲端口失败: %v", err) + //} + l.Set("remote-debugging-port", "0") // 关键优化:不重新使用已有的数据目录时不要清除 l.Set("profile-directory", "Default") @@ -141,7 +151,6 @@ func (b *BasePublisher) SetupDriver() error { l.Set("disable-gpu") l.Set("disable-software-rasterizer") l.Set("disable-setuid-sandbox") - l.Set("remote-debugging-port", "9222") // 关键:禁用后台限制,让页面在后台也能正常执行 l.Set("disable-background-timer-throttling") @@ -159,12 +168,21 @@ func (b *BasePublisher) SetupDriver() error { return fmt.Errorf("启动浏览器失败: %v", err) } - b.Browser = rod.New().ControlURL(url).MustConnect() + b.Browser = rod.New().Context(b.ctx).ControlURL(url).MustConnect() b.Page = b.Browser.MustPage() - + time.Sleep(5000) return nil } +// func (b *BasePublisher) SetupDriver() error { +// b.LogInfo("初始化浏览器。。。。") +// b.Headless = true +// +// b.Browser = rod.New().Context(b.ctx). +// b.Page = b.Browser.MustPage() +// time.Sleep(5000) +// return nil +// } func (b *BasePublisher) Close() { if b.Page != nil { b.Page.Close() @@ -211,11 +229,11 @@ func (b *BasePublisher) RefreshPage() error { } func (b *BasePublisher) WaitForPageReady(timeout int) error { - return b.Page.Timeout(time.Duration(timeout) * time.Second).WaitLoad() + return b.Page.Context(b.ctx).WaitLoad() } func (b *BasePublisher) WaitForElement(selector string, timeout int) (*rod.Element, error) { - return b.Page.Timeout(time.Duration(timeout) * time.Second).Element(selector) + return b.Page.Context(b.ctx).Element(selector) } func (b *BasePublisher) WaitForElementVisible(selector string, timeout int) (*rod.Element, error) { @@ -246,7 +264,6 @@ func (b *BasePublisher) JSClick(element *rod.Element) error { return fmt.Errorf("element is nil") } err := element.Click(proto.InputMouseButtonLeft, 1) - // 方法1:使用 rod 自带的 Click if err != nil { b.Logger.Printf("click fail:" + err.Error()) } diff --git a/internal/publisher/csdn.go b/internal/publisher/csdn.go index d537973..a741e11 100644 --- a/internal/publisher/csdn.go +++ b/internal/publisher/csdn.go @@ -1,6 +1,7 @@ package publisher import ( + "context" "fmt" "geo/internal/config" "log" @@ -14,8 +15,8 @@ type CSDNPublisher struct { *BasePublisher } -func NewCSDNPublisher(task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { - return &CSDNPublisher{NewBasePublisher(task, cfg, logger)} +func NewCSDNPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { + return &CSDNPublisher{NewBasePublisher(ctx, task, cfg, logger)} } func (p *CSDNPublisher) CheckLogin() (bool, string) { @@ -185,7 +186,7 @@ func (p *CSDNPublisher) PublishNote() (bool, string) { if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } - defer p.Close() + defer p.Page.Close() // 执行发布流程 steps := []struct { diff --git a/internal/publisher/dysp.go b/internal/publisher/dysp.go index b816db1..71f5ce1 100644 --- a/internal/publisher/dysp.go +++ b/internal/publisher/dysp.go @@ -1,6 +1,7 @@ package publisher import ( + "context" "fmt" "geo/internal/config" "log" @@ -16,8 +17,8 @@ type DouyinSpPublisher struct { *BasePublisher } -func NewDouyinSpPublisher(task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { - return &DouyinSpPublisher{NewBasePublisher(task, cfg, logger)} +func NewDouyinSpPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { + return &DouyinSpPublisher{NewBasePublisher(ctx, task, cfg, logger)} } func (p *DouyinSpPublisher) CheckLogin() (bool, string) { @@ -392,7 +393,7 @@ func (p *DouyinSpPublisher) PublishNote() (bool, string) { if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } - defer p.Close() + defer p.Page.Close() steps := []struct { name string diff --git a/internal/publisher/interface.go b/internal/publisher/interface.go index de049e0..cfb3932 100644 --- a/internal/publisher/interface.go +++ b/internal/publisher/interface.go @@ -1,6 +1,7 @@ package publisher import ( + "context" "geo/internal/config" "log" ) @@ -11,6 +12,7 @@ type PublisherInerface interface { } type NewPublisher func( + ctx context.Context, param *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface @@ -110,7 +112,7 @@ var PublisherMap = map[string]*PublisherValue{ InitMethod: NewCSDNPublisher, ContentFormat: "markdown", ImgNeed: 1, - Type: 2, + Type: 1, WordContainImg: false, }, } diff --git a/internal/publisher/js.go b/internal/publisher/js.go index f8cf8f8..4b2366f 100644 --- a/internal/publisher/js.go +++ b/internal/publisher/js.go @@ -1,6 +1,7 @@ package publisher import ( + "context" "fmt" "geo/internal/config" "log" @@ -15,8 +16,8 @@ type JianshuPublisher struct { *BasePublisher } -func NewJianshuPublisher(task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { - return &JianshuPublisher{NewBasePublisher(task, cfg, logger)} +func NewJianshuPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { + return &JianshuPublisher{NewBasePublisher(ctx, task, cfg, logger)} } func (p *JianshuPublisher) CheckLogin() (bool, string) { @@ -304,7 +305,7 @@ func (p *JianshuPublisher) PublishNote() (bool, string) { if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } - defer p.Close() + defer p.Page.Close() steps := []struct { name string diff --git a/internal/publisher/shh.go b/internal/publisher/shh.go index d1a6457..e4cd741 100644 --- a/internal/publisher/shh.go +++ b/internal/publisher/shh.go @@ -1,6 +1,7 @@ package publisher import ( + "context" "fmt" "geo/internal/config" "log" @@ -15,8 +16,8 @@ type SohuPublisher struct { *BasePublisher } -func NewSohuPublisher(task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { - return &SohuPublisher{NewBasePublisher(task, cfg, logger)} +func NewSohuPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { + return &SohuPublisher{NewBasePublisher(ctx, task, cfg, logger)} } func (p *SohuPublisher) CheckLogin() (bool, string) { @@ -487,7 +488,7 @@ func (p *SohuPublisher) PublishNote() (bool, string) { if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } - defer p.Close() + defer p.Page.Close() steps := []struct { name string diff --git a/internal/publisher/sphsp.go b/internal/publisher/sphsp.go index 8135b54..4ecb7a0 100644 --- a/internal/publisher/sphsp.go +++ b/internal/publisher/sphsp.go @@ -1,6 +1,7 @@ package publisher import ( + "context" "encoding/base64" "fmt" "geo/internal/config" @@ -17,9 +18,9 @@ type ShipinhaoVideoPublisher struct { } // NewShipinhaoVideoPublisher 创建视频号发布器 -func NewShipinhaoVideoPublisher(task *TaskParams, config *config.Config, logger *log.Logger) PublisherInerface { +func NewShipinhaoVideoPublisher(ctx context.Context, task *TaskParams, config *config.Config, logger *log.Logger) PublisherInerface { return &ShipinhaoVideoPublisher{ - BasePublisher: NewBasePublisher(task, config, logger), + BasePublisher: NewBasePublisher(ctx, task, config, logger), } } @@ -31,7 +32,7 @@ func (p *ShipinhaoVideoPublisher) PublishNote() (bool, string) { if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } - + defer p.Page.Close() // 3. 加载 cookies 并检查登录状态 if err := p.LoadCookies(); err == nil { p.Page.Navigate(p.EditorURL) diff --git a/internal/publisher/toutiao.go b/internal/publisher/toutiao.go index e2320a5..7d10a01 100644 --- a/internal/publisher/toutiao.go +++ b/internal/publisher/toutiao.go @@ -1,6 +1,7 @@ package publisher import ( + "context" "fmt" "geo/internal/config" "log" @@ -16,8 +17,8 @@ type ToutiaoPublisher struct { } // NewToutiaoPublisher 构造函数 -func NewToutiaoPublisher(task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { - return &ToutiaoPublisher{NewBasePublisher(task, cfg, logger)} +func NewToutiaoPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { + return &ToutiaoPublisher{NewBasePublisher(ctx, task, cfg, logger)} } func (p *ToutiaoPublisher) CheckLogin() (bool, string) { @@ -472,7 +473,7 @@ func (p *ToutiaoPublisher) PublishNote() (bool, string) { if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } - defer p.Close() + defer p.Page.Close() // 执行发布流程 steps := []struct { diff --git a/internal/publisher/wyh.go b/internal/publisher/wyh.go index f8702d2..39934e1 100644 --- a/internal/publisher/wyh.go +++ b/internal/publisher/wyh.go @@ -1,6 +1,7 @@ package publisher import ( + "context" "fmt" "geo/internal/config" "log" @@ -19,9 +20,9 @@ type WangyiPublisher struct { } // NewWangyiPublisher 构造函数 -func NewWangyiPublisher(task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { +func NewWangyiPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { return &WangyiPublisher{ - BasePublisher: NewBasePublisher(task, cfg, logger), + BasePublisher: NewBasePublisher(ctx, task, cfg, logger), Category: "", IsOriginal: true, } @@ -33,7 +34,7 @@ func (p *WangyiPublisher) CheckLogin() (bool, string) { if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } - defer p.Close() + defer p.Page.Close() p.Page.MustNavigate(p.EditorURL) p.Sleep(3) diff --git a/internal/publisher/xhs.go b/internal/publisher/xhs.go index 36ee130..a8fa728 100644 --- a/internal/publisher/xhs.go +++ b/internal/publisher/xhs.go @@ -1,6 +1,7 @@ package publisher import ( + "context" "fmt" "geo/internal/config" "log" @@ -16,8 +17,8 @@ type XiaohongshuPublisher struct { } // NewXiaohongshuPublisher 构造函数,增加 logger 参数 -func NewXiaohongshuPublisher(task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { - return &XiaohongshuPublisher{NewBasePublisher(task, cfg, logger)} +func NewXiaohongshuPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { + return &XiaohongshuPublisher{NewBasePublisher(ctx, task, cfg, logger)} } func (p *XiaohongshuPublisher) CheckLogin() (bool, string) { @@ -394,7 +395,6 @@ func (p *XiaohongshuPublisher) ClickUploadBotton() error { } else if len(buttons) > 0 { if err := p.JSClick(buttons[0]); err != nil { return fmt.Errorf("JS点击按钮失败: %v", err) - } else { p.LogInfo("已点击上传按钮") p.Sleep(1) @@ -427,7 +427,7 @@ func (p *XiaohongshuPublisher) PublishNote() (bool, string) { if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } - defer p.Close() + defer p.Page.Close() // 执行发布流程 steps := []struct { diff --git a/internal/publisher/xhssp.go b/internal/publisher/xhssp.go index 93c59d8..46dafe3 100644 --- a/internal/publisher/xhssp.go +++ b/internal/publisher/xhssp.go @@ -1,6 +1,7 @@ package publisher import ( + "context" "fmt" "geo/internal/config" "log" @@ -19,9 +20,9 @@ type XiaohongshuVideoPublisher struct { longWait int } -func NewXiaohongshuVideoPublisher(task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { +func NewXiaohongshuVideoPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { return &XiaohongshuVideoPublisher{ - BasePublisher: NewBasePublisher(task, cfg, logger), + BasePublisher: NewBasePublisher(ctx, task, cfg, logger), shortWait: 1, mediumWait: 3, longWait: 5, @@ -34,7 +35,7 @@ func (p *XiaohongshuVideoPublisher) CheckLogin() (bool, string) { if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } - defer p.Close() + defer p.Page.Close() p.Page.MustNavigate(p.LoginedURL) p.Sleep(3) diff --git a/internal/publisher/zh.go b/internal/publisher/zh.go index f969195..1063919 100644 --- a/internal/publisher/zh.go +++ b/internal/publisher/zh.go @@ -1,6 +1,7 @@ package publisher import ( + "context" "fmt" "geo/internal/config" "log" @@ -15,8 +16,8 @@ type ZhihuPublisher struct { *BasePublisher } -func NewZhihuPublisher(task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { - return &ZhihuPublisher{NewBasePublisher(task, cfg, logger)} +func NewZhihuPublisher(ctx context.Context, task *TaskParams, cfg *config.Config, logger *log.Logger) PublisherInerface { + return &ZhihuPublisher{NewBasePublisher(ctx, task, cfg, logger)} } func (p *ZhihuPublisher) CheckLogin() (bool, string) { @@ -25,7 +26,7 @@ func (p *ZhihuPublisher) CheckLogin() (bool, string) { if err := p.SetupDriver(); err != nil { return false, fmt.Sprintf("浏览器启动失败: %v", err) } - defer p.Close() + defer p.Page.Close() p.Page.MustNavigate(p.EditorURL) p.Sleep(3) diff --git a/internal/service/a_test.go b/internal/service/a_test.go new file mode 100644 index 0000000..ed13a55 --- /dev/null +++ b/internal/service/a_test.go @@ -0,0 +1,143 @@ +package service + +import ( + "fmt" + "sync" + "testing" + "time" + + "github.com/go-rod/rod" + "github.com/go-rod/rod/lib/launcher" +) + +func TestMultipleChromeProcesses(t *testing.T) { + browsers := make([]*rod.Browser, 5) + + // 启动 5 个独立的 Chrome 进程 + for i := 0; i < 5; i++ { + browsers[i] = rod.New().MustConnect() + defer browsers[i].MustClose() + + page := browsers[i].MustPage("https://example.com") + title := page.MustInfo().Title + fmt.Printf("进程 %d: %s\n", i, title) + } + + // 打开任务管理器,可以看到 5 个 chrome.exe 进程 + time.Sleep(10 * time.Minute) +} + +// TestCustomChromeWithMixedHeadless 使用自定义Chrome浏览器,启动5个进程(偶数有头,奇数无头) +func TestCustomChromeWithMixedHeadless(t *testing.T) { + // 配置你的Chrome路径 + chromePath := "D:\\gop\\geoGo\\chrome\\chrome.exe" + + processCount := 5 + browsers := make([]*rod.Browser, processCount) + launchers := make([]*launcher.Launcher, processCount) + var wg sync.WaitGroup + + t.Logf("开始启动 %d 个 Chrome 进程(偶数索引有头,奇数索引无头)...", processCount) + + for i := 0; i < processCount; i++ { + wg.Add(1) + go func(idx int) { + defer wg.Done() + + // 偶数(0,2,4):有头模式,奇数(1,3):无头模式 + headless := (idx%2 != 0) // 奇数无头,偶数有头 + + // 每个进程使用独立的用户数据目录 + userDataDir := fmt.Sprintf("./chrome_data/user_%d_%d", idx, time.Now().UnixNano()) + + t.Logf("[进程 %d] 启动中,headless=%v, userDataDir=%s", idx, headless, userDataDir) + + // 创建 launcher + l := launcher.New(). + Bin(chromePath). + UserDataDir(userDataDir). + Headless(headless). + Leakless(false) + + // 有头模式需要额外配置 + if !headless { + l.Set("window-size", "1920,1080") + l.Set("start-maximized") + // 移除 headless 相关参数 + l.Delete("headless") + } else { + // 无头模式优化 + l.Set("disable-gpu") + l.Set("no-sandbox") + l.Set("disable-dev-shm-usage") + } + + // 启动浏览器进程 + url, err := l.Launch() + if err != nil { + t.Logf("[进程 %d] ❌ 启动失败: %v", idx, err) + return + } + + // 连接到浏览器 + browser := rod.New().ControlURL(url).MustConnect() + + browsers[idx] = browser + launchers[idx] = l + + // 创建测试页面验证 + page := browser.MustPage("about:blank") + + // 获取浏览器版本 + version, err := browser.Version() + if err != nil { + t.Logf("[进程 %d] 获取版本失败: %v", idx, err) + } else { + mode := "有头" + if headless { + mode = "无头" + } + t.Logf("[进程 %d] ✅ 启动成功 | 模式: %s | 协议版本: %s | PID: %d", + idx, mode, version.ProtocolVersion, 0) + } + + // 有头模式:打开百度可见 + if !headless { + page.MustNavigate("https://www.baidu.com") + t.Logf("[进程 %d] 有头模式已打开百度页面,可见浏览器窗口", idx) + time.Sleep(2 * time.Second) + } else { + page.MustNavigate("https://www.example.com") + t.Logf("[进程 %d] 无头模式已访问 example.com(不可见)", idx) + } + + page.MustClose() + }(i) + } + + // 等待所有进程启动 + wg.Wait() + + // 验证启动结果 + successCount := 0 + for i, browser := range browsers { + if browser != nil { + successCount++ + t.Logf("[进程 %d] 运行中", i) + } else { + t.Logf("[进程 %d] 启动失败", i) + } + } + + t.Logf("成功启动 %d/%d 个 Chrome 进程", successCount, processCount) + t.Log("浏览器窗口已打开(有头模式可见),按 Enter 键关闭所有进程...") + fmt.Scanln() + + // 清理 + //for i, browser := range browsers { + // if browser != nil { + // browser.MustClose() + // t.Logf("[进程 %d] 已关闭", i) + // } + //} +} diff --git a/internal/service/login.go b/internal/service/login.go index 786234a..11e01ce 100644 --- a/internal/service/login.go +++ b/internal/service/login.go @@ -58,7 +58,7 @@ func (s *LoginService) LoginPlatform(c *fiber.Ctx, req *entitys.LoginPlatformReq LoginUrl: platInfo.LoginURL, }, } - pub := publisherClass.InitMethod(task, s.cfg, nil) + pub := publisherClass.InitMethod(c.UserContext(), task, s.cfg, nil) success, msg := pub.WaitLogin() if !success { return errcode.SysErr(msg) diff --git a/internal/service/publish.go b/internal/service/publish.go index 537fe72..6ecd292 100644 --- a/internal/service/publish.go +++ b/internal/service/publish.go @@ -156,7 +156,7 @@ func (s *PublishService) PublishExecuteRetry(c *fiber.Ctx, req *entitys.PublishE if err != nil { return err } - result := pm.RetryTask(req.RequestID) + result := pm.RetryTask(c.UserContext(), req.RequestID) return pkg.HandleResponse(c, result) } diff --git a/logs/tasks/2026-04-10/run.log b/logs/tasks/2026-04-10/run.log new file mode 100644 index 0000000..442dad5 --- /dev/null +++ b/logs/tasks/2026-04-10/run.log @@ -0,0 +1,485 @@ +GOROOT=D:\go #gosetup +GOPATH=D:\go #gosetup +D:\go\bin\go.exe build -o C:\Users\Administrator\AppData\Local\JetBrains\GoLand2025.2\tmp\GoLand\___go_build_geo_cmd_server.exe -gcflags "all=-N -l" geo/cmd/server #gosetup +warning: both GOPATH and GOROOT are the same directory (D:\go); see https://go.dev/wiki/InstallTroubleshooting +"D:\GoLand 2025.2.5\plugins\go-plugin\lib\dlv\windows\dlv.exe" --listen=127.0.0.1:62082 --headless=true --api-version=2 --check-go-version=false --only-same-user=false exec C:\Users\Administrator\AppData\Local\JetBrains\GoLand2025.2\tmp\GoLand\___go_build_geo_cmd_server.exe -- #gosetup +API server listening at: 127.0.0.1:62082 +WARNING: undefined behavior - version of Delve is too old for Go version go1.26.1 (maximum supported version 1.25) + + ┌───────────────────────────────────────────────────┐ + │ Fiber v2.52.12 │ + │ http://127.0.0.1:5001 │ + │ (bound on host 0.0.0.0 and port 5001) │ + │ │ + │ Handlers ............ 17 Processes ........... 1 │ + │ Prefork ....... Disabled PID ............. 22108 │ + └───────────────────────────────────────────────────┘ + + +2026/04/10 16:52:53 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[423.550ms] [rows:1] SELECT * FROM `token` WHERE access_token='6cb55ce2-2556-4908-91b5-5b78f0168cce' AND status=1 LIMIT 1 +token +16:52:53 | 200 | 424.6891ms | 127.0.0.1 | POST | /publish_on | - +2026/04/10 16:52:53 自动发布服务已启动,tokenID=1,worker数量=3 +2026/04/10 16:52:53 [Worker-1] 启动,tokenID=1 +2026/04/10 16:52:53 [Worker-2] 启动,tokenID=1 +2026/04/10 16:52:53 [Worker-0] 启动,tokenID=1 + +2026/04/10 16:52:54 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[412.746ms] [rows:1] + UPDATE publish p + SET p.status = 2,uid='cd76b1df-df01-4a' + WHERE p.request_id = ( + SELECT request_id FROM ( + SELECT p2.request_id + FROM publish p2 + INNER JOIN plat pl ON p2.plat_index COLLATE utf8mb4_unicode_ci = pl.index + WHERE p2.token_id = 1 + AND p2.status = 1 + AND p2.publish_time <= '2026-04-10 16:52:53' + AND pl.status = 1 + ORDER BY p2.publish_time ASC + LIMIT 1 + ) AS tmp + ) + AND p.status = 1 + +publish + +2026/04/10 16:52:54 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[418.524ms] [rows:1] + UPDATE publish p + SET p.status = 2,uid='f2a6982a-8bef-4e' + WHERE p.request_id = ( + SELECT request_id FROM ( + SELECT p2.request_id + FROM publish p2 + INNER JOIN plat pl ON p2.plat_index COLLATE utf8mb4_unicode_ci = pl.index + WHERE p2.token_id = 1 + AND p2.status = 1 + AND p2.publish_time <= '2026-04-10 16:52:53' + AND pl.status = 1 + ORDER BY p2.publish_time ASC + LIMIT 1 + ) AS tmp + ) + AND p.status = 1 + +publish + +2026/04/10 16:52:54 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[425.694ms] [rows:1] + UPDATE publish p + SET p.status = 2,uid='eba6d8a6-88c3-49' + WHERE p.request_id = ( + SELECT request_id FROM ( + SELECT p2.request_id + FROM publish p2 + INNER JOIN plat pl ON p2.plat_index COLLATE utf8mb4_unicode_ci = pl.index + WHERE p2.token_id = 1 + AND p2.status = 1 + AND p2.publish_time <= '2026-04-10 16:52:53' + AND pl.status = 1 + ORDER BY p2.publish_time ASC + LIMIT 1 + ) AS tmp + ) + AND p.status = 1 + +publish + +2026/04/10 16:52:54 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[391.244ms] [rows:1] + SELECT + p.request_id, + p.plat_index, + p.title, + p.tag, + p.user_index, + p.url, + p.img, + p.publish_time, + p.status, + pl.index as plat_index_value, + pl.status as plat_status, + pl.login_url, + pl.edit_url, + pl.logined_url, + pl.desc + FROM publish p + INNER JOIN plat pl ON p.plat_index COLLATE utf8mb4_unicode_ci = pl.index AND pl.status = 1 + WHERE uid='cd76b1df-df01-4a' AND p.token_id = 1 + ORDER BY p.publish_time DESC + LIMIT 1 + +publish +2026/04/10 16:52:54.478724 ================================================================================ +2026/04/10 16:52:54.478724 任务开始 | RequestID: 6 | 时间: 2026-04-10 16:52:54.478 +2026/04/10 16:52:54.478724 ================================================================================ +2026/04/10 16:52:54.478724 [任务 6] 开始处理(headless=true) +2026/04/10 16:52:54.478724 [任务 6] 任务详情 - 平台:zh,标题:地推看过来!!!,用户:0d86b848uu2183uu4a08 +2026/04/10 16:52:54.478724 [任务 6] 标签解析完成: [#地推 #四川地推 #创业] +2026/04/10 16:52:54.478724 [任务 6] 开始下载文档... +2026/04/10 16:52:54 [Worker-1] 开始处理任务 requestID=6 + +2026/04/10 16:52:54 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[406.859ms] [rows:1] + SELECT + p.request_id, + p.plat_index, + p.title, + p.tag, + p.user_index, + p.url, + p.img, + p.publish_time, + p.status, + pl.index as plat_index_value, + pl.status as plat_status, + pl.login_url, + pl.edit_url, + pl.logined_url, + pl.desc + FROM publish p + INNER JOIN plat pl ON p.plat_index COLLATE utf8mb4_unicode_ci = pl.index AND pl.status = 1 + WHERE uid='f2a6982a-8bef-4e' AND p.token_id = 1 + ORDER BY p.publish_time DESC + LIMIT 1 + +publish +2026/04/10 16:52:54.499312 ================================================================================ +2026/04/10 16:52:54.499312 任务开始 | RequestID: 4 | 时间: 2026-04-10 16:52:54.499 +2026/04/10 16:52:54.499312 ================================================================================ +2026/04/10 16:52:54.499840 [任务 4] 开始处理(headless=true) +2026/04/10 16:52:54.499840 [任务 4] 任务详情 - 平台:xhssp,标题:萌妹,用户:0d86b848uu2183uu4a08 +2026/04/10 16:52:54.499840 [任务 4] 标签解析完成: [萌妹 宅舞 超性感] +2026/04/10 16:52:54.499840 [任务 4] 开始下载文档... + +2026/04/10 16:52:54 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[401.220ms] [rows:1] + SELECT + p.request_id, + p.plat_index, + p.title, + p.tag, + p.user_index, + p.url, + p.img, + p.publish_time, + p.status, + pl.index as plat_index_value, + pl.status as plat_status, + pl.login_url, + pl.edit_url, + pl.logined_url, + pl.desc + FROM publish p + INNER JOIN plat pl ON p.plat_index COLLATE utf8mb4_unicode_ci = pl.index AND pl.status = 1 + WHERE uid='eba6d8a6-88c3-49' AND p.token_id = 1 + ORDER BY p.publish_time DESC + LIMIT 1 + +publish +2026/04/10 16:52:54.501413 ================================================================================ +2026/04/10 16:52:54.501413 任务开始 | RequestID: 5 | 时间: 2026-04-10 16:52:54.501 +2026/04/10 16:52:54.501413 ================================================================================ +2026/04/10 16:52:54.501413 [任务 5] 开始处理(headless=true) +2026/04/10 16:52:54.501413 [任务 5] 任务详情 - 平台:sphsp,标题:盘点全网最火的meme原型,用户:0d86b848uu2183uu4a08 +2026/04/10 16:52:54.501413 [任务 5] 标签解析完成: [猫 meme 万恶之源] +2026/04/10 16:52:54.501413 [任务 5] 开始下载文档... +2026/04/10 16:52:54 [Worker-2] 开始处理任务 requestID=4 +2026/04/10 16:52:54 [Worker-0] 开始处理任务 requestID=5 +2026/04/10 16:52:54.568721 [任务 6] ✅ 文档下载成功: D:\gop\geoGo\docs\6.docx +2026/04/10 16:52:54.569769 [任务 6] 开始下载图片... +2026/04/10 16:52:54.629638 [任务 6] ✅ 图片下载成功: D:\gop\geoGo\images\6_1e3dcdb4-0f8a-4b57-aac2-85ecff03ff12.jpg +2026/04/10 16:52:55.723048 [任务 4] ✅ 文档下载成功: D:\gop\geoGo\docs\4.mp4 +2026/04/10 16:52:55.723048 [任务 4] 开始下载图片... +2026/04/10 16:52:56.014721 [任务 5] ✅ 文档下载成功: D:\gop\geoGo\docs\5.mp4 +2026/04/10 16:52:56.015773 [任务 5] 开始下载图片... +2026/04/10 16:52:56.206874 [任务 5] ✅ 图片下载成功: D:\gop\geoGo\images\5_003e4110-e937-491f-8cf2-92df3de00a8c.png +2026/04/10 16:52:56.206874 [任务 5] 开始执行发布... +2026/04/10 16:52:56.206874 📌 ================================================== +2026/04/10 16:52:56.206874 📌 开始执行:sphsp +2026/04/10 16:52:56.206874 📌 标题: 盘点全网最火的meme原型 +2026/04/10 16:52:56.206874 📌 标签: [猫 meme 万恶之源] +2026/04/10 16:52:56.206874 📌 ================================================== +2026/04/10 16:52:56.206874 📌 初始化浏览器。。。。 +2026/04/10 16:52:56.233002 [任务 4] ✅ 图片下载成功: D:\gop\geoGo\images\4_769c6040-bff9-472b-9b9c-33d8f74cf61f.png +2026/04/10 16:52:56.233002 [任务 4] 开始执行发布... +2026/04/10 16:52:56.233002 📌 ================================================== +2026/04/10 16:52:56.233002 📌 开始执行:xhssp +2026/04/10 16:52:56.233002 📌 标题: 萌妹 +2026/04/10 16:52:56.233002 📌 标签: [萌妹 宅舞 超性感] +2026/04/10 16:52:56.233002 📌 ================================================== +2026/04/10 16:52:56.233002 📌 初始化浏览器。。。。 +2026/04/10 16:52:56.907120 [任务 6] 开始提取文档内容... +2026/04/10 16:52:58.614081 [任务 6] ✅ 内容提取成功,长度: 69 +2026/04/10 16:52:58.614081 [任务 6] 开始执行发布... +2026/04/10 16:52:58.614081 📌 ================================================== +2026/04/10 16:52:58.614081 📌 开始执行:zh +2026/04/10 16:52:58.614081 📌 标题: 地推看过来!!! +2026/04/10 16:52:58.614081 📌 标签: [#地推 #四川地推 #创业] +2026/04/10 16:52:58.614081 📌 ================================================== +2026/04/10 16:52:58.614081 📌 初始化浏览器。。。。 +2026/04/10 16:52:58.616804 ✅ 初始化页面: 成功 +2026/04/10 16:52:59.468517 [任务 5] ❌ 发布失败: 需要登录 +2026/04/10 16:52:59.617059 📌 开始上传视频: D:\gop\geoGo\docs\4.mp4 +2026/04/10 16:52:59.646726 📌 视频文件已选择: D:\gop\geoGo\docs\4.mp4 +2026/04/10 16:52:59.646726 📌 等待视频上传完成... + +2026/04/10 16:52:59 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[429.171ms] [rows:1] UPDATE publish SET status = 3, msg = '需要登录' WHERE token_id=1 AND request_id = '5' +publish +2026/04/10 16:52:59.897687 ================================================================================ +2026/04/10 16:52:59.897687 任务结束 | RequestID: 5 | 结果: false +2026/04/10 16:52:59.898712 [任务 5] 已删除文档文件: D:\gop\geoGo\docs\5.mp4 +2026/04/10 16:52:59.899367 [任务 5] 已删除图片文件: D:\gop\geoGo\images\5_003e4110-e937-491f-8cf2-92df3de00a8c.png +2026/04/10 16:52:59 [Worker-0] 任务失败: 需要登录 +2026/04/10 16:53:00.050108 📌 发布按钮已可点击,视频上传完成 +2026/04/10 16:53:00.050108 ✅ 上传视频: 成功 +2026/04/10 16:53:01.050256 📌 输入视频标题: 萌妹 +2026/04/10 16:53:01.755888 📌 未登录或登录已过期,需要重新登录 +2026/04/10 16:53:01.755888 ❌ 初始化页面: 失败 需要登录 +2026/04/10 16:53:01.779792 [任务 6] ❌ 发布失败: 初始化页面失败: 需要登录 + +2026/04/10 16:53:02 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[427.718ms] [rows:1] UPDATE publish SET status = 3, msg = '初始化页面失败: 需要登录' WHERE token_id=1 AND request_id = '6' +publish +2026/04/10 16:53:02.207510 ================================================================================ +2026/04/10 16:53:02.207510 任务结束 | RequestID: 6 | 结果: false +2026/04/10 16:53:02.208014 [任务 6] 已删除文档文件: D:\gop\geoGo\docs\6.docx +2026/04/10 16:53:02.208014 [任务 6] 已删除图片文件: D:\gop\geoGo\images\6_1e3dcdb4-0f8a-4b57-aac2-85ecff03ff12.jpg +2026/04/10 16:53:02 [Worker-1] 任务失败: 初始化页面失败: 需要登录 + +2026/04/10 16:53:02 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[406.214ms] [rows:1] + UPDATE publish p + SET p.status = 2,uid='e3a191eb-07d2-48' + WHERE p.request_id = ( + SELECT request_id FROM ( + SELECT p2.request_id + FROM publish p2 + INNER JOIN plat pl ON p2.plat_index COLLATE utf8mb4_unicode_ci = pl.index + WHERE p2.token_id = 1 + AND p2.status = 1 + AND p2.publish_time <= '2026-04-10 16:53:01' + AND pl.status = 1 + ORDER BY p2.publish_time ASC + LIMIT 1 + ) AS tmp + ) + AND p.status = 1 + +publish +2026/04/10 16:53:02.529767 ❌ 输入标题: 失败 未找到标题输入框 +2026/04/10 16:53:02.529767 [任务 4] ❌ 发布失败: 输入标题失败: 未找到标题输入框 + +2026/04/10 16:53:02 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[379.609ms] [rows:1] + SELECT + p.request_id, + p.plat_index, + p.title, + p.tag, + p.user_index, + p.url, + p.img, + p.publish_time, + p.status, + pl.index as plat_index_value, + pl.status as plat_status, + pl.login_url, + pl.edit_url, + pl.logined_url, + pl.desc + FROM publish p + INNER JOIN plat pl ON p.plat_index COLLATE utf8mb4_unicode_ci = pl.index AND pl.status = 1 + WHERE uid='e3a191eb-07d2-48' AND p.token_id = 1 + ORDER BY p.publish_time DESC + LIMIT 1 + +publish +2026/04/10 16:53:02.686657 ================================================================================ +2026/04/10 16:53:02.686657 任务开始 | RequestID: 1 | 时间: 2026-04-10 16:53:02.686 +2026/04/10 16:53:02.686657 ================================================================================ +2026/04/10 16:53:02.686657 [任务 1] 开始处理(headless=true) +2026/04/10 16:53:02.686657 [任务 1] 任务详情 - 平台:xhs,标题:房地产新格局,用户:0d86b848uu2183uu4a08 +2026/04/10 16:53:02.686657 [任务 1] 标签解析完成: [房地产 房地产格局] +2026/04/10 16:53:02.686657 [任务 1] 开始下载文档... +2026/04/10 16:53:02 [Worker-0] 开始处理任务 requestID=1 +2026/04/10 16:53:02.750797 [任务 1] ✅ 文档下载成功: D:\gop\geoGo\docs\1.docx +2026/04/10 16:53:02.750797 [任务 1] 开始下载图片... + +2026/04/10 16:53:02 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[445.705ms] [rows:1] UPDATE publish SET status = 3, msg = '输入标题失败: 未找到标题输入框' WHERE token_id=1 AND request_id = '4' +publish +2026/04/10 16:53:02.975473 ================================================================================ +2026/04/10 16:53:02.975473 任务结束 | RequestID: 4 | 结果: false +2026/04/10 16:53:02.976009 [任务 4] 已删除文档文件: D:\gop\geoGo\docs\4.mp4 +2026/04/10 16:53:02.976539 [任务 4] 已删除图片文件: D:\gop\geoGo\images\4_769c6040-bff9-472b-9b9c-33d8f74cf61f.png +2026/04/10 16:53:02 [Worker-2] 任务失败: 输入标题失败: 未找到标题输入框 +2026/04/10 16:53:03.042424 [任务 1] ✅ 图片下载成功: D:\gop\geoGo\images\1_9ffe0dd7-108b-455d-aca4-bf729eba7ab9.png +2026/04/10 16:53:03.042424 [任务 1] 开始提取文档内容... + +2026/04/10 16:53:04 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[378.221ms] [rows:1] + UPDATE publish p + SET p.status = 2,uid='5b37ab96-e0de-46' + WHERE p.request_id = ( + SELECT request_id FROM ( + SELECT p2.request_id + FROM publish p2 + INNER JOIN plat pl ON p2.plat_index COLLATE utf8mb4_unicode_ci = pl.index + WHERE p2.token_id = 1 + AND p2.status = 1 + AND p2.publish_time <= '2026-04-10 16:53:04' + AND pl.status = 1 + ORDER BY p2.publish_time ASC + LIMIT 1 + ) AS tmp + ) + AND p.status = 1 + +publish +2026/04/10 16:53:04.715720 [任务 1] ✅ 内容提取成功,长度: 5372 +2026/04/10 16:53:04.715720 [任务 1] 开始执行发布... +2026/04/10 16:53:04.715720 📌 ================================================== +2026/04/10 16:53:04.715720 📌 开始执行:xhs +2026/04/10 16:53:04.715720 📌 标题: 房地产新格局 +2026/04/10 16:53:04.715720 📌 标签: [房地产 房地产格局] +2026/04/10 16:53:04.715720 📌 ================================================== +2026/04/10 16:53:04.715720 📌 初始化浏览器。。。。 + +2026/04/10 16:53:05 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[421.319ms] [rows:1] + SELECT + p.request_id, + p.plat_index, + p.title, + p.tag, + p.user_index, + p.url, + p.img, + p.publish_time, + p.status, + pl.index as plat_index_value, + pl.status as plat_status, + pl.login_url, + pl.edit_url, + pl.logined_url, + pl.desc + FROM publish p + INNER JOIN plat pl ON p.plat_index COLLATE utf8mb4_unicode_ci = pl.index AND pl.status = 1 + WHERE uid='5b37ab96-e0de-46' AND p.token_id = 1 + ORDER BY p.publish_time DESC + LIMIT 1 + +publish +2026/04/10 16:53:05.009080 ================================================================================ +2026/04/10 16:53:05.009080 任务开始 | RequestID: 2 | 时间: 2026-04-10 16:53:05.009 +2026/04/10 16:53:05.009080 ================================================================================ +2026/04/10 16:53:05.009080 [任务 2] 开始处理(headless=true) +2026/04/10 16:53:05.009080 [任务 2] 任务详情 - 平台:xhs,标题:健身与猝死的关系,用户:0d86b848uu2183uu4a08 +2026/04/10 16:53:05.009080 [任务 2] 标签解析完成: [健身 猝死] +2026/04/10 16:53:05.009080 [任务 2] 开始下载文档... +2026/04/10 16:53:05 [Worker-1] 开始处理任务 requestID=2 +2026/04/10 16:53:05.067271 [任务 2] ✅ 文档下载成功: D:\gop\geoGo\docs\2.docx +2026/04/10 16:53:05.067271 [任务 2] 开始下载图片... +2026/04/10 16:53:05.309691 [任务 2] ✅ 图片下载成功: D:\gop\geoGo\images\2_ec459546-2873-4539-ae96-4d99c19ec62d.jpg +2026/04/10 16:53:05.309691 [任务 2] 开始提取文档内容... + +2026/04/10 16:53:05 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[401.004ms] [rows:0] + UPDATE publish p + SET p.status = 2,uid='d54d1bab-80a4-4a' + WHERE p.request_id = ( + SELECT request_id FROM ( + SELECT p2.request_id + FROM publish p2 + INNER JOIN plat pl ON p2.plat_index COLLATE utf8mb4_unicode_ci = pl.index + WHERE p2.token_id = 1 + AND p2.status = 1 + AND p2.publish_time <= '2026-04-10 16:53:04' + AND pl.status = 1 + ORDER BY p2.publish_time ASC + LIMIT 1 + ) AS tmp + ) + AND p.status = 1 + +publish +2026/04/10 16:53:06.894855 [任务 2] ✅ 内容提取成功,长度: 2274 +2026/04/10 16:53:06.894855 [任务 2] 开始执行发布... +2026/04/10 16:53:06.894855 📌 ================================================== +2026/04/10 16:53:06.894855 📌 开始执行:xhs +2026/04/10 16:53:06.894855 📌 标题: 健身与猝死的关系 +2026/04/10 16:53:06.894855 📌 标签: [健身 猝死] +2026/04/10 16:53:06.894855 📌 ================================================== +2026/04/10 16:53:06.894855 📌 初始化浏览器。。。。 +2026/04/10 16:53:07.849886 ✅ 初始化: 成功 +2026/04/10 16:53:08.352714 ✅ 保存cookie: 成功 +2026/04/10 16:53:09.245978 ✅ 初始化: 成功 +2026/04/10 16:53:09.748755 ✅ 保存cookie: 成功 +2026/04/10 16:53:10.331882 📌 已点击上传按钮 +2026/04/10 16:53:11.332203 ✅ 点击上传按钮: 成功 +2026/04/10 16:53:11.832762 📌 输入文章内容... +2026/04/10 16:53:12.378724 📌 清空编辑器失败: eval js error: TypeError: Cannot set properties of undefined (setting 'innerText') + at HTMLDivElement. (:1:44) + at HTMLDivElement. (:1:127) +2026/04/10 16:53:12.748818 📌 内容已输入,长度: 2274 +2026/04/10 16:53:12.748818 ✅ 输入内容: 成功 +2026/04/10 16:53:13.249362 📌 输入标题... + +2026/04/10 16:53:37 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[438.945ms] [rows:0] + UPDATE publish p + SET p.status = 2,uid='49e9bf0f-120a-4c' + WHERE p.request_id = ( + SELECT request_id FROM ( + SELECT p2.request_id + FROM publish p2 + INNER JOIN plat pl ON p2.plat_index COLLATE utf8mb4_unicode_ci = pl.index + WHERE p2.token_id = 1 + AND p2.status = 1 + AND p2.publish_time <= '2026-04-10 16:53:37' + AND pl.status = 1 + ORDER BY p2.publish_time ASC + LIMIT 1 + ) AS tmp + ) + AND p.status = 1 + +publish +2026/04/10 16:54:02 任务执行超时,[Worker-0] context deadline exceeded requestID=1 +2026/04/10 16:54:02.688485 click fail:context deadline exceeded +2026/04/10 16:54:02.688485 ❌ 点击上传按钮: 失败 JS点击按钮失败: context deadline exceeded +2026/04/10 16:54:02.689219 [任务 1] ❌ 发布失败: 点击上传按钮失败: JS点击按钮失败: context deadline exceeded + +2026/04/10 16:54:03 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[421.134ms] [rows:1] UPDATE publish SET status = 3, msg = '任务执行超时,[Worker-0] context deadline exceeded requestID=1' WHERE token_id=1 AND request_id = '1' +publish + +2026/04/10 16:54:03 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[470.041ms] [rows:1] UPDATE publish SET status = 3, msg = '点击上传按钮失败: JS点击按钮失败: context deadline exceeded' WHERE token_id=1 AND request_id = '1' +publish +2026/04/10 16:54:03.159260 ================================================================================ +2026/04/10 16:54:03.159260 任务结束 | RequestID: 1 | 结果: false +2026/04/10 16:54:03.159767 [任务 1] 已删除文档文件: D:\gop\geoGo\docs\1.docx +2026/04/10 16:54:03.160296 [任务 1] 已删除图片文件: D:\gop\geoGo\images\1_9ffe0dd7-108b-455d-aca4-bf729eba7ab9.png +2026/04/10 16:54:05 任务执行超时,[Worker-1] context deadline exceeded requestID=2 +2026/04/10 16:54:05.009280 ❌ 输入标题: 失败 未找到标题输入框 +2026/04/10 16:54:05.009280 [任务 2] ❌ 发布失败: 输入标题失败: 未找到标题输入框 + +2026/04/10 16:54:05 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[403.700ms] [rows:1] UPDATE publish SET status = 3, msg = '任务执行超时,[Worker-1] context deadline exceeded requestID=2' WHERE token_id=1 AND request_id = '2' +publish + +2026/04/10 16:54:05 D:/gop/geoGo/utils/utils_gorm/sql_log.go:46 SLOW SQL >= 200ms +[428.723ms] [rows:1] UPDATE publish SET status = 3, msg = '输入标题失败: 未找到标题输入框' WHERE token_id=1 AND request_id = '2' +publish +2026/04/10 16:54:05.438513 ================================================================================ +2026/04/10 16:54:05.438513 任务结束 | RequestID: 2 | 结果: false +2026/04/10 16:54:05.439077 [任务 2] 已删除文档文件: D:\gop\geoGo\docs\2.docx +2026/04/10 16:54:05.439641 [任务 2] 已删除图片文件: D:\gop\geoGo\images\2_ec459546-2873-4539-ae96-4d99c19ec62d.jpg + + diff --git a/pkg/func.go b/pkg/func.go index be3de7f..8129fb0 100644 --- a/pkg/func.go +++ b/pkg/func.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "math/rand/v2" + "net" "net/http" "os" "path/filepath" @@ -315,3 +316,18 @@ func ParseTags(tagStr string) []string { } return result } + +func GetFreePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return 0, err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + defer l.Close() + + return l.Addr().(*net.TCPAddr).Port, nil +}