data-export
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseData Export
数据导出
CSV Export (Node.js)
CSV导出(Node.js)
typescript
import { stringify } from 'csv-stringify';
import { pipeline } from 'stream/promises';
// Streaming CSV (handles large datasets)
app.get('/api/export/users.csv', async (req, res) => {
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename="users.csv"');
const cursor = db.collection('users').find().cursor();
const csvStringifier = stringify({
header: true,
columns: ['name', 'email', 'createdAt'],
});
await pipeline(cursor, csvStringifier, res);
});
// Simple in-memory CSV
import { stringify } from 'csv-stringify/sync';
const csv = stringify(rows, { header: true, columns: ['name', 'email', 'amount'] });typescript
import { stringify } from 'csv-stringify';
import { pipeline } from 'stream/promises';
// Streaming CSV (handles large datasets)
app.get('/api/export/users.csv', async (req, res) => {
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename="users.csv"');
const cursor = db.collection('users').find().cursor();
const csvStringifier = stringify({
header: true,
columns: ['name', 'email', 'createdAt'],
});
await pipeline(cursor, csvStringifier, res);
});
// Simple in-memory CSV
import { stringify } from 'csv-stringify/sync';
const csv = stringify(rows, { header: true, columns: ['name', 'email', 'amount'] });Excel Export (ExcelJS — recommended)
Excel导出(ExcelJS — 推荐)
typescript
import ExcelJS from 'exceljs';
app.get('/api/export/report.xlsx', async (req, res) => {
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('Report');
// Headers with styling
sheet.columns = [
{ header: 'Name', key: 'name', width: 25 },
{ header: 'Email', key: 'email', width: 30 },
{ header: 'Amount', key: 'amount', width: 15 },
];
sheet.getRow(1).font = { bold: true };
// Data
const users = await getUsers();
users.forEach((u) => sheet.addRow(u));
// Number formatting
sheet.getColumn('amount').numFmt = '$#,##0.00';
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition', 'attachment; filename="report.xlsx"');
await workbook.xlsx.write(res);
});typescript
import ExcelJS from 'exceljs';
app.get('/api/export/report.xlsx', async (req, res) => {
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('Report');
// Headers with styling
sheet.columns = [
{ header: 'Name', key: 'name', width: 25 },
{ header: 'Email', key: 'email', width: 30 },
{ header: 'Amount', key: 'amount', width: 15 },
];
sheet.getRow(1).font = { bold: true };
// Data
const users = await getUsers();
users.forEach((u) => sheet.addRow(u));
// Number formatting
sheet.getColumn('amount').numFmt = '$#,##0.00';
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition', 'attachment; filename="report.xlsx"');
await workbook.xlsx.write(res);
});Streaming Excel for Large Datasets
针对大型数据集的流式Excel导出
typescript
const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({ stream: res });
const sheet = workbook.addWorksheet('Data');
sheet.columns = [{ header: 'Name', key: 'name' }, { header: 'Value', key: 'value' }];
for await (const row of cursor) {
sheet.addRow(row).commit(); // Flushes row to stream
}
await workbook.commit();typescript
const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({ stream: res });
const sheet = workbook.addWorksheet('Data');
sheet.columns = [{ header: 'Name', key: 'name' }, { header: 'Value', key: 'value' }];
for await (const row of cursor) {
sheet.addRow(row).commit(); // Flushes row to stream
}
await workbook.commit();Frontend CSV Parsing (Papa Parse)
前端CSV解析(Papa Parse)
typescript
import Papa from 'papaparse';
// Parse uploaded CSV
const result = Papa.parse<UserRow>(file, {
header: true,
skipEmptyLines: true,
dynamicTyping: true,
complete: (results) => {
console.log(results.data); // Parsed rows
console.log(results.errors); // Parse errors
},
});
// Generate CSV in browser
const csv = Papa.unparse(data);
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);typescript
import Papa from 'papaparse';
// Parse uploaded CSV
const result = Papa.parse<UserRow>(file, {
header: true,
skipEmptyLines: true,
dynamicTyping: true,
complete: (results) => {
console.log(results.data); // Parsed rows
console.log(results.errors); // Parse errors
},
});
// Generate CSV in browser
const csv = Papa.unparse(data);
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);Python (openpyxl)
Python(openpyxl)
python
from openpyxl import Workbook
from io import BytesIO
def export_excel(data: list[dict]) -> bytes:
wb = Workbook()
ws = wb.active
ws.title = "Report"
headers = list(data[0].keys())
ws.append(headers)
for row in data:
ws.append([row[h] for h in headers])
buffer = BytesIO()
wb.save(buffer)
return buffer.getvalue()python
from openpyxl import Workbook
from io import BytesIO
def export_excel(data: list[dict]) -> bytes:
wb = Workbook()
ws = wb.active
ws.title = "Report"
headers = list(data[0].keys())
ws.append(headers)
for row in data:
ws.append([row[h] for h in headers])
buffer = BytesIO()
wb.save(buffer)
return buffer.getvalue()Anti-Patterns
反模式
| Anti-Pattern | Fix |
|---|---|
| Loading all data into memory | Stream from DB cursor for large exports |
| No Content-Disposition header | Always set for browser download |
| Generating exports in request handler | Use background job for >10K rows |
| No progress indication | Use WebSocket/SSE for large export progress |
| Unescaped CSV values | Use library (csv-stringify, Papa Parse) |
| 反模式 | 修复方案 |
|---|---|
| 将所有数据加载到内存中 | 从数据库游标流式导出大型数据集 |
| 未设置Content-Disposition头 | 浏览器下载时务必设置该头 |
| 在请求处理程序中生成导出文件 | 超过1万行数据时使用后台任务 |
| 无进度提示 | 针对大型导出使用WebSocket/SSE |
| CSV值未转义 | 使用专用库(csv-stringify、Papa Parse) |
Production Checklist
生产环境检查清单
- Streaming for datasets >10K rows
- Background job queue for large exports
- Proper Content-Type and Content-Disposition headers
- Memory limits monitored
- Rate limiting on export endpoints
- Temporary file cleanup if writing to disk
- 数据集超过1万行时使用流式处理
- 大型导出使用后台任务队列
- 设置正确的Content-Type和Content-Disposition头
- 监控内存限制
- 对导出端点进行限流
- 若写入磁盘则清理临时文件