23 KiB
个人记账预算系统 API 接口文档
版本: v1.0.0
基础路径:http://localhost:3001/api
技术栈: Node.js + Express + Prisma (SQLite)
更新: 2026-04-27
一、文档说明
本文档遵循 OpenAPI 3.0 规范,定义个人记账预算系统的所有 RESTful API 接口。文档与代码实现保持同步,可作为前后端开发、测试验证、接口调用的权威参考。
二、认证与鉴权
MVP 阶段: 当前所有接口通过 userId 查询参数实现数据隔离,生产环境需替换为 JWT Token 鉴权。
Authorization: Bearer <JWT_TOKEN>
数据隔离规则: 所有接口均需要传入 userId 参数,确保用户只能访问自己的数据。
三、全局约定
3.1 统一响应格式
所有接口均返回 JSON 格式,遵循统一结构:
成功响应
{
"success": true,
"data": { ... },
"message": "操作成功"
}
错误响应
{
"success": false,
"data": null,
"message": "错误信息描述"
}
3.2 HTTP 状态码
| 状态码 | 说明 | 触发场景 |
|---|---|---|
| 200 | 成功 | 请求成功处理 |
| 400 | 客户端错误 | 参数校验失败、必填字段缺失 |
| 404 | 资源不存在 | 请求的记录/账户/预算不存在 |
| 500 | 服务端错误 | 数据库异常、内部逻辑错误 |
3.3 日期格式
| 字段类型 | 格式 | 示例 |
|---|---|---|
| 日期 | YYYY-MM-DD |
2026-04-27 |
| 日期时间 | ISO 8601 | 2026-04-27T10:00:00.000Z |
| 月份 | YYYY-MM |
2026-04 |
3.4 金额处理
- 所有金额字段为 Number 类型,单位为 元
- 精度:保留两位小数
- 示例:
168.50表示 168.5 元
3.5 分页约定
当前接口采用 全量返回 模式,前端负责分页逻辑(每页 10 条)。后续将接入后端分页。
四、核心业务模型
4.1 用户 (User)
interface User {
id: number; // 用户唯一标识
name: string; // 用户名
email: string; // 邮箱(唯一)
createdAt: string; // 创建时间
}
4.2 账户 (Account)
interface Account {
id: number; // 账户唯一标识
userId: number; // 所属用户 ID
name: string; // 账户名称(如"支付宝")
type: string; // 账户类型:payment/bank/cash
color: string; // UI 显示颜色(如 "#1890FF")
balance: number; // 当前余额(元)
createdAt: string; // 创建时间
updatedAt: string; // 更新时间
}
4.3 交易记录 (Record)
interface Record {
id: number; // 记录唯一标识
userId: number; // 所属用户 ID
accountId: number; // 关联账户 ID
type: string; // 类型:income/expense
amount: number; // 金额(元)
category: string; // 分类(如"餐饮")
description?: string; // 备注
date: string; // 交易日期
account?: Account; // 关联账户详情(JOIN 查询填充)
createdAt: string; // 创建时间
updatedAt: string; // 更新时间
}
4.4 预算 (Budget)
interface Budget {
id: number; // 预算唯一标识
userId: number; // 所属用户 ID
category: string; // 预算分类
amount: number; // 预算金额上限(元)
month: string; // 预算月份(YYYY-MM)
createdAt: string; // 创建时间
updatedAt: string; // 更新时间
}
五、接口详情
5.1 健康检查与系统接口
5.1.1 服务健康检查
接口: GET /health
说明: 用于负载均衡/容器探针,不依赖数据库
请求: 无
响应:
{
"success": true,
"data": {
"timestamp": "2026-04-27T10:00:00.000Z"
},
"message": "Personal Finance Backend is running"
}
5.1.2 API 根路径
接口: GET /api
说明: 返回版本信息,用于前端检测后端可达性
响应:
{
"success": true,
"data": {
"version": "1.0.0"
},
"message": "Personal Finance API"
}
5.2 用户模块 /api/users
说明: MVP 阶段临时接口,生产环境应替换为注册/登录流程
5.2.1 创建用户
接口: POST /api/users
请求体:
{
"name": "测试用户",
"email": "test@example.com"
}
参数说明:
| 参数 | 类型 | 必填 | 说明 | 校验规则 |
|---|---|---|---|---|
| name | string | 是 | 用户名 | 非空字符串 |
| string | 是 | 邮箱 | 唯一约束 |
成功响应 (200):
{
"success": true,
"data": {
"id": 1,
"name": "测试用户",
"email": "test@example.com",
"createdAt": "2026-04-27T10:00:00.000Z",
"updatedAt": "2026-04-27T10:00:00.000Z"
},
"message": "用户创建成功"
}
错误响应:
{
"success": false,
"data": null,
"message": "该邮箱已被注册"
}
错误码:
| 状态码 | 错误码 | 说明 |
|---|---|---|
| 400 | P2002 | 邮箱重复 |
| 500 | INTERNAL_ERROR | 服务器内部错误 |
5.2.2 获取用户列表
接口: GET /api/users
请求: 无
响应 (200):
{
"success": true,
"data": [
{
"id": 1,
"name": "测试用户",
"email": "test@example.com",
"createdAt": "2026-04-27T10:00:00.000Z",
"updatedAt": "2026-04-27T10:00:00.000Z"
}
]
}
5.3 账户模块 /api/accounts
5.3.1 获取账户列表
接口: GET /api/accounts
查询参数:
| 参数 | 类型 | 必填 | 位置 | 说明 |
|---|---|---|---|---|
| userId | number | 是 | query | 用户 ID |
请求示例:
GET /api/accounts?userId=1
响应 (200):
{
"success": true,
"data": [
{
"id": 1,
"userId": 1,
"name": "支付宝",
"type": "payment",
"color": "#1890FF",
"balance": 5000,
"createdAt": "2026-04-27T10:00:00.000Z",
"updatedAt": "2026-04-27T10:00:00.000Z"
}
]
}
5.3.2 获取账户详情
接口: GET /api/accounts/:id
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | number | 是 | 账户 ID |
请求示例:
GET /api/accounts/1
响应 (200):
{
"success": true,
"data": {
"id": 1,
"userId": 1,
"name": "支付宝",
"type": "payment",
"color": "#1890FF",
"balance": 5000,
"createdAt": "2026-04-27T10:00:00.000Z",
"updatedAt": "2026-04-27T10:00:00.000Z"
}
}
错误响应 (404):
{
"success": false,
"data": null,
"message": "账户不存在"
}
5.3.3 创建账户
接口: POST /api/accounts
请求体:
{
"userId": 1,
"name": "支付宝",
"type": "payment",
"color": "#1890FF",
"balance": 5000
}
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| userId | number | 是 | - | 用户 ID |
| name | string | 是 | - | 账户名称 |
| type | string | 是 | - | 账户类型:payment/bank/cash |
| color | string | 否 | "#1890FF" | UI 颜色 |
| balance | number | 否 | 0 | 初始余额 |
成功响应 (200):
{
"success": true,
"data": {
"id": 2,
"userId": 1,
"name": "支付宝",
"type": "payment",
"color": "#1890FF",
"balance": 5000,
"createdAt": "2026-04-27T10:00:00.000Z",
"updatedAt": "2026-04-27T10:00:00.000Z"
},
"message": "账户创建成功"
}
5.3.4 更新账户
接口: PUT /api/accounts/:id
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | number | 是 | 账户 ID |
请求体 (部分更新,仅更新传入字段):
{
"name": "新名称",
"balance": 6000
}
响应 (200):
{
"success": true,
"data": { ... },
"message": "账户更新成功"
}
5.3.5 删除账户
接口: DELETE /api/accounts/:id
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | number | 是 | 账户 ID |
响应 (200):
{
"success": true,
"data": null,
"message": "账户删除成功"
}
5.4 交易记录模块 /api/records
核心业务: 创建/更新/删除记录时,使用 Prisma 事务联动更新账户余额,确保数据一致性。
5.4.1 获取交易记录列表
接口: GET /api/records
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| userId | number | 是 | 用户 ID |
| accountId | number | 否 | 账户 ID 筛选 |
| type | string | 否 | 类型筛选:income/expense |
| category | string | 否 | 分类筛选 |
| startDate | string | 否 | 开始日期(YYYY-MM-DD) |
| endDate | string | 否 | 结束日期(YYYY-MM-DD) |
请求示例:
GET /api/records?userId=1&type=expense&startDate=2026-04-01&endDate=2026-04-30
排序规则: 按 createdAt 倒序(最新记录在前)
关联查询: 自动包含 account 对象,便于前端展示账户名
响应 (200):
{
"success": true,
"data": [
{
"id": 5,
"userId": 1,
"accountId": 1,
"type": "expense",
"amount": 68,
"category": "餐饮",
"description": "午饭",
"date": "2026-04-27T00:00:00.000Z",
"account": {
"id": 1,
"name": "支付宝",
"type": "payment",
"color": "#1890FF",
"balance": 4932
},
"createdAt": "2026-04-27T10:30:00.000Z",
"updatedAt": "2026-04-27T10:30:00.000Z"
}
]
}
5.4.2 获取交易记录详情
接口: GET /api/records/:id
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | number | 是 | 记录 ID |
响应 (200):
{
"success": true,
"data": {
"id": 5,
"userId": 1,
"accountId": 1,
"type": "expense",
"amount": 68,
"category": "餐饮",
"description": "午饭",
"date": "2026-04-27T00:00:00.000Z",
"account": { ... },
"createdAt": "2026-04-27T10:30:00.000Z",
"updatedAt": "2026-04-27T10:30:00.000Z"
}
}
5.4.3 创建交易记录
接口: POST /api/records
请求体:
{
"userId": 1,
"accountId": 1,
"type": "expense",
"amount": 68,
"category": "餐饮",
"description": "午饭",
"date": "2026-04-27"
}
参数说明:
| 参数 | 类型 | 必填 | 说明 | 校验规则 |
|---|---|---|---|---|
| userId | number | 是 | 用户 ID | 正整数 |
| accountId | number | 是 | 账户 ID | 正整数 |
| type | string | 是 | 交易类型 | 枚举:income/expense |
| amount | number | 是 | 金额(元) | 必须大于 0 |
| category | string | 是 | 分类名称 | 非空字符串 |
| description | string | 否 | 备注说明 | 最大长度 200 |
| date | string | 是 | 交易日期 | 格式:YYYY-MM-DD |
业务逻辑:
1. 校验必填字段和金额合法性
2. 安全解析日期(本地时区,避免 UTC 偏移)
3. 开启 Prisma 事务
├─ 3.1 创建交易记录
├─ 3.2 查询当前账户余额
├─ 3.3 根据类型计算新余额(income 加,expense 减)
└─ 3.4 更新账户余额
4. 提交事务,返回创建记录
5. 任何步骤失败则整体回滚
成功响应 (200):
{
"success": true,
"data": {
"id": 6,
"userId": 1,
"accountId": 1,
"type": "expense",
"amount": 68,
"category": "餐饮",
"description": "午饭",
"date": "2026-04-27T00:00:00.000Z",
"createdAt": "2026-04-27T10:30:00.000Z",
"updatedAt": "2026-04-27T10:30:00.000Z"
},
"message": "交易记录创建成功"
}
错误响应:
{
"success": false,
"data": null,
"message": "必填字段缺失"
}
5.4.4 更新交易记录
接口: PUT /api/records/:id
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | number | 是 | 记录 ID |
请求体 (部分更新):
{
"type": "expense",
"amount": 100,
"category": "交通",
"description": "打车",
"date": "2026-04-27"
}
业务逻辑:
1. 查询原记录
2. 反向冲销原金额对余额的影响
├─ 原收入:减去原金额
└─ 原支出:加回原金额
3. 更新记录字段(仅更新传入字段)
4. 按新 type/amount 重新计算余额
5. 更新账户余额
6. 提交事务
响应 (200):
{
"success": true,
"data": { ... },
"message": "交易记录更新成功"
}
5.4.5 删除交易记录
接口: DELETE /api/records/:id
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | number | 是 | 记录 ID |
业务逻辑:
1. 查询原记录
2. 反向冲销余额(撤销记录对余额的影响)
├─ 原收入:减去金额
└─ 原支出:加回金额
3. 更新账户余额
4. 删除记录
5. 提交事务
响应 (200):
{
"success": true,
"data": null,
"message": "交易记录删除成功"
}
5.5 预算模块 /api/budgets
5.5.1 获取预算列表
接口: GET /api/budgets
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| userId | number | 是 | 用户 ID |
| month | string | 否 | 月份筛选(YYYY-MM) |
请求示例:
GET /api/budgets?userId=1&month=2026-04
排序规则: 按 createdAt 倒序
响应 (200):
{
"success": true,
"data": [
{
"id": 1,
"userId": 1,
"category": "餐饮",
"amount": 1500,
"month": "2026-04",
"createdAt": "2026-04-27T10:00:00.000Z",
"updatedAt": "2026-04-27T10:00:00.000Z"
}
]
}
5.5.2 获取预算详情
接口: GET /api/budgets/:id
路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | number | 是 | 预算 ID |
5.5.3 创建预算
接口: POST /api/budgets
请求体:
{
"userId": 1,
"category": "餐饮",
"amount": 1500,
"month": "2026-04"
}
参数说明:
| 参数 | 类型 | 必填 | 说明 | 校验规则 |
|---|---|---|---|---|
| userId | number | 是 | 用户 ID | 正整数 |
| category | string | 是 | 预算分类 | 非空字符串 |
| amount | number | 是 | 预算金额(元) | 必须大于 0 |
| month | string | 是 | 预算月份 | 格式:YYYY-MM |
5.5.4 更新预算
接口: PUT /api/budgets/:id
请求体 (部分更新):
{
"amount": 2000
}
5.5.5 删除预算
接口: DELETE /api/budgets/:id
5.6 统计分析模块 /api/statistics
5.6.1 月度统计
接口: GET /api/statistics/monthly
说明: 获取指定月份的总收入、总支出、结余,以及各支出分类的汇总金额(用于饼图)
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| userId | number | 是 | 用户 ID |
| month | string | 是 | 月份(YYYY-MM) |
请求示例:
GET /api/statistics/monthly?userId=1&month=2026-04
响应 (200):
{
"success": true,
"data": {
"totalIncome": 9000,
"totalExpense": 520,
"balance": 8480,
"categoryStats": [
{"category": "餐饮", "amount": 68},
{"category": "交通", "amount": 25},
{"category": "购物", "amount": 299},
{"category": "娱乐", "amount": 128}
]
}
}
5.6.2 趋势统计
接口: GET /api/statistics/trend
说明: 获取按日期聚合的日级收支数据(用于折线图/柱状图)
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| userId | number | 是 | 用户 ID |
| startDate | string | 否 | 开始日期(不传则返回所有数据) |
| endDate | string | 否 | 结束日期 |
请求示例:
GET /api/statistics/trend?userId=1&startDate=2026-04-01&endDate=2026-04-30
排序规则: 按 date 正序
响应 (200):
{
"success": true,
"data": [
{"date": "2026-04-25", "income": 0, "expense": 128},
{"date": "2026-04-26", "income": 0, "expense": 299},
{"date": "2026-04-27", "income": 0, "expense": 93}
]
}
5.6.3 月度对比
接口: GET /api/statistics/compare
说明: 获取当前月与上月的收支对比数据(用于环比分析)
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| userId | number | 是 | 用户 ID |
| month | string | 是 | 月份(YYYY-MM) |
请求示例:
GET /api/statistics/compare?userId=1&month=2026-04
响应 (200):
{
"success": true,
"data": {
"currentMonth": {
"label": "4月",
"income": 9000,
"expense": 520
},
"lastMonth": {
"label": "3月",
"income": 8500,
"expense": 1200
}
}
}
5.7 仪表盘模块 /api/dashboard
5.7.1 仪表盘汇总数据
接口: GET /api/dashboard/summary
说明: 聚合多源数据(账户余额/本月收支/预算使用率),为首页提供一次性数据,减少前端请求次数
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| userId | number | 是 | 用户 ID |
请求示例:
GET /api/dashboard/summary?userId=1
性能优化: 使用 Promise.all 并行查询三类数据(账户、交易、预算)
响应 (200):
{
"success": true,
"data": {
"totalBalance": 18000,
"monthIncome": 9000,
"monthExpense": 520,
"accounts": [
{
"id": 1,
"userId": 1,
"name": "支付宝",
"type": "payment",
"color": "#1890FF",
"balance": 4932
},
{
"id": 2,
"userId": 1,
"name": "微信钱包",
"type": "payment",
"color": "#52C41A",
"balance": 2975
}
],
"budgetUsage": [
{
"id": 1,
"userId": 1,
"category": "餐饮",
"amount": 1500,
"month": "2026-04",
"spent": 68,
"percentage": 4.53
},
{
"id": 2,
"userId": 1,
"category": "交通",
"amount": 500,
"month": "2026-04",
"spent": 25,
"percentage": 5
}
]
}
}
5.8 工具接口
5.8.1 初始化测试数据
接口: GET /api/init-test-data
说明: 仅开发环境使用,用于快速创建测试数据。具有幂等性(已有数据则跳过)
安全警告: 生产环境必须移除此接口
请求示例:
GET /api/init-test-data
响应 (200):
{
"success": true,
"data": {
"userId": 1
},
"message": "数据初始化成功"
}
初始化数据:
- 1 个测试用户(测试用户)
- 3 个账户(支付宝/微信钱包/招商银行)
- 6 笔交易记录(2 收入 + 4 支出)
- 4 笔预算(餐饮/交通/购物/娱乐)
六、业务逻辑说明
6.1 余额联动机制
交易记录的创建/更新/删除均会联动更新账户余额,使用 Prisma 事务确保一致性:
| 操作 | 账户余额变化 |
|---|---|
| 创建收入记录 | balance += amount |
| 创建支出记录 | balance -= amount |
| 更新记录 | 先冲销原金额,再应用新金额 |
| 删除记录 | 反向冲销原金额影响 |
6.2 日期处理机制
问题: new Date("YYYY-MM-DD") 在 JavaScript 中会被当作 UTC 时间解析,导致 UTC+8 时区下出现 8 小时偏移。
解决方案: 使用安全日期解析函数
function parseDate(dateStr) {
if (dateStr.includes('T')) {
return new Date(dateStr); // 完整时间戳直接解析
}
const parts = dateStr.split('-');
if (parts.length === 3) {
// 按本地时区构造日期,月份从 0 开始
return new Date(
parseInt(parts[0]),
parseInt(parts[1]) - 1,
parseInt(parts[2])
);
}
return new Date(dateStr); // 兜底
}
6.3 排序字段选择
规则: 使用 createdAt 而非 date 进行记录排序
原因: date 字段存储业务日期,可能存在 UTC 转换问题;createdAt 是系统自动生成的创建时间戳,更准确反映记录顺序。
七、错误码汇总
7.1 HTTP 状态码
| 状态码 | 说明 | 常见场景 |
|---|---|---|
| 200 | 成功 | 请求成功处理 |
| 400 | 客户端错误 | 参数缺失、校验失败 |
| 404 | 资源不存在 | 记录/账户/预算不存在 |
| 500 | 服务端错误 | 数据库异常 |
7.2 Prisma 错误码
| 错误码 | 说明 | 处理策略 |
|---|---|---|
| P2002 | 唯一约束冲突 | 返回友好提示(如"邮箱已被注册") |
| P2025 | 记录不存在 | 返回 404 状态 |
八、前端调用示例
8.1 获取账户列表
import { apiClient } from '@/services/apiClient';
import type { Account, ApiResponse } from '@/types';
const response: ApiResponse<Account[]> = await apiClient.get('/accounts', {
userId: 1
});
if (response.success) {
console.log('账户列表:', response.data);
}
8.2 创建交易记录
const response: ApiResponse<Record> = await apiClient.post('/records', {
userId: 1,
accountId: 3,
type: 'expense',
amount: 68,
category: '餐饮',
description: '午饭',
date: '2026-04-27'
});
if (response.success) {
console.log('记录创建成功:', response.data);
}
8.3 获取仪表盘数据
const response: ApiResponse<DashboardSummary> = await apiClient.get(
'/dashboard/summary',
{ userId: 1 }
);
if (response.success) {
const { totalBalance, monthIncome, monthExpense, budgetUsage } = response.data;
console.log(`总余额: ¥${totalBalance}`);
}
九、API 变更记录
| 日期 | 版本 | 变更人 | 变更内容 | 影响范围 |
|---|---|---|---|---|
| 2026-04-27 | v1.0.0 | 架构师 | 初始版本,定义所有核心接口 | 全模块 |
| 2026-04-27 | v1.0.1 | 后端 | 修复排序字段(date → createdAt) | /api/records |
| 2026-04-27 | v1.0.2 | 后端 | 增加日期安全解析逻辑 | POST /api/records |
十、安全规范
10.1 当前状态 (MVP)
- 所有接口通过
userId参数做数据隔离 - CORS 允许所有来源(仅开发环境)
- 无请求频率限制
10.2 生产环境要求
- 接入 JWT 鉴权,从 Token 解析 userId,禁止客户端传入
- CORS 限制为前端域名
- 增加请求频率限制(Rate Limit)
- 移除
/api/init-test-data等开发接口 - 增加 SQL 注入防护(Prisma 已提供基础防护)
- 增加请求体大小限制(防止 DoS 攻击)
- 增加 HTTPS 强制
文档版本: v1.0.2
最后更新: 2026-04-27
维护人: 架构师
反馈渠道: 提交 Issue 至项目仓库