eggjs 怎么实现获取账单列表接口并且实现列表数据分页查询功能?

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,高可用系列 2核4GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: eggjs 怎么实现获取账单列表接口并且实现列表数据分页查询功能?

说明


该分页列表查询接口我自己分成三步写的,跟小册上的实现方式不太样,我是通过 mysql 的 limit 分页去实现的。


主要分成三个大的部分逻辑:


  1. 通过查询一个月里的账单所处的是哪一天,全都查出来,形成一个天数列表
  2. 通过天数列表,去查里面每一天的账单数据
  3. 最后统计这个月的所有账单



列表原型要求

可以查看原型,看前端账单需要怎样展示:账单是以时间天数作为一条数据,需要显示每天的账单信息,还有当月总支出,当月总收入,全部类型,月份选择。

bd9604a6d5a8488da65ef690265863bb.png



分析接口需要字段


入参字段有:查询时需要

curPage, // 当前页数 (默认为1)
pageSize, // 一页多少条(默认为5)
typeId, // 类型 'all'就是全部类型
billDate, // 账单日期


需要返回的字段有

data: {
  totalExpense, // 当月支出
  totalIncome, // 当月收入
  dataList: [{
    day, // 日期
  bills: {} // bill 数据表中的每一项账单
  }, ...] // 列表数据
  pageObj: {
    curPage, // 当前页数
  pageSize, // 一页多少条
  totalPage, // 总分页数
  totalRow, // 总条数
  }
}


实现获取账单列表接口

1、在控制层添加一个 list 方法

打开 /controller/bill.js,新增一个 list 方法,里面的大体逻辑


  1. 获取查询参数
  2. 拿到 token 获取用户信息 user_id
  3. 通过 user_id 获取列表分页数据
  4. 组装数据:需要通过日获取一天的账单列表数据
  5. 获取当月总支出、当月总收入、总条数



const moment = require('moment'); // JavaScript 日期处理类库
class BillController extends Controller {
  async list() {
    const { ctx, app } = this;
    try {
      /**
       *  curPage, // 当前页数 (默认为1)
       *  pageSize, // 一页多少条(默认为5)
       *  typeId, // 类型 'all'就是全部类型
       *  billDate, // YYYY-MM 账单日期
       * */ 
      // 1、获取查询参数
      const { curPage = 1, pageSize = 5, typeId = 'all', billDate } = ctx.query;
      console.log('1、获取查询参数',curPage,pageSize,typeId,billDate);
      // 2、拿到 token 获取用户信息 user_id
      const token = ctx.request.header.authorization;
      const decode = await app.jwt.verify(token, app.config.jwt.secret);
      if (!decode) return;
      let user_id = decode.id;
      console.log('2、拿到 token 获取用户信息 user_id',user_id);
      // 3、通过 user_id 获取列表分页数据
      const dayResult = await ctx.service.bill.list(user_id, typeId, billDate, curPage, pageSize);
      const dayList = JSON.parse(JSON.stringify(dayResult));
      console.log('3、通过 user_id 获取当前用户的账单列表',dayResult,dayList);
      // 4、组装数据:需要通过日获取一天的账单列表数据
      let dataList = [];
      for(var i = 0; i < dayList.length; i++) {
        const day = moment(dayList[i].day).format("YYYY-MM-DD");
        const month = moment(dayList[i].day).format("YYYY-MM");
        if(month === billDate) {
          const listResult = await ctx.service.bill.listByDay(user_id, typeId, billDate, day);
          const billsList = JSON.parse(JSON.stringify(listResult));
          console.log(day,listResult,billsList);
          dataList.push({
            day: day,
            bills: billsList
          });
        }
      }
      console.log('4、然后将 list 过滤出月份和类型所对应的账单列表',dataList);
      // 5、获取当月总支出、当月总收入、总条数
      // 获取一个月的有账单的所有天数列表 typeId 写死为all
      const allDayResult = await ctx.service.bill.allList(user_id, 'all', billDate);
      const allDayList = JSON.parse(JSON.stringify(allDayResult));
      // 当月总支出:支付类型:1:支出,2:收入
      let totalExpense = allDayList.reduce((curr, next) => {
        if (next.pay_type === 1) {
          curr += Number(next.amount);
          return curr;
        }
        return curr;
      }, 0).toFixed(2); 
      // 当月总收入:支付类型:1:支出,2:收入
      let totalIncome = allDayList.reduce((curr, next) => {
        if (next.pay_type === 2) {
          curr += Number(next.amount);
          return curr;
        }
        return curr;
      }, 0).toFixed(2);
      // 总条数 需要根据 typeId 去过滤
      const allDayTypeIdResult = await ctx.service.bill.allList(user_id, typeId, billDate);
      const allDayTypeIdList = JSON.parse(JSON.stringify(allDayTypeIdResult));
      let obj = {}; // 用年月日作为 key(YYYY-MM-DD)
      // 天数去重
      let newAllDayTypeIdList = allDayTypeIdList.reduce((item, next) => {
        let nextDate = moment(next.date).format("YYYY-MM-DD");
        obj[nextDate] ? '' : obj[nextDate] = true && item.push(next);
        return item;
      }, []);
      let totalRow = newAllDayTypeIdList.length;
      console.log('5、获取一个月的有账单的天数列表', obj, newAllDayTypeIdList);
      console.log('总条数', totalRow, '当月总支出:',totalExpense, '当月总收入:',totalIncome);
      ctx.body = {
        status: 200,
        desc: '请求成功',
        data: {
          totalExpense, // 当月总支出
          totalIncome, // 当月总收入
          dataList: dataList, // 列表数据
          pageObj: {
            curPage, // 当前页数
            pageSize, // 一页多少条
            totalPage: Math.ceil(totalRow / pageSize), // 总分页数
            totalRow, // 总条数
          }
        }
      }
    } catch (error) {
      console.log(error);
      ctx.body = {
        status: 500,
        desc: '系统错误',
        data: null
      }
    }
  }
}


2、在服务层添加几个方法获取sql数据

这里需要会一点 sql 语句,分页的参考资料在文章后面。

获取一个月的列表分页数据:主要就是通过日期去去重找到哪些天有账单的日期列表数据,然后通过降序分页处理。

async list(user_id, type_id, month, curPage, pageSize) {
    const { app } = this;
    try {
      // 去重找到日的数据列表降序分页
      let type_id_str = type_id === "all" || type_id === "" ? "" : " and type_id = " + type_id;
      let date_str = " and DATE_FORMAT(b.date,'%Y-%m') = '" + month + "'";
      let sql = "select distinct STR_TO_DATE(b.date,'%Y-%m-%d') day from `kaimo-cost`.bill b where user_id = "+ user_id+ type_id_str + date_str + " order by day desc limit "+(curPage-1)*pageSize+", "+pageSize;
      const result = await app.mysql.query(sql);
      return result;
    } catch (error) {
      console.log(error);
      return null;
    }
  }


获取某一天的账单列表数据:主要加入一些查询条件

async listByDay(user_id, type_id, month, day) {
  const { app } = this;
  try {
    const QUERY_STR = 'id, pay_type, amount, date, type_id, type_name, remark';
    let type_id_str = type_id === "all" || type_id === "" ? "" : " and type_id = " + type_id;
    let date_str = " and STR_TO_DATE(b.date,'%Y-%m-%d') = '" + day + "'" + " and DATE_FORMAT(b.date,'%Y-%m') = '" + month + "'";
    let sql = "select " + QUERY_STR + " from `kaimo-cost`.bill b where user_id = " + user_id + date_str + type_id_str;
    console.log('通过日获取列表数据', sql);
    const result = await app.mysql.query(sql);
    return result;
  } catch (error) {
    console.log(error);
    return null;
  }
}

获取某个月的所有账单数据

async allList(user_id, type_id, month) {
  const { app } = this;
  try {
    let type_id_str = type_id === "all" || type_id === "" ? "" : " and type_id = " + type_id;
    let sql = "select * from `kaimo-cost`.bill b where user_id = "+ user_id + " and DATE_FORMAT(b.date,'%Y-%m') = '" + month + "'" + type_id_str;
    console.log('获取月的所有账单数据', sql);
    const result = await app.mysql.query(sql);
    return result;
  } catch (error) {
    console.log(error);
    return null;
  }
}


3、配置路由

// 获取账单列表
router.get('/api/bill/list', verify_token, controller.bill.list);




测试获取账单列表接口


1、在 apifox 里新建接口

如下图所示:

c2a5936725c246a2a022cf94e879ec6d.png



2、伪造列表数据

我们可以将下面的 sql 在 DBeaver 执行,插入这些数据。

INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(1, 1, '33.00', '2022-01-01 22:00:00', 2, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(2, 1, '3', '2022-01-01 19:00:00', 2, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(3, 1, '13', '2022-01-02 22:00:00', 2, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(4, 2, '56', '2022-01-03 18:00:00', 2, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(5, 2, '42', '2022-01-05 12:07:00', 1, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(6, 2, '11', '2022-01-06 22:00:00', 2, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(7, 1, '3', '2022-01-07 22:00:00', 5, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(8, 1, '33', '2022-02-01 22:00:00', 14, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(9, 1, '3', '2022-02-02 22:00:00', 14, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(10, 2, '2', '2022-02-03 22:00:00', 2, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(11, 2, '46.9', '2022-02-04 22:00:00', 2, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(12, 2, '27', '2022-02-05 20:00:00', 1, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(13, 2, '4', '2022-02-05 22:00:00', 1, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(14, 2, '7', '2022-02-07 22:00:00', 4, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(15, 1, '11', '2022-02-08 22:00:00', 4, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(16, 2, '0', '2022-02-09 22:00:00', 4, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(17, 2, '16.8', '2022-02-10 22:00:00', 6, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(18, 1, '143', '2022-02-11 22:00:00', 6, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(19, 2, '70', '2022-02-12 22:00:00', 6, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(20, 1, '44', '2022-02-13 22:00:00', 8, '1', 5, '测试');
INSERT INTO `kaimo-cost`.bill
(id, pay_type, amount, `date`, type_id, type_name, user_id, remark)
VALUES(21, 1, '3', '2022-02-11 19:00:00', 5, '1', 5, '测试');


3、开始运行测试

登录之后,把 token 加入 Authorization 参数里

9301f6f072704f549103295666ffebaa.png

填入下面的参数,我们测试一下

12eb256759794b06ad13c29b54ab3ebc.png


结果如下:

{
    "status": 200,
    "desc": "请求成功",
    "data": {
        "totalExpense": "237.00",
        "totalIncome": "173.70",
        "dataList": [
            {
                "day": "2022-02-02",
                "bills": [
                    {
                        "id": 9,
                        "pay_type": 1,
                        "amount": "3",
                        "date": "2022-02-02 22:00:00",
                        "type_id": 14,
                        "type_name": "1",
                        "remark": "测试"
                    }
                ]
            },
            {
                "day": "2022-02-01",
                "bills": [
                    {
                        "id": 8,
                        "pay_type": 1,
                        "amount": "33",
                        "date": "2022-02-01 22:00:00",
                        "type_id": 14,
                        "type_name": "1",
                        "remark": "测试"
                    }
                ]
            }
        ],
        "pageObj": {
            "curPage": "2",
            "pageSize": "10",
            "totalPage": 2,
            "totalRow": 12
        }
    }
}


8885e3cf62bc44049524a2fcfbe6b9bb.png

其他的条件可以自己去测试一下,如有错误也在所难免,还请指正。



注意

在编写分页逻辑的过程中,我们可能会遇到一些问题,这个时候,我需要清楚是 sql 的问题,还是控制层逻辑问题,我们可以在关键的地方打印一些日志,比如查询的 sql 是什么,方便我们快速粘贴到 DBeaver 里去查询。


a8ae7f8c777544b291af38f8e79a5d99.png


c040c09a115948c68b81d9fae838408b.png




参考资料



JavaScript 日期处理类库 Moment.js


下面的接口实现需要用到 moment.js

例子:


> moment("2013-12-24 14:30:00").format("YYYY-MM-DD")
> '2013-12-24'
> moment("2013-12-24 14:30:00").format("YYYY-MM")
> '2013-12'
> moment('2022-03-08 19:15:41').format("x")
> '1646738141000'


limit 分页公式:

(1)limit分页公式:curPage是当前第几页;pageSize是一页多少条记录

limit (curPage-1)*pageSize,pageSize;


(2)用的地方:sql语句中

select * from student limit(curPage-1)*pageSize,pageSize;


日期格式函数:

STR_TO_DATE(b.date,'%Y-%m-%d') 输出年月日
DATE_FORMAT(b.date,'%Y-%m') 输出年月



相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
11月前
|
算法
基于大爆炸优化算法的PID控制器参数寻优matlab仿真
本研究基于大爆炸优化算法对PID控制器参数进行寻优,并通过Matlab仿真对比优化前后PID控制效果。使用MATLAB2022a实现核心程序,展示了算法迭代过程及最优PID参数的求解。大爆炸优化算法通过模拟宇宙大爆炸和大收缩过程,在搜索空间中迭代寻找全局最优解,特别适用于PID参数优化,提升控制系统性能。
|
机器学习/深度学习 数据可视化 算法框架/工具
使用Python实现深度学习模型:视频处理与动作识别
【7月更文挑战第16天】 使用Python实现深度学习模型:视频处理与动作识别
786 17
|
IDE PHP 开发工具
「Python入门」python环境搭建及VScode使用python运行方式
**Python 概述与环境搭建摘要** Python是一种解释型、面向对象、交互式的脚本语言,以其简单易学和丰富库著称。安装Python时,推荐在Windows上选择.exe安装程序,记得勾选“Add Python to PATH”。安装完成后,通过环境变量配置确保Python可被系统识别。验证安装成功,可在CMD中输入`python --version`。Visual Studio Code (VScode)是流行的Python IDE,安装Python插件并选择解释器后,可直接在VScode内编写和运行Python代码。
596 0
「Python入门」python环境搭建及VScode使用python运行方式
|
搜索推荐 关系型数据库 MySQL
MySQL 模糊查询新纪元:超越 LIKE+% 的高效探索
在数据库的日常操作中,模糊查询是一项不可或缺的功能,它允许我们根据不完全匹配的关键字来检索数据。传统上,MySQL 使用 LIKE 关键字配合 % 通配符来实现这一功能,虽然灵活但性能上往往不尽如人意,尤其是在处理大型数据集时。今天,我们将一起探索几种超越 LIKE+% 的模糊查询技术,以提升查询效率与用户体验。
712 2
|
Java
Unicode编码和中文互转(JAVA实现)
Unicode编码和中文互转(JAVA实现)
622 1
|
Web App开发
笔记本电脑能连接WiFi但浏览器无法打开网页的解决办法
笔记本电脑能连接WiFi但浏览器无法打开网页的解决办法
16427 0
笔记本电脑能连接WiFi但浏览器无法打开网页的解决办法
|
监控 前端开发 JavaScript
常见的前端监控性能指标
常见的前端监控性能指标
458 0
|
NoSQL Java 关系型数据库
一个完整的外卖系统
一个完整的外卖系统
262 0
|
消息中间件 Java Apache
RocketMQ快速入门 1
RocketMQ快速入门
338 0
|
算法
数据包络分析(DEA)——BCC模型
数据包络分析(DEA)——BCC模型
2873 0