import {fileURLToPath} from 'url';
import {dirname} from 'path';
import {getParam, upload} from './params.js';
import {randomString, jsonToHtml, sanitizeHtml} from './utils.js';
import {FONT_STYLE} from './constants.js';
import {successResponse, errorResponse, validationErrorResponse, serverErrorResponse} from './response.js';
// 获取当前模块的目录名
const __filename = fileURLToPath(import.meta.url);
const basePath = dirname(dirname(__filename)); // 项目根目录
// 延迟获取 upload_path,确保环境变量已被加载
let upload_path;
/**
* 健康检查接口
* @param {*} req
* @param {*} res
*/
function statusHandler(req, res) {
console.log("健康检查", new Date().getMilliseconds());
return res.status(200).json(successResponse({}, "Service is running", 200));
}
/**
* @swagger
* /api/v1/poster:
* post:
* summary: 生成海报
* description: 从网页URL或HTML内容生成海报图像
* tags: [Poster]
* security:
* - ApiKeyAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* webpage:
* type: string
* description: 要生成海报的网页URL
* example: "https://example.com"
* html:
* type: string
* description: 要生成海报的HTML内容(可选,优先级高于webpage)
* example: "
Hello World
"
* device:
* type: number
* description: 设备缩放因子
* default: 1
* example: 1
* width:
* type: number
* description: 海报宽度
* default: 1920
* example: 1920
* height:
* type: number
* description: 海报高度
* default: 1080
* example: 1080
* type:
* type: string
* description: 输出图像类型
* default: "png"
* example: "png"
* encoding:
* type: string
* description: 编码类型
* default: "binary"
* example: "binary"
* responses:
* 200:
* description: 成功生成海报
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* description: 请求是否成功
* data:
* type: object
* properties:
* name:
* type: string
* example: "poster_abc123def456.png"
* description: 生成的海报文件名
* path:
* type: string
* example: "http://example.com/uploads/posters/2024/11/14/poster_abc123def456.png"
* description: 生成的海报文件访问路径
* message:
* type: string
* example: "Poster generated successfully"
* description: 响应消息
* code:
* type: number
* example: 200
* description: 响应代码
* 400:
* description: 请求参数错误
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* description: 请求是否成功
* data:
* type: object
* example: null
* description: 响应数据
* message:
* type: string
* example: "Missing required parameter: webpage or html"
* description: 错误消息
* code:
* type: number
* example: 3001
* description: 错误代码
* 401:
* description: 未授权访问
* 500:
* description: 服务器内部错误
*/
/**
* 海报生成接口
* @param {*} req
* @param {*} res
* @param {*} browserManager
* @param {*} storageManager
*/
async function posterHandler(
req,
res,
browserManager,
storageManager
) {
// 确保在使用时获取最新的环境变量值
upload_path = process.env.UPLOAD_PATH || 'uploads';
upload_path = upload_path + '/posters'
// 参数校验 - 现在支持webpage或html
if (!req.body || (!req.body.webpage && !req.body.html)) {
return res.status(400).json(validationErrorResponse('Missing required parameter: webpage or html', 3001));
}
const webpage = req.body.webpage;
let html = req.body.html;
const device = req.body.device || 1;
const width = req.body.width || 1920;
const height = req.body.height || 1080;
const type = req.body.type || 'png';
const encoding = req.body.encoding || 'binary';
const filename = 'poster_' + randomString(20) + '.' + type;
let param = getParam(req, filename, upload_path);
let base64;
const page = browserManager.getTargetPage();
try {
await page.setViewport({width: width, height: height, deviceScaleFactor: device, isMobile: true});
// 根据参数选择加载方式
if (html) {
// 检查html是否为JSON格式,如果是则转换为HTML字符串
if (typeof html === 'object' && html !== null) {
html = jsonToHtml(html);
}
// 直接使用HTML内容
await page.setContent(html, {
timeout: 30000,
waitUntil: 'networkidle0'
});
} else {
// 导航到网页
await page.goto(webpage, {
timeout: 30000,
waitUntil: 'networkidle0'
});
}
await page.addStyleTag({content: FONT_STYLE});
await page.screenshot(param).then((data) => {
base64 = data;
});
// Update the upload function to return standardized response
const result = await upload(res, encoding, base64, type, filename, storageManager, basePath, upload_path);
return res.status(200).json(successResponse(result, "Poster generated successfully", 200));
} catch (err) {
console.error('Poster generation error:', err);
return res.status(500).json(serverErrorResponse('Failed to generate poster', 4001));
} finally {
// 确保只归还有效的页面
if (page && !page.isClosed()) {
browserManager.returnTargetPage(page);
}
}
}
/**
* @swagger
* /api/v1/pdf:
* post:
* summary: 生成PDF
* description: 从网页URL或HTML内容生成PDF文档
* tags: [PDF]
* security:
* - ApiKeyAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* webpage:
* type: string
* description: 要生成PDF的网页URL
* example: "https://example.com"
* html:
* type: string
* description: 要生成PDF的HTML内容(可选,优先级高于webpage)
* example: "Hello World
"
* device:
* type: number
* description: 设备缩放因子
* default: 1
* example: 1
* width:
* type: number
* description: PDF宽度
* default: 1920
* example: 1920
* height:
* type: number
* description: PDF高度
* default: 1080
* example: 1080
* responses:
* 200:
* description: 成功生成PDF
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* description: 请求是否成功
* data:
* type: object
* properties:
* name:
* type: string
* example: "pdf_abc123def456.pdf"
* description: 生成的PDF文件名
* path:
* type: string
* example: "http://example.com/uploads/pdfs/2024/11/14/pdf_abc123def456.pdf"
* description: 生成的PDF文件访问路径
* message:
* type: string
* example: "PDF generated successfully"
* description: 响应消息
* code:
* type: number
* example: 200
* description: 响应代码
* 400:
* description: 请求参数错误
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* description: 请求是否成功
* data:
* type: object
* example: null
* description: 响应数据
* message:
* type: string
* example: "Missing required parameter: webpage or html"
* description: 错误消息
* code:
* type: number
* example: 3001
* description: 错误代码
* 401:
* description: 未授权访问
* 500:
* description: 服务器内部错误
*/
/**
* PDF下载接口
* @param {*} req
* @param {*} res
* @param {*} browserManager
* @param {*} storageManager
*/
async function pdfHandler(
req,
res,
browserManager,
storageManager
) {
// 确保在使用时获取最新的环境变量值
upload_path = process.env.UPLOAD_PATH || 'uploads';
upload_path = upload_path + '/pdfs'
// 参数校验
if (!req.body || (!req.body.webpage && !req.body.html)) {
return res.status(400).json(validationErrorResponse('Missing required parameter: webpage or html', 3001));
}
const webpage = req.body.webpage;
let html = req.body.html;
const filename = 'pdf_' + randomString(20) + '.pdf';
let base64;
const page = browserManager.getTargetPage();
try {
// 根据参数选择加载方式
if (html) {
// 检查html是否为JSON格式,如果是则转换为HTML字符串
if (typeof html === 'object' && html !== null) {
html = jsonToHtml(html);
}
// 直接使用HTML内容
await page.setContent(html, {
timeout: 30000,
});
} else {
// 导航到网页
await page.goto(webpage, {
timeout: 30000,
waitUntil: 'networkidle0'
});
}
await page.addStyleTag({content: FONT_STYLE});
// 生成PDF
await page.pdf({
format: 'A4',
printBackground: true,
path: `${upload_path}/${filename}`
});
// 复用上传函数处理PDF文件
const result = await upload(res, 'binary', base64, 'pdf', filename, storageManager, basePath, upload_path);
return res.status(200).json(successResponse(result, "PDF generated successfully", 200));
} catch (err) {
console.error('PDF generation error:', err);
return res.status(500).json(serverErrorResponse('Failed to generate PDF', 4002));
} finally {
// 确保只归还有效的页面
if (page && !page.isClosed()) {
browserManager.returnTargetPage(page);
}
}
}
export {
statusHandler,
posterHandler,
pdfHandler
};