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 };