ERPTurbo_Poster/server.mjs
shenyifei dc940d2598 feat(api): 添加海报和PDF生成功能
- 新增海报生成接口,支持从网页URL或HTML内容生成海报图像
- 新增PDF生成接口,支持从网页URL或HTML内容生成PDF文档
- 添加Swagger API文档注释,完善接口描述和参数说明
- 实现HTML内容参数支持,允许直接传入HTML结构生成海报/PDF
- 添加输入验证和标准化响应格式
- 引入DOMPurify库对HTML内容进行安全过滤
- 更新环境变量配置,支持API密钥认证和CORS设置
- 优化上传逻辑,统一返回标准响应结构
- 添加构建脚本支持Docker镜像打包和推送
2025-11-20 17:51:35 +08:00

188 lines
5.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import express from 'express';
import bodyParser from 'body-parser';
import dotenv from 'dotenv';
import swaggerUi from 'swagger-ui-express';
import specs from './lib/swagger.js';
import cors from 'cors';
// 配置 dotenv
dotenv.config();
// 处理API前缀配置
function getApiPrefix() {
let prefix;
if ('API_PREFIX' in process.env) {
prefix = process.env.API_PREFIX;
} else {
prefix = '/api/v1';
}
// 如果明确设置为空字符串,则返回空
if (prefix === '') {
return '';
}
// 标准化前缀格式:确保以斜杠开头且不以斜杠结尾
if (prefix && !prefix.startsWith('/')) {
prefix = '/' + prefix;
}
if (prefix && prefix.length > 1 && prefix.endsWith('/')) {
prefix = prefix.slice(0, -1);
}
return prefix;
}
// 处理CORS配置
function getCorsConfig() {
const enableCors = process.env.ENABLE_CORS === 'true';
if (!enableCors) {
return null; // 不启用CORS
}
const corsOrigins = process.env.CORS_ORIGINS;
let origins = ['http://localhost:3000']; // 默认安全配置
if (corsOrigins) {
try {
// 支持多个源,用逗号分隔
origins = corsOrigins.split(',').map(origin => origin.trim()).filter(origin => origin);
} catch (error) {
console.error('CORS配置解析错误使用默认配置:', error.message);
}
}
return {
origin: origins,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
credentials: true,
optionsSuccessStatus: 200 // 支持老版本浏览器
};
}
const apiPrefix = getApiPrefix();
const corsConfig = getCorsConfig();
import BrowserManager from './lib/browser.js';
import StorageManager from './lib/storage.js';
import {pdfHandler, posterHandler, statusHandler} from './lib/routes.js';
import apiKeyAuth from './lib/auth.js';
import {validationErrorResponse} from './lib/response.js';
// 初始化存储管理器
const storageManager = new StorageManager(process.env);
// 初始化浏览器管理器
const browserManager = new BrowserManager();
await browserManager.initBrowser(2);
const app = express();
// 应用CORS中间件如果启用
if (corsConfig) {
app.use(cors(corsConfig));
console.log('✅ CORS已启用允许的源:', corsConfig.origin);
}
app.use(bodyParser.json({limit: '10mb'}));
// 配置Swagger UI
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
// 提供上传文件的静态访问
app.use('/uploads', express.static('uploads'));
/**
* @swagger
* /status:
* get:
* summary: 健康检查
* description: 检查服务是否正常运行
* tags: [Health]
* responses:
* 200:
* description: 服务正常运行
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* description: 请求是否成功
* data:
* type: object
* example: {}
* description: 响应数据
* message:
* type: string
* example: "Service is running"
* description: 响应消息
* code:
* type: number
* example: 200
* description: 响应代码
* 500:
* description: 服务异常
*/
// 健康检查接口
app.get('/status', statusHandler);
// 添加安全头
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
next();
});
// 海报生成接口
app.post(`${apiPrefix}/poster`, apiKeyAuth, async function (req, res) {
// 基本的安全检查
if (!req.headers['content-type'] || !req.headers['content-type'].includes('application/json')) {
return res.status(400).json(validationErrorResponse('Content-Type must be application/json', 3002));
}
await posterHandler(
req,
res,
browserManager,
storageManager
);
});
// PDF生成接口
app.post(`${apiPrefix}/pdf`, apiKeyAuth, async function (req, res) {
// 基本的安全检查
if (!req.headers['content-type'] || !req.headers['content-type'].includes('application/json')) {
return res.status(400).json(validationErrorResponse('Content-Type must be application/json', 3002));
}
await pdfHandler(
req,
res,
browserManager,
storageManager
);
});
const port = process.env.PORT || 3000;
// 服务器启动时显示配置信息
console.log('🚀 服务器配置信息:');
console.log(` 端口: ${port}`);
console.log(` API前缀: ${apiPrefix}`);
console.log(` CORS状态: ${corsConfig ? '已启用' : '已禁用'}`);
if (corsConfig) {
console.log(` 允许的源: ${corsConfig.origin.join(', ')}`);
}
console.log(' 健康检查: /status');
console.log(` 海报API: ${apiPrefix}/poster`);
console.log(` PDF API: ${apiPrefix}/pdf`);
console.log(' API文档: /api-docs');
app.listen(port, () => console.log(`\n✨ Puppeteer app listening on port ${port}!`));