背景
最近做需求的时候遇到了多语言渲染页面的场景
产品给的是一个 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"
                    }
                }
            ]
        }
    ]
}