服务端渲染方案
服务端渲染方案
ECharts 的服务端渲染(SSR)方案主要解决动态图表在服务端生成静态内容的需求。传统浏览器端渲染依赖 DOM 操作,而服务端环境缺乏 DOM 接口,需要通过特定方式实现图表渲染。以下是完整的实现路径和技术细节。
核心实现原理
ECharts 的服务端渲染依赖于 node-canvas
或 jsdom
这类模拟浏览器环境的工具库。底层通过以下步骤完成渲染:
- 环境模拟:在 Node.js 中创建虚拟的 Canvas 或 SVG 渲染环境
- 图表初始化:使用与浏览器端相同的配置选项初始化图表实例
- 数据绑定:将数据注入图表实例
- 渲染输出:生成 Base64 图片或 SVG 字符串
const { createCanvas } = require('canvas');
const echarts = require('echarts');
// 创建虚拟Canvas
const canvas = createCanvas(800, 600);
const chart = echarts.init(canvas);
// 设置图表配置
chart.setOption({
title: { text: '服务端渲染示例' },
series: [{ type: 'bar', data: [12, 19, 3, 5, 2] }]
});
// 输出PNG图片
const buffer = canvas.toBuffer('image/png');
fs.writeFileSync('output.png', buffer);
具体实现方案
方案一:Canvas 渲染
使用 node-canvas
库模拟浏览器 Canvas 环境,适合生成静态图片:
const { createCanvas } = require('canvas');
const echarts = require('echarts');
function renderChart(options) {
const canvas = createCanvas(options.width || 800, options.height || 600);
const chart = echarts.init(canvas);
chart.setOption(options.config);
return canvas.toDataURL(); // 返回DataURL
}
性能优化点:
- 复用 Canvas 实例减少内存开销
- 使用
offscreen-canvas
处理高并发场景 - 设置合理的图片质量参数
方案二:SVG 渲染
通过 jsdom
实现 SVG 格式输出,适合需要矢量图的场景:
const { JSDOM } = require('jsdom');
const echarts = require('echarts');
async function renderSVG(options) {
const dom = new JSDOM(`<!DOCTYPE html><div id="chart"></div>`);
const chart = echarts.init(dom.window.document.getElementById('chart'));
chart.setOption(options);
return chart.renderToSVGString(); // 直接输出SVG字符串
}
特性对比:
特性 | Canvas方案 | SVG方案 |
---|---|---|
输出格式 | 位图 | 矢量图 |
文件大小 | 较大 | 较小 |
渲染性能 | 快 | 较慢 |
缩放效果 | 失真 | 无损 |
动态数据渲染
服务端渲染同样支持动态数据更新,典型处理流程:
// 模拟数据库查询
async function fetchData() {
return {
xAxis: ['Mon', 'Tue', 'Wed'],
series: [Math.random() * 100, Math.random() * 100]
};
}
// 动态渲染函数
async function renderDynamicChart() {
const data = await fetchData();
const canvas = createCanvas(600, 400);
const chart = echarts.init(canvas);
chart.setOption({
xAxis: { data: data.xAxis },
series: [{ data: data.series }]
});
return canvas.toBuffer();
}
集群渲染方案
高并发场景下推荐采用以下架构:
客户端请求 → 负载均衡 → 渲染集群 → Redis缓存 → 返回结果
具体实现代码示例:
// 使用Koa构建渲染服务
const Koa = require('koa');
const router = require('@koa/router')();
const app = new Koa();
// 添加缓存中间件
const cache = require('koa-redis-cache');
app.use(cache({
routes: ['/chart'],
expire: 3600
}));
// 图表渲染接口
router.get('/chart', async (ctx) => {
const { type = 'png', config } = ctx.query;
const result = await renderService.render(config, type);
ctx.body = result;
});
错误处理机制
完善的错误处理应包含以下层次:
- 参数校验:
function validateOptions(options) {
if (!options || typeof options !== 'object') {
throw new Error('Invalid options format');
}
// 更多校验规则...
}
- 渲染容错:
try {
const result = await renderChart(options);
if (!result) throw new Error('Empty render result');
return result;
} catch (err) {
console.error(`Render failed: ${err.stack}`);
return fallbackImage; // 返回降级图片
}
- 性能监控:
const start = Date.now();
await renderChart(options);
const duration = Date.now() - start;
metrics.timing('chart.render.time', duration);
if (duration > 1000) {
metrics.increment('chart.render.slow');
}
实际应用场景
场景一:PDF报表生成
将图表嵌入PDF文档的完整流程:
const { PDFDocument } = require('pdf-lib');
async function generatePDF() {
const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage([600, 800]);
// 渲染图表
const chartImage = await renderChart(salesOptions);
const pngImage = await pdfDoc.embedPng(chartImage);
// 插入PDF
page.drawImage(pngImage, {
x: 50,
y: 500,
width: 500,
height: 300
});
return pdfDoc.save();
}
场景二:邮件模板嵌入
在邮件中直接嵌入动态图表:
const nodemailer = require('nodemailer');
async function sendReportEmail() {
const chartDataURL = await renderChart(emailOptions);
const transporter = nodemailer.createTransport();
await transporter.sendMail({
html: `<img src="${chartDataURL}" alt="月度报表">`,
// 其他邮件配置...
});
}
性能优化实践
内存管理
// 显式释放资源
function disposeChart(chart) {
chart.dispose();
if (chart.getDom()) {
chart.getDom().remove();
}
}
// 使用WeakMap缓存实例
const chartCache = new WeakMap();
function getCachedChart(options) {
if (!chartCache.has(options)) {
chartCache.set(options, initChart(options));
}
return chartCache.get(options);
}
集群压力测试
使用Artillery进行负载测试:
config:
target: "http://render-service"
phases:
- duration: 60
arrivalRate: 50
scenarios:
- flow:
- get:
url: "/chart?type=svg"
典型优化结果:
- 预热后平均响应时间从 1200ms 降至 400ms
- 内存占用减少 40% 通过实例复用
- 吞吐量提升 3倍 经过横向扩展
版本兼容处理
处理不同ECharts版本的差异:
function adaptOptionForVersion(option, version) {
if (version.startsWith('4.')) {
// v4版本特殊处理
delete option.darkMode;
} else if (version.startsWith('5.')) {
// v5版本特性支持
option.aria = option.aria || {};
}
return option;
}
安全防护措施
- 防止恶意配置:
function sanitizeOptions(options) {
// 限制最大数据量
if (options.dataset && options.dataset.source) {
if (options.dataset.source.length > 1000) {
throw new Error('Data exceeds maximum limit');
}
}
// 过滤危险属性
const forbiddenKeys = ['script', 'javascript'];
Object.keys(options).forEach(key => {
if (forbiddenKeys.includes(key.toLowerCase())) {
delete options[key];
}
});
}
- 访问频率限制:
const rateLimit = require('koa-ratelimit');
app.use(rateLimit({
driver: 'redis',
db: redisClient,
duration: 60000,
max: 100
}));
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:SVG与Canvas渲染选择
下一篇:图表导出与打印