This commit is contained in:
Rzy 2026-04-10 18:33:22 +08:00
parent 777611e996
commit 15ea020bc1
20 changed files with 787 additions and 109 deletions

View File

@ -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}]
[{"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}]

View File

@ -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)

View File

@ -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)

View File

@ -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())
}

View File

@ -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 {

View File

@ -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

View File

@ -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,
},
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -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)

View File

@ -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 {

View File

@ -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)

View File

@ -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)

143
internal/service/a_test.go Normal file
View File

@ -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)
// }
//}
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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=1worker数量=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.<anonymous> (<anonymous>:1:44)
at HTMLDivElement.<anonymous> (<anonymous>:1:127) <nil>
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

View File

@ -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
}