【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"
                    }
                }
            ]
        }
    ]
}
相关推荐
xiaofeichaichai9 小时前
Webpack
前端·webpack·node.js
Python私教12 小时前
把开源 Agent 打包成"解压双击即用"的 Windows 便携包:一条命令的完整实现
node.js
没事别瞎琢磨14 小时前
十一、审计与 Run Session——每一步操作都被记录
人工智能·node.js
没事别瞎琢磨14 小时前
十六、AgentSandbox——把所有模块串起来的编排类
人工智能·node.js
没事别瞎琢磨14 小时前
十二、网络代理与白名单规则引擎
人工智能·node.js
没事别瞎琢磨14 小时前
十四、Git Worktree 隔离执行
人工智能·node.js
没事别瞎琢磨16 小时前
十、统一 Runner 入口——能力检测与模式回退
人工智能·node.js
没事别瞎琢磨16 小时前
八、环境隔离——构建安全的子进程环境
人工智能·node.js
没事别瞎琢磨17 小时前
六、输出捕获与截断
人工智能·node.js
没事别瞎琢磨17 小时前
七、敏感路径预检——Protected Paths
人工智能·node.js