IMS数据导入导出设计

数据的导入导出是企业信息系统的核心功能之一。本文将介绍 IMS 系统中 Excel/CSV 导入导出的完整设计方案。

核心功能需求

  • 多格式支持 - 支持 Excel (.xlsx, .xls) 和 CSV 格式
  • 模板下载 - 提供标准导入模板,用户按模板填写
  • 数据校验 - 格式校验、业务规则校验、重复检测
  • 大文件处理 - 支持万级数据导入,需要进度提示
  • 错误报告 - 导入失败时给出详细错误位置和原因
  • 分批导入 - 大数据量分批入库,防止事务超时

整体架构设计

导入导出系统分为四个模块:

  • 解析层 - 解析 Excel/CSV 文件
  • 校验层 - 数据格式和业务规则校验
  • 处理层 - 数据转换和入库
  • 导出层 - 生成导出文件

导入模板定义

首先定义导入模板的数据结构:

import-template.js
// 导入模板定义
const ImportTemplate = {
    customer: {
        name: '客户导入模板',
        fields: [
            {
                key: 'customer_code',
                label: '客户编码',
                required: true,
                type: 'string',
                pattern: /^[A-Z0-9]{6,10}$/,
                errorMsg: '客户编码必须为6-10位大写字母或数字'
            },
            {
                key: 'customer_name',
                label: '客户名称',
                required: true,
                type: 'string',
                maxLength: 100
            },
            {
                key: 'contact_person',
                label: '联系人',
                required: false,
                type: 'string',
                maxLength: 50
            },
            {
                key: 'phone',
                label: '联系电话',
                required: true,
                type: 'string',
                pattern: /^1[3-9]\d{9}$/
            },
            {
                key: 'status',
                label: '状态',
                required: false,
                type: 'enum',
                options: ['正常', '禁用'],
                default: '正常'
            }
        ]
    }
};

文件解析模块

解析 Excel 和 CSV 文件:

file-parser.js
// 文件解析服务
const xlsx = require('xlsx');

class FileParser {
    // 解析 Excel 文件
    parseExcel(buffer) {
        const workbook = xlsx.read(buffer, { type: 'buffer' });
        const sheetName = workbook.SheetNames[0];
        const sheet = workbook.Sheets[sheetName];
        return xlsx.utils.sheet_to_json(sheet);
    }

    // 解析 CSV 文件
    parseCSV(content) {
        const lines = content.split('\n').filter(l => l.trim());
        if (lines.length < 2) return [];

        const headers = lines[0].split(',').map(h => h.trim());
        const data = [];

        for (let i = 1; i < lines.length; i++) {
            const values = parseCSVLine(lines[i]);
            const row = {};
            headers.forEach((h, idx) => {
                row[h] = values[idx] || '';
            });
            data.push(row);
        }

        return data;
    }

    // 自动检测文件类型并解析
    parse(file) {
        const ext = file.originalname.split('.').pop().toLowerCase();
        switch(ext) {
            case 'xlsx':
            case 'xls':
                return this.parseExcel(file.buffer);
            case 'csv':
                return this.parseCSV(file.buffer.toString('utf-8'));
            default:
                throw new Error('不支持的文件格式');
        }
    }
}

数据校验模块

实现多层次数据校验:

data-validator.js
// 数据校验服务
class DataValidator {
    constructor(template) {
        this.template = template;
        this.errors = [];
    }

    validate(data) {
        this.errors = [];
        data.forEach((row, index) => {
            const rowNum = index + 2;  // 跳过表头
            this.validateRow(row, rowNum);
        });
        return {
            valid: this.errors.length === 0,
            errors: this.errors,
            successCount: data.length - this.errors.filter(e => e.level === 'error').length
        };
    }

    validateRow(row, rowNum) {
        for (const field of this.template.fields) {
            const value = row[field.key];

            // 必填校验
            if (field.required && !value) {
                this.addError(rowNum, field.key, '必填字段不能为空');
                continue;
            }

            if (!value) continue;

            // 类型校验
            if (!this.validateType(value, field.type)) {
                this.addError(rowNum, field.key, `${field.label}格式错误`);
            }

            // 正则校验
            if (field.pattern && !field.pattern.test(value)) {
                this.addError(rowNum, field.key, field.errorMsg || `${field.label}格式不正确`);
            }

            // 长度校验
            if (field.maxLength && value.length > field.maxLength) {
                this.addError(rowNum, field.key, `${field.label}不能超过${field.maxLength}个字符`);
            }

            // 枚举校验
            if (field.options && !field.options.includes(value)) {
                this.addError(rowNum, field.key, `${field.label}必须为:${field.options.join('/')}`);
            }
        }
    }

    validateType(value, type) {
        switch(type) {
            case 'number': return !isNaN(value);
            case 'date': return !isNaN(Date.parse(value));
            default: return true;
        }
    }

    addError(row, field, message) {
        this.errors.push({ row, field, message, level: 'error' });
    }
}

分批导入处理

大文件采用分批导入策略:

batch-importer.js
// 分批导入服务
class BatchImporter {
    constructor(batchSize = 500) {
        this.batchSize = batchSize;
    }

    async import(validData, onProgress) {
        const total = validData.length;
        let successCount = 0;
        let errorCount = 0;

        for (let i = 0; i < total; i += this.batchSize) {
            const batch = validData.slice(i, i + this.batchSize);

            try {
                await this.processBatch(batch);
                successCount += batch.length;
            } catch (e) {
                errorCount += batch.length;
                console.error(`批次导入失败: ${e.message}`);
            }

            // 进度回调
            if (onProgress) {
                const progress = Math.min((i + batch.length) / total * 100, 100);
                onProgress({ progress, successCount, errorCount });
            }
        }

        return { successCount, errorCount };
    }

    async processBatch(batch) {
        // 事务处理,确保批次原子性
        const conn = await db.getConnection();
        await conn.beginTransaction();

        try {
            for (const item of batch) {
                await conn.execute(
                    `INSERT INTO customer (customer_code, customer_name, phone, status) VALUES (?, ?, ?, ?)`,
                    [item.customer_code, item.customer_name, item.phone, item.status || '正常']
                );
            }
            await conn.commit();
        } catch (e) {
            await conn.rollback();
            throw e;
        } finally {
            conn.release();
        }
    }
}

导出功能实现

支持自定义列导出:

exporter.js
// 数据导出服务
class DataExporter {
    async exportToExcel(data, options = {}) {
        const worksheet = xlsx.utils.json_to_sheet(data);

        // 设置列宽
        worksheet['!cols'] = calculateColumnWidth(data);

        const workbook = xlsx.utils.book_new();
        xlsx.utils.book_append_sheet(workbook, worksheet, '数据导出');

        return xlsx.write(workbook, { type: 'buffer', bookType: 'xlsx' });
    }

    async exportToCSV(data) {
        const headers = Object.keys(data[0] || {});
        let csv = headers.join(',') + '\n';

        for (const row of data) {
            const values = headers.map(h => escapeCSVValue(row[h]));
            csv += values.join(',') + '\n';
        }

        return csv;
    }
}

function escapeCSVValue(value) {
    if (value === null || value === undefined) return '';
    const str = String(value);
    if (str.includes(',') || str.includes('"') || str.includes('\n')) {
        return `"${str.replace(/"/g, "")}"`;
    }
    return str;
}

模板下载功能

生成带示例的导入模板:

template-generator.js
// 生成导入模板
function generateTemplate(template) {
    // 表头行
    const headers = template.fields.map(f => f.label);
    const exampleRow = template.fields.map(f => {
        if (f.options) return f.options[0];
        if (f.pattern) return '示例值';
        return '';
    });

    const data = [headers, exampleRow];
    const worksheet = xlsx.utils.aoa_to_sheet(data);

    // 冻结首行
    worksheet['!freeze'] = { topRow: 1 };

    const workbook = xlsx.utils.book_new();
    xlsx.utils.book_append_sheet(workbook, worksheet, '导入模板');

    return xlsx.write(workbook, { type: 'buffer', bookType: 'xlsx' });
}

总结

数据导入导出功能需要重点关注:模板标准化、数据多层次校验、大文件分批处理、错误详细报告。良好的导入导出体验能大幅提升用户使用效率。