Skip to content
Nodejs使用队列(以截图网址对应页面为例)

初始化项目(空文件夹下)

js
npm init -y

安装相关包

js
// 截图相关
npm i puppeteer
npm i puppeteer-core  // 用不上,但是必须要下,被puppeteer所依赖

// 服务器相关
npm install express

根目录下创建队列处理文件

worker.js

js
const puppeteer = require('puppeteer');
const { parentPort } = require('worker_threads');

async function capturePage(url) {
	// 下面的try里面写具体的业务逻辑(异步执行的逻辑代码,例如下面的截图逻辑) 
    try {
        const browser = await puppeteer.launch({ headless: true });
        const page = await browser.newPage();

        // 设置视口,deviceScaleFactor 影响清晰度
        // 下面这个可以根据ui尺寸设计
        // 打开的窗口大小(短图海报,不含按钮)
        await page.setViewport({ width: 750, height: 1320, deviceScaleFactor: 2 });

        // 导航到目标网页
        await page.goto(url);

        // 等待页面加载完成
        await new Promise(resolve => setTimeout(resolve, 2000)); // 等待 2 秒

        // 获取页面内容高度并调整视口
        const bodyHandle = await page.$('body');
        const boundingBox = await bodyHandle.boundingBox();
        await bodyHandle.dispose();

        // 这个设置截图的图片的宽高大小
        await page.setViewport({
            width: 750,
            height: Math.ceil(boundingBox.height),
            deviceScaleFactor: 2
        });
        let fileName = Date.now() + '.png';
        // 截取长页面截图
        await page.screenshot({ path: fileName });

        // 关闭浏览器
        await browser.close();

        return fileName;
    } catch (error) {
        return error;
    }
}

// 监听主文件app.js发送的消息
parentPort.on('message', async ({ url }) => {
    const result = await capturePage(url);
    parentPort.postMessage(result);
});

根目录下app.js代码

js
const express = require('express');
const { Worker } = require('worker_threads');
const app = express();

const maxThreads = 5;  // 默认最大线程数
let threadPool = [];   // 线程池
let queue = [];        // 等待任务的队列

app.use(express.json());

// 初始化线程池(初始化时创建5个线程)
for (let i = 0; i < maxThreads; i++) {
  threadPool.push(createWorker());
}

// 创建新线程并运行
function createWorker() {
  return {
    busy: false,
    worker: new Worker('./worker.js'),
  };
}

// 动态分配线程
function assignTaskToWorker(url) {
  const availableWorker = threadPool.find((w) => !w.busy);

  // 如果有可用线程,分配任务(busy为false代表线程空闲,线程可用)
  if (availableWorker) {
    availableWorker.busy = true; // 标记为忙碌
    availableWorker.worker.postMessage({ url }); // 发送消息给worker
    availableWorker.worker.once('message', (msg) => { // 接收worker的消息
      console.log(`任务完成:${msg}`); // 处理结果
      availableWorker.busy = false; // 标记为空闲

      if (queue.length > 0) {
        const nextTask = queue.shift();
        assignTaskToWorker(nextTask);
      }
    });
  } else {
    // 如果没有可用线程,将任务加入队列
    queue.push(url);
    console.log('所有线程都在忙,任务已加入队列');
  }
}

// 接受 POST 请求
app.post('/capture', (req, res) => {
  const { url } = req.body;  // 获取到参数信息

  if (!url) {
    // 缺少参数
    return res.status(400).send('请输入有效的 URL');
  }

  console.log(`接受到新任务: ${url}`);
  // 进入队列执行
  assignTaskToWorker(url);
  res.send('任务已提交');
});

// 启动服务器
app.listen(3000, () => {
  console.log('服务器运行在 http://localhost:3000');
});

运行项目

js
node .\app.js

打开apifox,新建接口,地址为:http://127.0.0.1:3000/capture,参数选择body-json,内容如下:

js
{
    "url":"https://www.baidu.com/"
}

多次频繁请求接口,查看控制台打印信息以及侧边文件新增的图片信息即可