- 新增海报生成接口,支持从网页URL或HTML内容生成海报图像 - 新增PDF生成接口,支持从网页URL或HTML内容生成PDF文档 - 添加Swagger API文档注释,完善接口描述和参数说明 - 实现HTML内容参数支持,允许直接传入HTML结构生成海报/PDF - 添加输入验证和标准化响应格式 - 引入DOMPurify库对HTML内容进行安全过滤 - 更新环境变量配置,支持API密钥认证和CORS设置 - 优化上传逻辑,统一返回标准响应结构 - 添加构建脚本支持Docker镜像打包和推送
188 lines
5.1 KiB
JavaScript
188 lines
5.1 KiB
JavaScript
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}!`));
|