4. 活期存款账户余额查询与明细
任务目标
实现活期存款账户的实时余额查询与交易明细分页展示。核心要求:余额由交易流水实时汇总得出(不依赖单独余额字段,避免不一致);明细按时间倒序分页,返回当前页数据与分页元信息。
数据模型与核心逻辑
账户表存开户信息,流水表存每笔交易(正数入账、负数出账)。余额 = 期初余额 + 所有流水金额之和。分页用 page(从1开始)与 pageSize,按 created_at DESC, id DESC 排序保证稳定。
完整实现(Node.js + SQLite)
依赖:npm i better-sqlite3。以下单文件可直接运行,含建表、造数、查询与断言验证。
const Database = require('better-sqlite3');
const db = new Database(':memory:');
db.exec(`
CREATE TABLE account (
id INTEGER PRIMARY KEY,
owner TEXT NOT NULL,
opening_balance INTEGER NOT NULL DEFAULT 0 -- 单位:分
);
CREATE TABLE txn (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL,
amount INTEGER NOT NULL, -- 正入账 负出账, 单位:分
type TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY(account_id) REFERENCES account(id)
);
CREATE INDEX idx_txn_acc_time ON txn(account_id, created_at DESC, id DESC);
`);
// 造数
db.prepare(`INSERT INTO account(id,owner,opening_balance) VALUES (1,'张三',100000)`).run();
const ins = db.prepare(`INSERT INTO txn(account_id,amount,type,created_at) VALUES (?,?,?,?)`);
const rows = [
[1, 50000, 'deposit', '2024-05-01T09:00:00'],
[1, -20000, 'withdraw', '2024-05-02T10:30:00'],
[1, 30000, 'deposit', '2024-05-03T14:15:00'],
[1, -15000, 'withdraw', '2024-05-04T08:45:00'],
[1, 10000, 'deposit', '2024-05-05T16:20:00'],
];
for (const r of rows) ins.run(...r);
// 余额查询: 期初 + 流水汇总
function getBalance(accountId) {
const acc = db.prepare(`SELECT opening_balance FROM account WHERE id=?`).get(accountId);
if (!acc) throw new Error('账户不存在');
const { sum } = db.prepare(`SELECT COALESCE(SUM(amount),0) AS sum FROM txn WHERE account_id=?`).get(accountId);
return acc.opening_balance + sum;
}
// 明细分页: 时间倒序
function getStatement(accountId, page = 1, pageSize = 3) {
if (page < 1 || pageSize < 1) throw new Error('分页参数非法');
const { total } = db.prepare(`SELECT COUNT(*) AS total FROM txn WHERE account_id=?`).get(accountId);
const offset = (page - 1) * pageSize;
const items = db.prepare(
`SELECT id, amount, type, created_at AS createdAt FROM txn
WHERE account_id=? ORDER BY created_at DESC, id DESC LIMIT ? OFFSET ?`
).all(accountId, pageSize, offset);
return {
balance: getBalance(accountId),
page, pageSize, total,
totalPages: Math.ceil(total / pageSize),
items,
};
}
// 验证
const r1 = getStatement(1, 1, 3);
console.log('余额(分):', r1.balance); // 100000+50000-20000+30000-15000+10000 = 155000
console.log('第1页:', r1.items.map(i => i.id));
console.log('total/totalPages:', r1.total, r1.totalPages);
const r2 = getStatement(1, 2, 3);
console.log('第2页:', r2.items.map(i => i.id));
// 断言
const assert = require('assert');
assert.strictEqual(r1.balance, 155000);
assert.deepStrictEqual(r1.items.map(i => i.id), [5, 4, 3]);
assert.deepStrictEqual(r2.items.map(i => i.id), [2, 1]);
assert.strictEqual(r1.total, 5);
assert.strictEqual(r1.totalPages, 2);
console.log('全部断言通过');
运行与验证
- 保存为
demo.js,执行node demo.js。 - 观察输出:余额应为
155000(分,即 1550.00 元);第1页 id 为[5,4,3],第2页为[2,1],说明按时间倒序且分页正确。 - 通过条件:脚本打印
全部断言通过,无异常抛出。
关键点
- 余额实时汇总而非读缓存字段,保证与流水一致;生产环境可加对账任务校验。
- 排序加
id DESC兜底,避免同一时间戳导致分页错乱或重复。 - 金额用整数分存储,避免浮点误差。