数智赛事小程序 生产球员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进入分页。