说明
该分页列表查询接口我自己分成三步写的,跟小册上的实现方式不太样,我是通过 mysql 的 limit 分页去实现的。
主要分成三个大的部分逻辑:
- 通过查询一个月里的账单所处的是哪一天,全都查出来,形成一个天数列表
- 通过天数列表,去查里面每一天的账单数据
- 最后统计这个月的所有账单
列表原型要求
可以查看原型,看前端账单需要怎样展示:账单是以时间天数作为一条数据,需要显示每天的账单信息,还有当月总支出,当月总收入,全部类型,月份选择。
分析接口需要字段
入参字段有:查询时需要
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 方法,里面的大体逻辑
- 获取查询参数
- 拿到 token 获取用户信息 user_id
- 通过 user_id 获取列表分页数据
- 组装数据:需要通过日获取一天的账单列表数据
- 获取当月总支出、当月总收入、总条数
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 里新建接口
如下图所示:
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
参数里
填入下面的参数,我们测试一下
结果如下:
{
"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
}
}
}
其他的条件可以自己去测试一下,如有错误也在所难免,还请指正。
注意
在编写分页逻辑的过程中,我们可能会遇到一些问题,这个时候,我需要清楚是 sql 的问题,还是控制层逻辑问题,我们可以在关键的地方打印一些日志,比如查询的 sql 是什么,方便我们快速粘贴到 DBeaver 里去查询。
参考资料
- JavaScript 日期处理类库【Moment.js】:http://momentjs.cn/docs/
- Mysql实现分页
- mysql函数date(),date_format()和str_to_date()
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')