针对阿里开源nodejs框架 Egg.js 的一些封装

2 篇文章 0 订阅
2 篇文章 0 订阅

 

1、配置项扩展

通过application.js扩展app属性,达到扩展配置项的目的,一般扩展 apiUrl、loginFailCount、loginFailLockSeconds。

config.default.js

exports.keys = 'my-cookie-secret-key';
exports.view = {
  defaultViewEngine: 'nunjucks',
  defaultExtension: '.html',
  mapping: {
    '.html': 'nunjucks'
  },
};
exports.apiUrl="http://127.0.0.1:9080";//默认接口地址
exports.loginFailCount=3;//允许失败登录次数,默认3次
exports.loginFailLockSeconds=600;//默认多次登录失败后锁定时间(秒)

2、Router扩展

通过application.js扩展app方法parseRouters,简化路由配置。

约定:

  1. pre为controller目录下 js的路径数组,例 :/controller/sys/user.js   对应的pre 为 ['sys','user'];methods为js包含的方法。
  2. methods中以to开头的统一为跳转页面请求,统一采用get方式;不带to的统一为ajax请求,统一采用post方式。
const routers = [
  { pre: ["sys", "role"], methods: ["toPage", "page", "toAdd", "add", "toUpdate", "update", "delete", "toAuth", "auth", "menu", "list"] },
  { pre: ["sys", "dict"], methods: ["toPage", "page", "toAdd", "add", "toUpdate", "update", "delete"] },
  { pre: ["sys", "dict", "item"], methods: ["toPage", "page", "toAdd", "add", "toUpdate", "update", "delete"] },
  { pre: ["sys", "menu"], methods: ["toPage", "page", "toAdd", "add", "toUpdate", "update", "delete", "tree"] },
];

module.exports = app => {
  app.get('/', 'index.toLogin');
  app.get('/toLogin', 'index.toLogin');
  app.get('/toIndex', 'index.index');
  app.post('/login', 'index.login');
  app.get('/logout', 'index.logout');
  app.get('/home', 'index.home');
  app.get('/toPassword', 'index.toPassword');
  app.post('/password', 'index.password');
  app.post('/file/upload', 'file.upload');
  app.post('/file/img', 'file.img');
  app.parseRouters(routers);
};

3、接口请求封装

Egg原生的接口请求采用curl,用起来比较繁琐,结合实际的应用场景,我们在helper.js中封装了三个方法:post、postObj、postPage。

post

post是最原始的封装。调用服务层接口都是采用post方式请求json。当url中包含不包含http时,实际请求地址会自动添加配置的apiUrl。

/app/extend/helper.js 中扩展方法 post

async post(url,req){
    if(url.indexOf('http')==-1){
        return this.ctx.curl(this.app.apiUrl+url, {method: 'POST',data:req,dataType:'json'});
    }else{
        return this.ctx.curl(url, {method: 'POST',data:req,dataType:'json'});
    }
}

controller中使用

async toUpdate(ctx) {
    const result = await ctx.helper.post('/check/template/get',{id:ctx.query.id});//利用post请求数据
    await ctx.render('pages/check/template-add',result.data.data);//使用请求回的数据渲染页面
}

postObj

postObj是对post的进一步封装,请求接口数据,并把数据返回给前台。

主要应用场景:新增、修改、删除、查询list等

  /**
   * 请求后台接口
   * @param {*} url  接口地址:相对地址或者http开头完整地址 
   * @param {*} req  请求数据
   */
  async postObj(url, req) {
    const res = await this.post(url, req);
    this.resObj(res);
  }

postPage

postPage用来处理分页数据请求。

自动解析请求内的layui分页参数page和limit,转换为后台服务mybatis分页插件需要的参数pageNum和pageSize。

对分页参数做简单验证。

将请求的结果解析为layui数据表格需要的数据格式,并返回给前台。

  /**
   * 请求分页数据,并校验页数和每页数据条数
   * @param {*} url 接口地址:相对地址或者http开头完整地址 
   * @param {*} req 请求数据
   */
  async postPage(url, req) {
    req.pageNum = this.ctx.request.body.page;
    req.pageSize = this.ctx.request.body.limit;
    if (req.pageNum && req.pageSize) {
      if (req.pageNum > 0 && req.pageSize <= 100) {
        const res = await this.post(url, req);
        this.resPage(res);
        return true;
      } else {
        msgFail('分页参数错误');
        return false;
      }
    }
  }

4、Excel文件导出封装

在后台管理系统中Excel导出是很常见的功能。这里我们借助nodejs模块exceljs和fs封装了方法excel。


  /**
   * 请求接口得到数据并生成excel
   *  支持复杂表头(m1:合并单元格左上坐标;m2:合并单元格右下坐标)
   *  支持合计行  totalRowText totalRow
   *  支持数据自定义 func
   *  支持数据字典
   *  支持日期
   * @param {string} url 接口地址:相对地址或者http开头完整地址 
   * @param {object} req 请求数据
   * @param {Array} headers excel标题栏
   * @param {string} name 文件名称
   * @param {function} func 数据自定义函数
   */
  async excelNew(url, req, headers, name, func) {
    let columns = [];//exceljs要求的columns
    let hjRow = {};//合计行
    let titleRows = headers.length;//标题栏行数

    //处理表头
    for (let i = 0; i < titleRows; i++) {
      let row = headers[i];
      for (let j = 0, rlen = row.length; j < rlen; j++) {
        let col = row[j];
        let { f, t, w = 15 } = col;
        if (!f) continue;//不存在f则跳过

        if (col.totalRow) hjRow[f] = true;
        if (col.totalRowText) hjRow[f] = col.totalRowText;
        col.style = { alignment: { vertical: 'middle', horizontal: 'center' } };
        col.header = t;
        col.key = f;
        col.width = w;
        columns.push(col);
      }
    }

    const result = await this.post(url, req);//请求数据
    let data = result.data;
    if (func) data = func(data);

    //处理合计行
    if (JSON.stringify(hjRow) != "{}") {
      let tr = {};
      for (let i = 0, len = data.data.length; i < len; i++) {
        let item = data.data[i];
        for (let key in item) {
          if (hjRow[key] === true) {
            tr[key] = (tr[key] || 0) + item[key];
            continue;
          }
          tr[key] = hjRow[key] || '';
        }
      }
      data.data.push(tr);
    }

    let workbook = new Excel.Workbook();
    let sheet = workbook.addWorksheet('My Sheet', { views: [{ xSplit: 1, ySplit: 1 }] });
    sheet.columns = columns;
    sheet.addRows(data.data);

    //处理复杂表头
    if (titleRows > 1) {
      for (let i = 1; i < titleRows; i++)  sheet.spliceRows(1, 0, []);//头部插入空行

      for (let i = 0; i < titleRows; i++) {
        let row = headers[i];
        for (let j = 0, rlen = row.length; j < rlen; j++) {
          let col = row[j];
          if (!col.m1) continue;

          sheet.getCell(col.m1).value = col.t;
          sheet.mergeCells(col.m1 + ":" + col.m2);
        }
      }
    }

    //处理样式、日期、字典项
    let that = this;
    sheet.eachRow(function (row, rowNumber) {
      //设置行高
      row.height = 25;
      
      row.eachCell({ includeEmpty: true }, function (cell, colNumber) {
        //设置边框 黑色 细实线
        let top = left = bottom = right = { style: 'thin', color: { argb: '000000' } };
        cell.border = { top, left, bottom, right };

        //设置标题部分为粗体
        if (rowNumber <= titleRows) { cell.font = { bold: true };  return; }

        //处理数据项里面的日期和字典
        let {type,dict} = columns[colNumber - 1];
        if (type && (cell.value || cell.value == 0)) return;//非日期、字典或值为空的直接返回
        switch(type){
          case 'date': cell.value = that.parseDate(cell.value);break;
          case 'dict': cell.value = that.parseDict(cell.value.toString(), dict);break;
        }

      });
    });

    this.ctx.set('Content-Type', 'application/vnd.openxmlformats');
    this.ctx.set('Content-Disposition', "attachment;filename*=UTF-8' '" + encodeURIComponent(name) + '.xlsx');
    this.ctx.body = await workbook.xlsx.writeBuffer();
  }

controller内方法示例,具体可参考demo项目用户导出。

async excel(ctx) {
    let req = ctx.helper.data(['strBeginTime', 'strEndTime', 'deptId']);
    req.deptId = req.deptId || ctx.session.user.deptId;

    let headers = [[
      { t: '单位名称',f: 'deptName', w: 20, m1:'A1',m2:'A3',totalRowText: '合计'},
      { t: '办理身份证证件',m1:'B1',m2:'M1'},
      { t: '临时身份证证件', m1:'N1',m2:'O1' },
      { t: '总计', m1:'P1',m2:'R2' }
    ], [
      { t: '申领', m1:'B2',m2:'D2' },
      { t: '换领', m1:'E2',m2:'G2' },
      { t: '补领', m1:'H2',m2:'J2' },
      { t: '小计', m1:'K2',m2:'M2' },
      { t: '临时身份证', m1:'N2',m2:'O2' }
    ], [
      { t: '本地人数', f: 'slbdCount', totalRow: true },
      { t: '异地人数', f: 'slydCount', totalRow: true },
      { t: '金额', f: 'slJe', totalRow: true },
      { t: '本地人数', f: 'hlbdCount', totalRow: true },
      { t: '异地人数', f: 'hlydCount', totalRow: true },
      { t: '金额', f: 'hlJe', totalRow: true },
      { t: '本地人数', f: 'blbdCount', totalRow: true },
      { t: '异地人数', f: 'blydCount', totalRow: true },
      { t: '金额', f: 'blJe', totalRow: true },
      { t: '本地人数', f: 'xj_bdrs', totalRow: true },
      { t: '异地人数', f: 'xj_ydrs', totalRow: true },
      { t: '金额', f: 'xj_je', totalRow: true },
      { t: '人数', f: 'lsCount', totalRow: true },
      { t: '金额', f: 'lsJe', totalRow: true },
      { t: '本地人数', f: 'hj_bdrs', totalRow: true },
      { t: '异地人数', f: 'hj_ydrs', totalRow: true },
      { t: '金额', f: 'hj_je', totalRow: true }
    ]];
    await ctx.helper.excelNew('/bill/querySfzbb', req, headers, '身份证受理统计',function(res){
        for (let i = 0, len = res.data.length; i < len; i++) {
          let r = res.data[i];
          r.xj_bdrs = r.slbdCount + r.hlbdCount + r.blbdCount;
          r.xj_ydrs = r.slydCount + r.hlydCount + r.blydCount;
          r.xj_je = r.slJe + r.hlJe + r.blJe;
          r.hj_bdrs = r.slbdCount + r.hlbdCount + r.blbdCount + r.lsCount;
          r.hj_ydrs = r.slydCount + r.hlydCount + r.blydCount;
          r.hj_je = r.slJe + r.hlJe + r.blJe + r.lsJe;
      }
      return res;
    });
  }

字典项处理  type:'dict',dict:'{{字典项名称}}'

时间处理     type:'date'

5、字典项处理

数据字典的加载和更新

我们通过app.js在项目启动的时候就加载数据字典到app.locals,然后通过定时任务UpdateCache.js定时更新。

数据字典的使用

  • 后台使用

1、页面模板中渲染下拉框

 

2、excel导出时转换字典项

 

  • 前台使用

前台主要用在转换数据表格分页数据,我们在aosCore中封装了parseDict方法。

6、controller层参数验证扩展和封装

  • app.js 中扩展Egg参数验证
//扩展校验规则
app.validator.addRule('uid', (rule, value) => {
    if(value == null || value.length !=32){
        return "必须为32位";
    }
});

app.validator.addRule('sfzh', (rule, value) => {
    if(!/^[1-9][0-9]{5}([1][9][0-9]{2}|[2][0][0|1][0-9])([0][1-9]|[1][0|1|2])([0][1-9]|[1|2][0-9]|[3][0|1])[0-9]{3}([0-9]|[X])$/.test(value)){
        return "不合法的身份证号";
    }
});

app.validator.addRule('name', (rule, value) => {
    if(!/^[\u4E00-\u9FA5]{2,5}$/.test(value)){
        return "不合法的名称";
    }
});
  • helper中封装参数验证,减少大量代码

if (!ctx.helper.check(insertRule, data)) return;//参数验证

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值