# 个人记账预算系统 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 ``` **数据隔离规则**: 所有接口均需要传入 `userId` 参数,确保用户只能访问自己的数据。 --- ## 三、全局约定 ### 3.1 统一响应格式 所有接口均返回 JSON 格式,遵循统一结构: #### 成功响应 ```json { "success": true, "data": { ... }, "message": "操作成功" } ``` #### 错误响应 ```json { "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) ```typescript interface User { id: number; // 用户唯一标识 name: string; // 用户名 email: string; // 邮箱(唯一) createdAt: string; // 创建时间 } ``` ### 4.2 账户 (Account) ```typescript 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) ```typescript 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) ```typescript 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` **说明**: 用于负载均衡/容器探针,不依赖数据库 **请求**: 无 **响应**: ```json { "success": true, "data": { "timestamp": "2026-04-27T10:00:00.000Z" }, "message": "Personal Finance Backend is running" } ``` #### 5.1.2 API 根路径 **接口**: `GET /api` **说明**: 返回版本信息,用于前端检测后端可达性 **响应**: ```json { "success": true, "data": { "version": "1.0.0" }, "message": "Personal Finance API" } ``` --- ### 5.2 用户模块 `/api/users` > **说明**: MVP 阶段临时接口,生产环境应替换为注册/登录流程 #### 5.2.1 创建用户 **接口**: `POST /api/users` **请求体**: ```json { "name": "测试用户", "email": "test@example.com" } ``` **参数说明**: | 参数 | 类型 | 必填 | 说明 | 校验规则 | |------|------|------|------|---------| | name | string | 是 | 用户名 | 非空字符串 | | email | string | 是 | 邮箱 | 唯一约束 | **成功响应** (200): ```json { "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": "用户创建成功" } ``` **错误响应**: ```json { "success": false, "data": null, "message": "该邮箱已被注册" } ``` **错误码**: | 状态码 | 错误码 | 说明 | |--------|--------|------| | 400 | P2002 | 邮箱重复 | | 500 | INTERNAL_ERROR | 服务器内部错误 | #### 5.2.2 获取用户列表 **接口**: `GET /api/users` **请求**: 无 **响应** (200): ```json { "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): ```json { "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): ```json { "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): ```json { "success": false, "data": null, "message": "账户不存在" } ``` #### 5.3.3 创建账户 **接口**: `POST /api/accounts` **请求体**: ```json { "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): ```json { "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 | **请求体** (部分更新,仅更新传入字段): ```json { "name": "新名称", "balance": 6000 } ``` **响应** (200): ```json { "success": true, "data": { ... }, "message": "账户更新成功" } ``` #### 5.3.5 删除账户 **接口**: `DELETE /api/accounts/:id` **路径参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | number | 是 | 账户 ID | **响应** (200): ```json { "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): ```json { "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): ```json { "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` **请求体**: ```json { "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): ```json { "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": "交易记录创建成功" } ``` **错误响应**: ```json { "success": false, "data": null, "message": "必填字段缺失" } ``` #### 5.4.4 更新交易记录 **接口**: `PUT /api/records/:id` **路径参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | number | 是 | 记录 ID | **请求体** (部分更新): ```json { "type": "expense", "amount": 100, "category": "交通", "description": "打车", "date": "2026-04-27" } ``` **业务逻辑**: ``` 1. 查询原记录 2. 反向冲销原金额对余额的影响 ├─ 原收入:减去原金额 └─ 原支出:加回原金额 3. 更新记录字段(仅更新传入字段) 4. 按新 type/amount 重新计算余额 5. 更新账户余额 6. 提交事务 ``` **响应** (200): ```json { "success": true, "data": { ... }, "message": "交易记录更新成功" } ``` #### 5.4.5 删除交易记录 **接口**: `DELETE /api/records/:id` **路径参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | number | 是 | 记录 ID | **业务逻辑**: ``` 1. 查询原记录 2. 反向冲销余额(撤销记录对余额的影响) ├─ 原收入:减去金额 └─ 原支出:加回金额 3. 更新账户余额 4. 删除记录 5. 提交事务 ``` **响应** (200): ```json { "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): ```json { "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` **请求体**: ```json { "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` **请求体** (部分更新): ```json { "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): ```json { "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): ```json { "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): ```json { "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): ```json { "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): ```json { "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 小时偏移。 **解决方案**: 使用安全日期解析函数 ```javascript 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 获取账户列表 ```typescript import { apiClient } from '@/services/apiClient'; import type { Account, ApiResponse } from '@/types'; const response: ApiResponse = await apiClient.get('/accounts', { userId: 1 }); if (response.success) { console.log('账户列表:', response.data); } ``` ### 8.2 创建交易记录 ```typescript const response: ApiResponse = 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 获取仪表盘数据 ```typescript const response: ApiResponse = 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 至项目仓库