go代码打开浏览器的编码设计过程

113 阅读4分钟

一、初始版本:使用定时任务执行

  1. 配置方式
    配置项中使用 jobStatus 控制任务启停,jobCron 设定 Cron 表达式,任务调度使用 robfig/cron 库。
  2. 执行逻辑
    启动时根据配置注册定时任务,任务执行时通过 exec.Command 打开 Chrome 浏览器访问指定网址(如百度),随后立即关闭浏览器。
  3. 存在问题
    • 浏览器打开后立即关闭,实际网页还未加载完成;
    • 任务每次都会强行开启和关闭浏览器,资源浪费严重;
    • 定时执行缺乏灵活性,容易错过或重复执行。
job:
  jobStatus:
    runstartsync: true

  jobCron:
    runstartsync: "0 0 2 * * 0"
var Cfg = &Config{}


type Config struct {
	Job    CronJobConfig `json:"job"`
}

type CronJobConfig struct {
	JobStatus map[string]bool   `json:"jobStatus"`
	JobCron   map[string]string `json:"jobCron"`
}


func InitConfig(filePath string) {
	// 读取配置文件
	if filePath == "" {
		viper.SetConfigFile("config.yaml")
	} else {
		viper.SetConfigFile(filePath)
	}

	err := viper.ReadInConfig()
	if err != nil {
		fmt.Printf("Error reading config file: %v\n", err)
		return
	}

	err = viper.Unmarshal(Cfg)
	if err != nil {
		fmt.Printf("Error unmarshaling config: %v\n", err)
		return
	}
	fmt.Println(Cfg)
}

添加定时任务

package job

import (
	"fmt"
	"github.com/robfig/cron/v3"
)

var (
	JobList = make([]*Job, 0)
)

type Job struct {
	Name   string
	Status bool
	Cron   string
	Func   func()
}

var cronScheduler *cron.Cron

func StartCronJob() {
	addJob()
	// 创建一个支持秒级的 cron 调度器
	cronScheduler = cron.New(cron.WithSeconds())

	for _, job := range JobList {
		if job.Status {
			fmt.Println("添加定时任务", job.Name+" "+job.Cron)
			cronScheduler.AddFunc(job.Cron, job.Func)
		}
	}

	// 启动 cron 调度器
	cronScheduler.Start()
	fmt.Println("定时任务启动")
}

// StopCronJob 停止定时任务
func StopCronJob() {
	if cronScheduler != nil {
		// 停止 cron 调度器
		stop := cronScheduler.Stop()
		// 等待所有任务完成
		<-stop.Done()
		fmt.Println("定时任务已停止")
	}
}
package job

import (
	"context"
	"jz-scraw/internal/app/consts"
	"jz-scraw/internal/app/logic"
	"jz-scraw/internal/app/model"
	"jz-scraw/internal/app/types"
	"jz-scraw/internal/config"
	"jz-scraw/pkg/http_call"
)

func AddRunStartSync() {
	name := "runstartsync"
	JobList = append(JobList, &Job{
		Status: config.Cfg.Job.JobStatus[name],
		Name:   name,
		Cron:   config.Cfg.Job.JobCron[name],
		Func:   addRunStartSync,
	})
}

func addRunStartSync() {
	cmd := exec.Command(`cmd`, `/c`, `start`, "chrome.exe", "www.baidu.com")
    cmd.Start()
    exec.Command(`taskkill`, `/im`, `chrome.exe`, `/f`).Run()
}
func main() {
    job.StopCronJob()
    }

二、优化方向一:增加随机延迟协程执行

为缓解资源压力及避免浏览器闪退,改用协程加延迟控制。

  1. 优化点
    • 打开网页后延迟关闭,延迟时间为 10~20 秒随机;
    • 使用协程异步执行任务,防止阻塞主线程;
    • 独立协程定时关闭浏览器,每 10 分钟执行一次,避免长期驻留。
  1. 改进效果
    • 网页有更充足的时间加载;
    • 控制更灵活,避免 CPU 和内存飙升。
  1. 仍存在问题
    • 定时关闭存在误杀风险(网页刚打开就被关闭);
    • 无法感知任务是否正在运行;
    • 缺乏运行状态判断和窗口数量控制。
func AddRunStartSync() {
	addRunStartSync()
}
func addRunStartSync() {
	cmd := exec.Command(`cmd`, `/c`, `start`, "chrome.exe", "www.baidu.com")
    cmd.Start()
    randomSec := 10 + rand.Intn(10)
	time.Sleep(time.Duration(randomSec) * time.Second)
    exec.Command(`taskkill`, `/im`, `chrome.exe`, `/f`).Run()
}

func CloseChrome() {
	fmt.Println("关闭了窗口")
	exec.Command(`taskkill`, `/im`, `chrome.exe`, `/f`).Run()
}
func main() {
    go func() {
		for {
			AddRunStartSync()
		}
	}()

    
	go func() {
		for {
			// 每 10分钟 关闭浏览器
			time.Sleep(60 * 10 * time.Second)
			job.CloseChrome()
		}
	}()
}

三、优化方向二:引入状态控制窗口数量感知机制

针对误杀浏览器窗口和任务冲突的问题,引入状态控制结构 isFuckRun,实现自动化流程更精细的调度策略:

  1. 状态标识
    • 使用原子变量 isRun 表示当前是否允许执行任务;
    • windowCount 记录当前已打开窗口数量,用于判断是否需关闭浏览器。
  1. 执行逻辑调整
    • 仅在 isRun == true 的情况下执行任务;
    • 执行一次后增加窗口计数;
    • 若窗口数超过阈值(如 12 个),自动关闭浏览器并清零计数;
    • isRun == false,则休眠 30 分钟后尝试重新启用。
  1. 优势
    • 实现了任务运行过程的“开关控制”;
    • 避免浏览器被重复启动或错误关闭;
    • 支持自我恢复,具备一定容错能力。
func main() {
    go func() {
        for {
            fuckRunClient := job.GetIsFuckRun()
            fuckRunClient.ConsumeJobMqOne()
        }
    }()
}
type isFuckRun struct {
	isRun       atomic.Bool
	windowCount atomic.Int64
}

var isFuckRunClient *isFuckRun
func InitIsFuckRun() {
	isFuckRunClient = &isFuckRun{}
	isFuckRunClient.Start()
}

func GetIsFuckRun() *isFuckRun {
	return isFuckRunClient
}
func (f *isFuckRun) ConsumeJobMqOne() {
	if f.isRun.Load() {
		addRunStartSync()
		logger.Logger.Info("执行自动化流程中")
        // 到达12个窗口以上就关闭浏览器(具体根据业务修改关闭位置)
        if f.GetWindowCount() > 12 {
			CloseChrome()
		}
	} else {
		// 休眠30分钟
		logger.Logger.Info("等待自动化流程开启")
		time.Sleep(30 * 60 * time.Second)
		f.Start()
		logger.Logger.Info("自动开启自动化流程")
	}
}

func (f *isFuckRun) Close() {
	logger.Logger.Info("自动化流程已经关闭")
	f.isRun.Store(false)
	f.windowCount.Store(0)
}
func (f *isFuckRun) Start() {
	f.isRun.Store(true)
}

func (f *isFuckRun) IsRun() bool {
	return f.isRun.Load()
}

// AddWindowCount  并返回当前窗口数量
func (f *isFuckRun) AddWindowCount() {
	f.windowCount.Add(1)
}
func (f *isFuckRun) SetWindowCountZero() {
	f.windowCount.Store(0)
}
func (f *isFuckRun) GetWindowCount() int64 {
	logger.Logger.Info(fmt.Sprintf("窗口数: %d", f.windowCount.Load()))
	return f.windowCount.Load()
}


func addRunStartSync() {
	cmd := exec.Command(`cmd`, `/c`, `start`, "chrome.exe", "www.baidu.com")
    cmd.Start()
    randomSec := 10 + rand.Intn(10)
	time.Sleep(time.Duration(randomSec) * time.Second)
    exec.Command(`taskkill`, `/im`, `chrome.exe`, `/f`).Run()
}

func CloseChrome() {
	fmt.Println("关闭了窗口")
	exec.Command(`taskkill`, `/im`, `chrome.exe`, `/f`).Run()
}