【Node.js】基于 Koa 将 Xlsx 文件转化为数据

背景

最近做需求的时候遇到了多语言渲染页面的场景

产品给的是一个 excel 文件,如果将 excel 文件手动录入到代码中比较费时间

本文就是针对这样的场景:基于 node-xlsx 库,实现了一个简单的 xlsx 文件处理工具


准备

数据准备

首先,我们需要准备一个 xlsx 文件作为上传数据的文件

搭建 Koa 项目

首先我们需要初始化一个 Koa 项目

1. 安装 koa-generator

shell 复制代码
npm install -g koa-generator

2. 使用 koa2 命令初始化项目

shell 复制代码
# 普通初始化
koa2 xlsx-handler

# 初始化并指定 ejs 作为模板引擎
koa2 -e xlsx-handler

安装对应的依赖

1. 安装 node-xlsx

shell 复制代码
npm install -S node-xlsx

2. 安装 @koa/multer 处理接口中间件

shell 复制代码
npm install -S @koa/multer multer

对 koa2 项目进行改造

根据 MVC 思想,我们需要将原本的文件逻辑进行拆分

shell 复制代码
app
├── bin
│   └── www
├── controllers
│   └── IndexController.js
│   └── XlsxController.js
├── routes
│   └── index.js
│   └── xlsx.js
├── views
│   └── common
│   └── index.ejs
├── public
│   └── css
│   └── js
│   └── img
├── app.js
├── package.json

实现

1. 接口定义

routes/xlsx.js 中定义接口

js 复制代码
const Router = require('koa-router');
const router = new Router();

const upload = require('@koa/multer')();

const xlsxController = require('../controllers/XlsxController');

router.prefix('/xlsx');

router.post('/upload', upload.fields([{ name: 'file', maxCount: 1 }]), xlsxController.upload);

module.exports = router;

2. 定义控制器

js 复制代码
// controllers/XlsxController.js

const jsonDTO = require('../entities/json');
const { parseXlsxToArray } = require('../libs/xlsx');

exports.upload = async (ctx, next) => {
  const file = ctx.files.file?.[0] ?? ctx.files.file;
  const data = parseXlsxToArray(file);
  ctx.body = jsonDTO.success(data);
};

3. 核心方法实现

js 复制代码
const nodeXlsx = require('node-xlsx');

exports.parseXlsxToArray = (file) => {
  const parsetXlsxResult = exports.parseXlsx(file);

  return Object.keys(parsetXlsxResult).reduce((res, key) => {
    res.push({
      sheetName: key,
      data: Object.keys(parsetXlsxResult[key]).map((subKey) => {
        // const langData = parsetXlsxResult[key][subKey];
        return {
          title: subKey,
          data: parsetXlsxResult[key][subKey],
        };
      }),
    });
    return res;
  }, []);
};

exports.parseXlsx = (file) => {
  const workSheetsFromBuffer = nodeXlsx.parse(file.buffer);

  return workSheetsFromBuffer.reduce((res, item, index) => {
    const { name: sheetName, data } = item;

    res[sheetName] = {};

    const [headers, ...rows] = data;

    headers
      .filter((_, i) => i > 1)
      .forEach((header, headerIndex) => {
        res[sheetName][header] = {};

        for (let row of rows) {
          const [key1, key2, ...values] = row;
          const rowKey = `${key1}.${key2}`;
          res[sheetName][header][rowKey] = values[headerIndex] ?? '';
        }
      });

    return res;
  }, {});
};

4. 统一响应体

js 复制代码
// entities/json.js

exports.success = (data) => {
  return {
    code: 0,
    message: 'ok',
    data,
  };
};

/**
 * @typedef {{ code: number, message: string, data: any }} ResponseBody
 * 失败响应
 * @type {(() => ResponseBody) | ((message: string) => ResponseBody) | ((code: number, message: string) => ResponseBody) | (<T> (code: number, message: string, data: T) => ResponseBody)}
 * @param {*} args 
 * @returns 
 */
exports.fail = (...args) => {
  if (args.length === 0) {
    return {
      code: 1,
      message: 'fail',
      data: null,
    }
  }
  if (args.length === 1) {
    return {
      code: 1,
      message: args[0],
      data: null,
    }
  }
  if (args.length === 2) {
    return {
      code: args[0],
      message: args[1],
      data: null,
    }
  }
  if (args.length === 3) {
    return {
      code: args[0],
      message: args[1],
      data: args[2],
    }
  }
};

5. 启动服务 & 测试接口

shell 复制代码
npm run dev

测试接口

使用 Postman 或其他工具测试接口

  • 接口地址:http://localhost:3000/xlsx/upload
  • 请求方法:POST
  • 上传文件:选择 xlsx 文件
  • 响应体:根据 entities/json.js 中的定义
http 复制代码
### 请求上传信息
POST http://localhost:3000/xlsx/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="locale.xlsx"
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

< ./locale.xlsx
------WebKitFormBoundary7MA4YWxkTrZu0gW--

响应数据示例

json 复制代码
{
    "code": 0,
    "message": "ok",
    "data": [
        {
            "sheetName": "当前显示的语言",
            "data": [
                {
                    "title": "简体中文/zh-CN",
                    "data": {
                        "USER_MANAGEMENT.USER_STATUS": "用户状态",
                        "USER_MANAGEMENT.DELETE_USER_SUCCESS": "删除用户提交成功",
                        "USER_MANAGEMENT.TIMESPAN": "自定义存期",
                        "USER_MANAGEMENT.EMAIL": "邮箱",
                        "USER_MANAGEMENT.RATE": "利率",
                        "USER_MANAGEMENT.FIXED_DATE": "固定存款期",
                        "USER_MANAGEMENT.FIXED_DATE_TYPE": "固定存期类型",
                        "USER_MANAGEMENT.AMOUNT": "存款额",
                        "USER_MANAGEMENT.CONTACT_TYPE": "存额类型",
                        "USER_MANAGEMENT.ENT_NAME": "客户名称",
                        "USER_MANAGEMENT.ACCOUNT_NO": "账户号码"
                    }
                },
                {
                    "title": "繁体中文/zh-XG",
                    "data": {
                        "USER_MANAGEMENT.USER_STATUS": "狀態",
                        "USER_MANAGEMENT.DELETE_USER_SUCCESS": "成功",
                        "USER_MANAGEMENT.TIMESPAN": "自定義存期",
                        "USER_MANAGEMENT.EMAIL": "郵箱",
                        "USER_MANAGEMENT.RATE": "利率",
                        "USER_MANAGEMENT.FIXED_DATE": "固定存款期",
                        "USER_MANAGEMENT.FIXED_DATE_TYPE": "固定存期類型",
                        "USER_MANAGEMENT.AMOUNT": "存款額",
                        "USER_MANAGEMENT.CONTACT_TYPE": "存額類型",
                        "USER_MANAGEMENT.ENT_NAME": "客戶名稱",
                        "USER_MANAGEMENT.ACCOUNT_NO": "賬戶號碼"
                    }
                },
                {
                    "title": "英文/en",
                    "data": {
                        "USER_MANAGEMENT.USER_STATUS": "state",
                        "USER_MANAGEMENT.DELETE_USER_SUCCESS": "success",
                        "USER_MANAGEMENT.TIMESPAN": "Custom Deposit Period",
                        "USER_MANAGEMENT.EMAIL": "Mail",
                        "USER_MANAGEMENT.RATE": "interest rate",
                        "USER_MANAGEMENT.FIXED_DATE": "Fixed deposit period",
                        "USER_MANAGEMENT.FIXED_DATE_TYPE": "Fixed Deposit Type",
                        "USER_MANAGEMENT.AMOUNT": "Deposit amount",
                        "USER_MANAGEMENT.CONTACT_TYPE": "Deposit type",
                        "USER_MANAGEMENT.ENT_NAME": "client's name",
                        "USER_MANAGEMENT.ACCOUNT_NO": "account number"
                    }
                }
            ]
        }
    ]
}
相关推荐
子兮曰1 天前
深度解析Proxy与目标对象(definiteObject):原理、特性与10个实战案例
前端·javascript·node.js
子兮曰2 天前
浏览器与 Node.js 全局变量体系详解:从 window 到 global 的核心差异
前端·javascript·node.js
汤姆Tom3 天前
Node.js 版本管理、NPM 命令、与 NVM 完全指南
前端·npm·node.js
RoyLin4 天前
TypeScript设计模式:原型模式
前端·后端·node.js
葡萄城技术团队4 天前
从100秒到10秒的性能优化,你真的掌握 Excel 的使用技巧了吗?
excel
RoyLin4 天前
TypeScript设计模式:单例模式
前端·后端·node.js
RoyLin4 天前
TypeScript设计模式:工厂方法模式
前端·后端·node.js
RoyLin4 天前
TypeScript设计模式:模板方法模式
前端·后端·node.js
RoyLin5 天前
TypeScript设计模式:适配器模式
前端·后端·node.js