Files
2026-04-29 10:53:19 +08:00

23 KiB
Raw Permalink Blame History

个人记账预算系统 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 用户名 非空字符串
email 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 至项目仓库