puppeteer 生成pdf

数智赛事小程序 生产球员pdf报告

 

在node中同时多个生成请求会造成无头浏览器崩溃(服务器配置不高的情况下)

 pdfRenderTaskQuene.addTask(taskId,callback,[url, fileKey, generateType, generateConf, storageConf], taskProcessor);

所以生成前先创建个队列 

 

async function taskProcessor(accessUrl, fileKey, generateType, gerateConf, storeConf){
    const filePath = path.join(fileTempDir, fileKey);
    const localFilePath = generateType == "image" ? await justGenerateImg(accessUrl, filePath, gerateConf) 
        : await justGeneratePdf(accessUrl, filePath, gerateConf);
    const url = await justUploadFile(localFilePath, fileKey, storeConf);

    return url;
} 

将要调用的方法添加到队列中

 

 

    addTask(id,callback,taskArg,processor,forceAndPriority=false) {
        const newTask = {
            id: id,
            callbacks: callback ? [callback] : null,
            taskArg: taskArg,
            processor: processor ? processor : this._processor
        };
        console.info("开始添加任务:", id);
        // 是否该任务是否存在于未消费队列
        let taskInQueue = this._findTask(id,this._tasksQueue);
        // 是否存在于正在执行的队列中
        let tasktInCurrent = false;
        if(!taskInQueue) tasktInCurrent = this._findTask(id,this._currentTask); 
        // 如果该任务不存在
        if(!taskInQueue && !tasktInCurrent){
            this._tasksQueue.push(newTask);
        }else if(forceAndPriority){
            if(tasktInCurrent) this._delTask(tasktInCurrent,this._currentTask);
            if(taskInQueue) this._delTask(taskInQueue,this._tasksQueue);
            // 如果当前存在该任务,则在原有的任务项的回调列表里增加新的回调
            // existentTask.callbacks.push(callback);
        }
        // 纪录运行器的繁忙状态
        const isBusy = this.ifBusy();
        // 若不繁忙则立即执行新添加的任务
        // 如果存在优先级则立即执行
        if(forceAndPriority || (!isBusy && !tasktInCurrent && !taskInQueue)){
            this._runTask(newTask,forceAndPriority);
        }
        console.info(`当前执行队列任务数量:${this._tasksQueue.length}`);
        return !isBusy;
    }

添加任务的方法。若任务不在队列中,添加到任务队列。若 forceAndPriority 为真,优先执行。根据繁忙状态决定是否立即执行任务。

 

 

运行任务

_runTask(task, priority = false) {
    console.info(`正在执行任务: ${task.id}`);
    const that = this;
    keepRunningTask = keepRunningTask.bind(this);
    !priority ? this._currentTask.push(task) : this._currentTask.unshift(task);
    this._delTask(task, this._tasksQueue);
    const taskRun = task.processor.apply(this, task.taskArg);
    if (taskRun.then) {
        return taskRun.then(result => {
            this._delTask(task, this._currentTask);
            invokeCallbacks(null, result);
            keepRunningTask();
        }).catch(err => {
            this._delTask(task, this._currentTask);
            invokeCallbacks(err ? err : new Error());
            keepRunningTask();
        });
    }
    invokeCallbacks(taskRun);
    keepRunningTask();
    this._delTask(task, this._currentTask);
    function keepRunningTask() {
        if (this._tasksQueue.length) {
            this._runTask(this._tasksQueue[0]);
        }
    }
    function invokeCallbacks(error, arg) {
        console.log(`task ${task.id} 已完成,error ${error},还剩下 ${that._tasksQueue.length} 任务`);
        Array.isArray(task.callbacks) && task.callbacks.forEach(callback => {
            callback(error, arg);
        });
    }
}

运行任务的方法。根据优先级决定任务插入位置并从队列中删除。若任务返回 Promise,在 then 中执行回调并继续运行下一个任务。否则立即执行回调。

 

async renderPdf(pageAccessAddress,localFilePath,config={}){
        try{
            console.info('浏览器正在生成pdf...,访问地址',pageAccessAddress, config);
            const borwser = await this.launchBrowser();
            const page = await borwser.newPage();
            console.info('pupteer: 新增页面完成');
            const configClone = {...config};
            page.on('error',function(error){
                // todo
                console.error(`浏览器渲染PDF发生错误:${pageAccessAddress}`,error);
            });
            const response = await page.goto(pageAccessAddress,{ waitUntil: configClone.waitUntil || 'networkidle0',timeout: typeof configClone.timeout !== "undefined" ? configClone.timeout : 1000 * 60 * 2 });
            if(!response.ok() && response.status() !== 304) throw new Error(`页面请求失败,status: ${response.status()}`);
            console.info('pupteer: 页面加载完成');
            ('waitUntil' in configClone) && (delete configClone.waitUntil);
            ('timeout' in configClone) && (delete configClone.timeout);
            if(configClone.height == "auto"){
                const pageHeight = await page.evaluate(() => { return document.documentElement.scrollHeight; });
                configClone.height = pageHeight || undefined;
            }
            await page.pdf({
                path: localFilePath,
                headerTemplate: this.genereteHeaderTemplate(),
                footerTemplate: this.genereteFooterTemplate(),
                ...configClone
            });
            console.info(`pdf生成成功,本地存储地址:${localFilePath}`);
            await page.close();
            console.info('pupteer: 页面成功关闭');
            return localFilePath;
        }catch(error){
            console.error('浏览器渲染PDF失败!',pageAccessAddress,error);
            throw new Error(error);
        }
    }

调用puppeteer api进行生成 ,同理生成图片则调用 puppeteer的screenshot api。

 

 

.page-break {
  page-break-after: always;
}

对于生成的pdf ,前端加入这个css进入分页。

登入分享下感受吧~