AI 开发个人记账 App(服务端篇)

一、背景

念账是一个 Flutter 记账客户端,第一版是快速原型,账号和账本数据都存在本地 SQLite 里。本次给它配上服务端,顺带将账号体系的功能一并完善了。

flowchart LR C[念账客户端] -->|HTTP API| D[后端服务] D --> E[(PostgreSQL)] C -.仅存登录态.-> F[SharedPreferences]

二、技术选型

选择 职责 为什么选它
Node.js 后端开发语言 选择一个我比较熟悉的技术栈。除此之外,Node.js 生态成熟,配合 TypeScript 获得类型安全
Fastify HTTP 服务器框架 API 设计严格,开发者明确定义请求和响应的数据格式,减少开发调试的不确定性。另外,它相对于 Express 性能更好
PostgreSQL 关系型数据库 本项目用 MySQL 也能做,但我更倾向一款类型和约束更严格、扩展能力更强的数据库
pnpm 包管理器,安装与管理依赖 相比 npm / yarn 更快、更省磁盘;依赖隔离严格,只有显式声明的依赖才能引入,避免误用别人顺带装进来的包
JSON Web Token(JWT)+ Refresh Token 认证方案,识别用户身份并维持登录态 JWT 无状态、验证签名即可识别身份,适合移动端;配合可主动吊销的 Refresh Token 维持登录态
Vitest + Supertest 测试工具,跑单元测试与接口集成测试 Vitest 原生支持 ECMAScript Modules(ESM)/ TypeScript、快且兼容 Jest;Supertest 以真实 HTTP 请求验证接口行为

三、工程结构

项目采用经典的三层分层架构(Controller--Service--Repository),并按 feature-first 组织目录:每个业务模块自包含从路由到表结构的完整链路,与业务无关的基础设施放 core/,跨模块共享的放 shared/。模块之间不互相引用,需要共享就下沉。

flowchart TD R[routes 路由] --> C[controller 请求/响应] C --> S[service 业务逻辑] S --> P[repository 数据访问] P --> SC[schema 表结构]

三层各司其职:routes + controller表现层 ,负责 HTTP 输入输出;service应用/业务层 ,承载业务逻辑;repository + schema持久层 ,用 Repository 模式把 SQL 和表映射封在内部。这套 controller → service → repository 的组合,和 Spring Boot、NestJS 等框架的默认分层一脉相承,是最主流的 Web 应用架构。

这套结构强调依赖单向:上层依赖下层,反向不允许,业务逻辑与技术细节尽量分离。controller 只依赖 service、不直接碰数据库,各层职责单一,业务逻辑也能独立测试。

四、认证体系

JWT(JSON Web Token)是一种把用户信息和有效期打包并签名的令牌格式,服务端验证签名即可识别身份、无需查库。Access Token 就是短期的 JWT;Refresh Token 则是长期的不透明随机串,库里只存哈希,因此可主动吊销。刷新时做轮换:旧 Refresh 立即作废、签发新的一对,防止重放;登出吊销对应 Refresh(幂等)。

sequenceDiagram participant C as 客户端 participant S as 服务端 participant DB as 数据库 C->>S: 登录(账号 + 密码) S->>DB: 校验密码 S->>DB: 签发 Access + Refresh,存 Refresh 哈希 S-->>C: Access(15m)+ Refresh(7d) C->>S: 业务请求 + Access Note over C,S: Access 过期 C->>S: 刷新(Refresh) S->>DB: 校验哈希 → 吊销旧 → 签发新对并落库 S-->>C: 新 Access + 新 Refresh

客户端把提前刷新、401 兜底重放、并发单飞全部封进拦截器,业务层无感知。安全默认值:密码 bcrypt 加盐;登录失败统一报"账号或密码错误",不泄露账号是否存在;所有令牌只存哈希。

五、邮箱验证码:注册与找回密码的统一机制

注册和找回密码要做的事其实是同一个套路:先往邮箱发一个验证码,用户提交后校验,通过就授予相应权限------注册是"创建账号",找回密码是"允许改密码"。既然是同一套路,就用一套机制承载,只用一个"用途(purpose)"字段区分这两种场景,不用维护两套代码。

验证码发到邮箱时是明文,但落库前会先做哈希,库里不存明文。哈希时额外掺入三样东西做盐:服务端密钥、邮箱、用途。加盐一是避免不同用户、不同场景的相同验证码在库里长成一样,二是让攻击者即使拿到数据库,也无法反推出验证码。

flowchart TD A[发送验证码] --> B{用途} B -->|注册| C{邮箱未被占用?} B -->|找回密码| D{邮箱已注册?} C -->|否| E[明确业务报错] D -->|否| E C -->|是| F{频控通过?} D -->|是| F F -->|否| G[提示稍后再试] F -->|是| H[生成码 => 哈希落库 => 作废旧码 => 发邮件] H --> I{校验最新记录<br/>未消费/未过期/未超限?} I -->|否| J[失败] I -->|是| K{哈希匹配?} K -->|否| L[失败次数+1] K -->|是| M[消费验证码]

规则围绕"短时效、一次性、可频控":10 分钟有效、用后作废、同邮箱发送有间隔与每日上限、失败到阈值作废。

六、RESTful API

接口采用常见的 RESTful 约定:统一 /api 前缀、资源用复数、多词用短横线,路径嵌套最多两层(如"用户 / 记账条目",不再往"条目下的分类"套),让调用方看路径就能猜到用途。

每个请求都经过同一条流水线:认证中间件认出用户 → Zod 校验参数 → controller → service → repository。认证和校验集中在入口,业务代码不用重复处理。

用户数据隔离:所有读写都强制带当前用户,按 ID 操作也一样,而且这条约束只放在仓储层这一个通道上执行,不会在业务分支里漏掉。越权访问统一返回"资源不存在"而非"无权限",避免泄露资源是否存在。

flowchart LR Req[请求] --> Auth[认证中间件<br/>解析 Access Token] Auth --> Val[Zod 边界校验] Val --> Ctrl[controller] Ctrl --> Svc[service] Svc --> Repo[repository<br/>强制 user_id 过滤] Repo --> DB[(PostgreSQL)]

金额用整数分、日期用 epoch 天,避免浮点误差和时区歧义;时间范围查询强制有界;汇总交给数据库;响应统一成 {code, message, data}

七、错误处理、日志与配置

错误的传递和处理:业务层抛携带业务码、文案、状态的领域错误,全局处理器接住后分类返回;代码异常统一给笼统文案,堆栈只进日志,不暴露内部细节。

flowchart TD A[抛出异常] --> B{是 AppError?} B -->|是| C[按业务码 + 状态返回] B -->|否| D[统一 500 文案] D --> E[完整堆栈写日志]

日志用 Pino,结构化并携带请求 ID;开发可读、生产 JSON、测试静默。密码、令牌、验证码一律不落日志。配置全部走环境变量,有默认值或启动校验,敏感项不硬编码、不进仓库。

八、测试策略

单元测试覆盖纯逻辑和可隔离的业务规则,比如密码哈希、令牌校验、验证码处理、service 分支。外部依赖用替身注入,跑得快、定位准。

集成测试在真实应用实例上通过 Supertest 发 HTTP 请求,覆盖注册、登录、刷新、记账增删改查等完整链路,验证各层拼起来是否真的通。

原则:测业务规则而不是测框架,隔离外部依赖。

  • 数据库用独立测试库,每个用例执行前清空数据、彼此独立;
  • 用例命名统一为"方法 + 场景 + 期望";
  • 核心逻辑覆盖率目标不低于 90%。
相关推荐
唐老板4 小时前
两周三个新漏洞,问题不在模型
ai编程
学者猫头鹰4 小时前
AI 大模型应用遇到的问题及解决方案
ai编程
Sunny_G4 小时前
鸿蒙 Markdown 编辑器表格所见即所得:七个版本的渲染重构(CodeMirror Decoration 实战)
ai编程·harmonyos
烬羽4 小时前
单 Agent 是直线代码,多 Agent 是一张会分岔、循环、暂停的图:LangGraph 工作流编排
agent·ai编程
用户64340495148514 小时前
Elpis 项目构建工具与前端基建实践总结
node.js
feiyu_gao4 小时前
Cocreation Framework:一套给所有人的 AI 协作思考系统
设计模式·aigc·ai编程
星陨5404 小时前
02_向量数据库性能调优:HNSW参数、内存管理与生产级优化实践
ai编程
FungLeo5 小时前
成为全栈·Node 后端篇·后端测试策略:单元、集成与测试数据库
单元测试·node.js·集成测试·测试策略·成为全栈·测试数据库
艺杯羹5 小时前
AI编程时代软件工程怎么学:从底层思维认知到驱动智能体的架构跃迁
java·人工智能·ai·架构·软件工程·ai编程